教程来自官方教程
https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html
Rust Http-Server
单线程WebServer
监听TCP链接
监听传入的TCP流,并在接收到流时打印信息。
use std::net::TcpListener;
fn main() {
// ① 绑定IP 和 端口
// bind 函数返回类型是 Result<T, E>,意味着绑定操作可能发生失败。
// 如
// 绑定小于等于1024的端口,需要管理员权限。
// 端口已被绑定。
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
// ② listener.incoming() 返回一个Stream的迭代器
for stream in listener.incoming() {
// ③ unwrap 出现任何错误结束程序
let stream = stream.unwrap();
// ④ 一个客户端链接成功,输出以下信息
println!("Connection established!");
}
}
读取请求
// ①
// 以下写法还可以改成如下
// use std::net::TcpListener;
// use std::net::TcpStream;
use std::net::{TcpListener, TcpStream};
use std::io::prelude::*;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
// ② stream 声明为可变的,因为TCPStream的内部状态可能会改变
fn handle_connection(mut stream: TcpStream) {
// ③ 在栈上声明一个名为buffer的1024字节的缓冲区
let mut buffer = [0; 1024];
// ④ 调用read方法,读取数据并存储至缓冲区
stream.read(&mut buffer).unwrap();
// ⑤ String::from_utf8_lossy 将缓冲区中的字节转换为字符串
// lossy 表示遇到无效UTF-8序列时,用替换为 问号
println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
}
编写响应
use std::net::{TcpListener, TcpStream};
use std::io::prelude::*;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
// ① 定义成功的响应数据
let response = "HTTP/1.1 200 OK\r\n\r\nHello, world!";
// ② as_bytes 将字符串转换为字节
// ③ 将字节发送到连接中。
stream.write(response.as_bytes()).unwrap();
// ④ 等待并阻止程序继续运行,直到所有字节都已经写入连接中。
stream.flush().unwrap();
}
读取HTML文件并渲染
use std::net::{TcpListener, TcpStream};
use std::io::prelude::*;
// ① 标准库的文件系统模块引入作用域中
use std::fs;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
// ② 读取 hello.html 并通过 format! 添加到 消息体中!
let contents = fs::read_to_string("hello.html").unwrap();
let response = format!("HTTP/1.1 200 OK\r\n\r\n {}", contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
验证请求有效性并选择性的响应
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";
// ① 匹配是否是 GET / HTTP/1.1 开头
// 是:读取hello.html
// 否:读取404.html
if buffer.starts_with(get) {
let contents = fs::read_to_string("hello.html").unwrap();
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
contents.len(),
contents
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
} else {
let status_line = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
let contents = fs::read_to_string("404.html").unwrap();
let response = format!("{}{}", status_line, contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
}
重构
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 (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\r\n", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n", "404.html")
};
let contents = fs::read_to_string(filename).unwrap();
let response = format!("{}Content-Length: {}\r\n\r\n{}", status_line, contents.len(), contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
多线程服务器
模拟慢速请求
use std::thread;
use std::time::Duration;
///...略
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", "hello.html")
} else if buffer.starts_with(sleep) {
// ① 服务器接收到请求休眠5秒钟
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n", "404.html")
};
///...略
}
使用线程池改进吞吐量
先贴上代码学习,后面再讲区别和缺点。
为每个请求创建独立线程
use hello::ThreadPool;
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);
});
}
}
使用固定数量的线程池
创建接口
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::new(4);
for stream in listener.incoming() {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
}
lib.rs
pub struct ThreadPool;
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
ThreadPool
}
pub fn execute<F>(&self, f: F)
where
// ① FnOnce 后的() 表传入的闭包既没有参数,也没有返回值
F: FnOnce() + Send + 'static,
{
}
}
在new中验证线程数量
创建用于存放线程的空间
lib.rs
use std::thread;
pub struct ThreadPool {
// ② 创建容纳thread::JoinHandle<()>实例向量
threads: Vec<thread::JoinHandle<()>>,
}
impl ThreadPool {
/// 创建 ThreadPool
/// size:线程池数量
/// # Panics
/// 当size为0时,处罚panic
pub fn new(size: usize) -> ThreadPool {
/// ① 除了用assert!还可以返回Result
/// pub fn new(size: usize) -> Result<ThreadPool, PoolCreationError> {
assert!(size > 0);
let mut threads = Vec::with_capacity(size);
for _ in 0..size {
}
ThreadPool { threads }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
}
}
创建线程
use std::thread;
pub struct ThreadPool {
workers: Vec<Worker>,
}
impl ThreadPool {
// --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id));
}
ThreadPool { workers }
}
// --snip--
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize) -> Worker {
let thread = thread::spawn(|| {});
Worker { id, thread }
}
}
使用通道请求发送给线程
/// ---略
use std::sync::Arc;
use std::sync::Mutex;
/// ---略
impl ThreadPool {
// --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
// ① 将通道的接收端放入 Arc 和 Mutex
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
// ② 创建新的Worker克隆Arc来增加引用计数,从而使工作线程共享接收端的所有权
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
// --snip--
}
/// ---略
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
/// ---略
}
}
实现execute方法
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
// --snip--
// ① 创建一个类型别名
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
// --snip--
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
// ② 创建一个新的Job实例
let job = Box::new(f);
// ③ 传递给通道的发送端
// 发送通道可能会失败,如
// 线程已经停止执行
self.sender.send(job).unwrap();
}
}
// --snip--
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
// ① receiver.lock() 请求互斥锁
// unwrap 处理所有可能出现的错误的情形。
// recv 从通道中接收Job
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {} got a job; executing.", id);
job();
});
Worker { id, thread }
}
}
至此多线程WebServer中,就可以正常运行了。
在官网的教程的末尾还提到了 这里为啥不能用 while let 的介绍,大家还是多读几次理解。
主要原始是,获取到的lock,什么时候释放的区别。
https://doc.rust-lang.org/book/ch20-02-multithreaded.html
优雅地停机与清理
struct Worker {
id: usize,
// ① 用Option包装,然后就可以调用take方法将Some变体的值移出来
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let job = receiver.lock().unwrap().recv().unwrap();
job();
}
});
Worker {
id,
// ② 用Some包装thread值
thread: Some(thread),
}
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
// ③ take取出值
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
通知线程停止监听
完整代码: