21-最后的项目:构建多线程web server
手写 TCP/HTTP 单线程 server、线程池并发与优雅停机实现精要
3 分钟阅读
精要笔记 · 基于 TRPL 简体中文 第21章
最后的项目:构建多线程 web server
综合复习项目:手写基础 HTTP server + 线程池。非生产最佳实践(可用 crates.io 现成库),目的是理解底层机制;本章不用 async/await。
构建单线程 web server
监听 TCP 连接
| |
std::net::TcpListener:bind绑定端口(7878 ≈ “rust”);incoming()迭代连接尝试。- 浏览器可能多连接(favicon、预连接等);无响应时连接 drop 关闭,浏览器或重连。
读取请求
| |
BufReader+lines()按行读;连续两个\r\n(空行)标志请求头结束。- 请求行格式:
Method Request-URI HTTP-Version CRLF
更仔细地观察 HTTP 请求
| |
- 第一行:方法(GET/POST)、URI(如
/)、HTTP 版本。 - 后续为 header;GET 通常无 body。
编写响应
| |
- 状态行 + headers + 空行 + body。
stream.write_all(response.as_bytes())
返回真正的 HTML
- 读
hello.html;响应加Content-Lengthheader。 format!拼状态行、header、body。
验证请求并有选择的进行响应
- 只匹配
GET / HTTP/1.1→ 200 +hello.html。 - 其他 → 404 +
404.html。 - 重构:
let (status_line, filename) = if ... else ...;+let解构,消除重复读写逻辑。
将单线程 server 变为多线程 server
模拟慢请求
/sleep分支thread::sleep(Duration::from_secs(5))。- 单线程下慢请求阻塞后续请求 → 需并发。
使用线程池改善吞吐量
线程池:固定数量 worker 线程 + 任务队列;限制并发防 DoS。
为每一个请求分配线程
thread::spawn(|| handle_connection(stream)) — 简单但可无限创建线程。
创建有限数量的线程
目标 API:
| |
采用编译器驱动开发构建 ThreadPool
- 定义
ThreadPool结构体(src/lib.rs库 crate)。 new(size: usize) -> Self;size == 0时assert!panic。execute<F>(&self, f: F) where F: FnOnce() + Send + 'static(对齐thread::spawn约束)。
在 new 中验证线程池的线程数量
assert!(size > 0, "Number of threads must be greater than 0");
分配空间以存储线程
- 存
Vec<Worker>而非裸JoinHandle;with_capacity(size)预分配。
将代码从 ThreadPool 发送给线程
Worker结构体:id+JoinHandle<()>;启动时空闭包占位。
使用信道向线程发送请求
ThreadPool持有mpsc::Sender<Job>。- 每个
Worker共享Arc<Mutex<Receiver<Job>>>(多生产者单消费者 → 共享接收端)。 type Job = Box<dyn FnOnce() + Send + 'static>;execute:sender.send(Box::new(f))。- Worker 循环:
while let Ok(job) = receiver.lock().unwrap().recv() { job(); }
while let陷阱:while let Ok(job) = receiver.lock().unwrap().recv()会在整个循环体持有锁;应let job = receiver.lock().unwrap().recv().unwrap();让锁在let结束时释放。
优雅停机与清理
为 ThreadPool 实现 Drop Trait
drop中对每个 worker 调用thread.join()。- 用
self.workers.drain(..)取出所有Worker再 join(避免借用冲突)。
向线程发出信号,让它们停止接收任务
sender改为Option<Sender<Job>>;drop时drop(sender)关闭信道。- Worker 中
recv返回Err时break退出循环。 - 演示:
for stream in listener.incoming().take(2)处理两请求后退出,ThreadPooldrop 触发清理。
总结
完整路径:TcpListener → 解析 HTTP → 响应 → ThreadPool(Worker + mpsc + Arc/Mutex)→ Drop 优雅停机。生产环境请用成熟 web 框架与线程池 crate。