一个简单的Web服务器 - 学习教程,Rust, web, http, 服务器 - 学习Rust还是比较推荐有一些基础的编程基础并且有良好的自学能力。所以本教程并不累赘讲述Rust的基础知识,通过实例开发的记录来记录相关的知识。

您当前正在浏览的是本站SEO版网页

请点击确认

马上提升浏览体验

一个简单的Web服务器
RUST 编程 学习 教程 9/23/2024 4:51:00 PM 阅读:6

学习Rust还是比较推荐有一些基础的编程基础并且有良好的自学能力。所以本教程并不累赘讲述Rust的基础知识,通过实例开发的记录来记录相关的知识。 关键字: Rust, web, http, 服务器

单线程 Web 服务器

use std::net::TcpListener;

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

    for stream in listener.incoming() {
        let stream = stream.unwrap();
        println!("端口监听成功!");
    }
}

通过这个例子可以快速的建立一个 Tcp 的端口监听,接下来就可以通过访问 http://127.0.0.1:8080 触发打印事件。

页面输出内容

目前通过浏览器访问的页面并不能成功打开,因为 Response 还没有做任何处理

for stream in listener.incoming() {
    let mut stream = stream.unwrap();
    println!("端口监听成功!");

    let response = "HTTP/1.1 200 OK\r\n\r\nHello World!";
    // 写入返回的数据
    stream.write_all(response.as_bytes()).unwrap();
    // 立即刷新返回数据
    stream.flush().unwrap();
}
现在我们通过浏览器打开,试试!不出意外的话,虽然程序正常运作,但浏览器还是访问错误的! 我们使用Api请求工具发现 `read ECONNRESET` 字样的报错。

所以再将输出的内容返回之前,我们应该要将客户端发送的数据成功读取才行。这样的设计有效的确保了,交互的有效性,只有在服务端接受完整发功过去的信息后,才会正常的接受服务端返回的数据。

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

    for stream in listener.incoming() {
        let mut stream = stream.unwrap();
        println!("端口监听成功!");

        // 暂时无法确认客户端发送的数据量,姑且认为小于1024吧
        let mut buffer = [0; 1024];
        stream.read(&mut buffer).unwrap();

        let response = "HTTP/1.1 200 OK\r\n\r\nHello World!";
        stream.write_all(response.as_bytes()).unwrap();
        stream.flush().unwrap();
    }
}

终于成功在浏览器中返回了 Hello World! :smile:

多线程

目前这样的服务器是没有使用意义的,如果我们返回的数据内容比较多,就会产生几个问题

  1. 并发问题:同时访问网站的用户服务器只能一个一个的接待,一旦服务器处理的时间变长,用户就会产生等待。这不是我们熟知的服务器逻辑
  2. 性能问题:stream.read 每次读取数据时,它直接与底层的输入流(如文件、网络连接等)进行交互。这意味着每次调用都可能触发一个相对昂贵的系统调用(例如从文件读取数据或从网络接收数据)。简单来说,它不适合投入生产!

thread::spawn

thread::spawn 是 Rust 标准库中用于创建新线程的函数。它接受一个闭包作为参数,这个闭包中的代码会在新创建的线程中执行。

利用这个知识点我们可以用来优化上面的单线程服务器代码。 我们创建一个方法 handle_client

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

    let response = format!("HTTP/1.1 200 OK\r\n\r\nHello World!");
    stream.write_all(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

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

    println!("服务启动成功[8080]");
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        thread::spawn(|| {
            handle_client(stream);
        });
    }
}

很好,我们解决了并发问题!

BufReader

BufReader 内部维护了一个缓冲区。当您从 BufReader 读取数据时,它首先检查缓冲区中是否已经有可用的数据。如果有,直接从缓冲区中返回数据,避免了立即进行系统调用。只有当缓冲区为空时,BufReader 才会执行系统调用一次性读取较大块的数据填充缓冲区。这样,通过减少系统调用的次数,提高了读取数据的效率。

fn handle_client(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&mut stream);
    let _: Vec<_> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .collect();

    let response = format!("HTTP/1.1 200 OK\r\n\r\nHello World!");
    stream.write_all(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

这样一个最简单的小型多并发服务器就完成了!

Build 发布应用

Windows

因为当前在 Windows 环境开发,所以发布应用非常简单

cargo build --release

Linux

先了解下目标系统的情况

uname -m

如当前的环境是 aarch64.Linux

rustup target add aarch64-unknown-linux-gnu

下载安装完成后就可以发布了

cargo build --release --target aarch64-unknown-linux-gnu