22-01-13
banq
下面是一个简单的 Web 服务器:
use std::net::{TcpListener, TcpStream}; use std::io::{BufRead, BufReader, Write}; fn main() { let listener = TcpListener::bind("localhost:3000").expect("failed to create TCP listener"); for connection in listener.incoming() { let stream = connection.expect("client connection failed"); handle_connection(stream) } } fn handle_connection(mut stream: TcpStream) { let mut reader = BufReader::new(&mut stream); let mut request = Vec::new(); reader .read_until(b'\n', &mut request) .expect("failed to read from stream"); let request = String::from_utf8(request).expect("malformed request line"); print!("HTTP request line: {}", request); let response = concat!( "HTTP/1.1 200 OK\r\n", "Content-Length: 12\n", "Connection: close\r\n\r\n", "Hello world!" ); stream .write(response.as_bytes()) .expect("failed to write to stream"); stream.flush().expect("failed to flush stream"); } |
但是这个Web一次只能处理一个请求,我们需要能够同时(同时)处理多个请求。为此,我们将使用多线程。
每个程序都从一个线程开始,即主线程。我们可以用std::thread::spawn. 因为我们希望能够同时处理多个请求,所以我们将每个请求处理程序派生到一个新线程上:
fn main() { let listener = TcpListener::bind("localhost:3000").expect("failed to create TCP listener"); for connection in listener.incoming() { let stream = connection.expect("client connection failed"); std::thread::spawn(|| handle_connection(stream)); } } |
spawn函数在创建一个新线程后立即返回,而不是在主线程上运行。
通过这个简单的更改,我们现在可以同时处理许多请求。当一个线程正在等待读取、写入或执行任何其他操作时,其他准备就绪的线程将被允许运行。而且由于操作系统调度程序是抢占式的,我们的服务器永远不会因需要很长时间的请求而被阻塞。
这个解决方案效果很好,因为我们的服务器是I/O bound。在我们要求从连接中读取数据和取回数据之间,我们不一定要做任何事情。我们只是在等待,受到网络数据输入/输出速度的限制。一个CPU限制,另一方面应用处理器的限制,因为它不断地做工作。让它运行得更快的唯一方法是添加更多的 CPU 内核或将工作分配到多台机器上。
让我们看看服务器现在真的快了多少!
单线程版本每秒能够处理 10 个请求,平均需要 10秒来响应。另一方面,多线程服务器每秒处理近 5000 个请求,延迟仅超过 100 毫秒。鉴于我们在处理程序中让线程sleep了 100 毫秒,这非常好:)