The Rust Programming Language - 第20章 最后的项目:构建多线程web server - 20.2 将单线程server变为多线程server

20 最后的项目:构建多线程web server

本章我们将会练习一个项目,顺便复习一下前面几章的内容,这个项目会实现宇哥返回”Hello“的web server

如下是我们的计划:

1.学习一些TCP与HTTP知识

2.在套接字(socket)上监听TCP请求

3.解析少量的HTTP请求

4.创建一个合适的HTTP响应

5.通过线程池改善server的吞吐量

但是注意,我们本次实例并不是使用Rust构建web server最好的方法。crates.io上有很多可用于生产环境的crate。它们提供了比我们所要编写的更为完整的web server 和线程池的实现

所以本章的目的在于学习而不是实现,走捷径。因为Rust是一个系统编程语言,我们能够选择处理什么层次的抽象,,并能够选择比其它语言可能或可用的层次更低的层次,因此我们将自己编写一个基础的HTTP server和线程池,以便学习将来可能用到的crate背后的通用理念和技术

20.2 将单线程server变为多线程server

目前的server会依次处理每一个请求,这意味着当出现大量请求时,它的性能会变差

在当前server实现中模拟慢请求

我们现在代码中加入一个变量sleep,当我们调用它的时候,让它睡眠5s再打印结果,这种延迟其实和慢请求时差不多的

use std::time::Duration;
use std::thread;

fn handle_connection(mut stream:TcpStream) {
    let mut buffer = [0;1024];
    stream.read(&mut buffer).unwrap();

    let get = b"GET / HTTP/1.1\r\n";
    let sleep = b"GET /sleep HTTP/1.1\r\n";

    let (status_line,filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n","hello.html")
    }else if buffer.starts_with(sleep){
        thread::sleep(Duration::from_secs(10));
        ("HTTP/1.1 200 OK\r\n\r\n","hello.html")
    }else {
        ("HTTP/1.1 404 NOT FOUND", "404.html")
    };
    
    let contents = fs::read_to_string(filename).unwrap();

    let response = format!("{}\r\nContent-Length: {}\r\n\r\n", status_line,contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

当我们运行代码,在浏览器中使用127.0.0.1:7878/sleep ,我们会发现结果也加载了5s才出现,如果快请求在慢请求之后的话,也要等待

当然,有很多办法能够改变我们web server使其避免所有的请求都排在慢请求之后,线程池就是一个方法

使用线程池改善吞吐量

线程池是一组预先分配的等待或准备处理任务的线程。当程序收到新任务,线程池中的某个线程会被分配到任务,这个线程就会离开并处理任务,其他线程等待处理其他任务,所有线程可并行,先处理完的回池等待。这样就可以增加server的吞吐量

我们会将线程池线程设置为数量较少以防止Dos攻击。线程池会维护一个接收请求的队列。每一个线程排队索取请求并处理请求,这种设计可以让N个线程同时并发处理N个线程

这只是改善web server的方法之一,还有fork/join模型和单线程异步I/O模型。对于Rust这样底层的语言,所有的这些都是可能的

在开始之前,我们先通过编写客户端接口来指导代码设计。我们会用期望的方式来构建API代码的结构,接着在这个结构内实现功能,而不是实现功能再设计公有API

为每个请求分配线程的代码结构

上面的问题在于我们只有一个线程,因此每次只能处理一个请求,现在我们来为每一个请求建立一个线程(这样做当然是有问题的,线程数量要可控,在这里却是无限的)

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        thread::spawn(||{
            handle_connection(stream);
        });
    }
}

这里我们创建了一个新线程,并在其中运行闭包的代码。这样访问http://127.0.0.1:7878/sleep时在另一个浏览器窗口访问http://127.0.0.1:7878就不会要等到睡眠结束才能处理了

为有限数量的线程创建一个类似的接口

为了控制线程数量我们来创建一个“线程池”的概念,并且为了让线程切换到线程池不会对使用该API的代码做较大的修改,我们来编写一个类似的接口

fn main() {
   

你可能感兴趣的:(#,Rust,rust,前端,开发语言,多线程,web,server)