Rust 实现 Http-Sever

教程来自官方教程
https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html

单线程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();
            }
        }
    }
}

通知线程停止监听

完整代码:

https://codechina.csdn.net/a457636876/rust_study_macro

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值