Rust 编写一个简单,高并发的http服务(纯标准库,编译后168kb),附并发压力测试

时间:2024-11-11 17:21:05

这个简单的Rust程序只有两个文件:

release编译二进制文件的大小约为168kb,只使用了标准库(跟官网案例差不多,但是有些不同)

简单实现了路由功能,本地文件读取……优化了官网案例(例如512的buffer过小)


接下来开始编写:

首先找一个目录,创建

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Hello!</h1>
    <p>Hi from Rust</p>
  </body>
</html>

接着创建

use std::fs::File;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::thread;

const CRLF: &str = "\r\n";
fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        thread::spawn(|| handle_connection(stream));
    }
}

// 首页
fn handle_index() -> (String, String) {
    (file_return(""), status(200, "OK"))
}

// 404页面
fn handle_404() -> (String, String) {
    (String::new(), status(200, "OK"))
}

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

    let _matched = |route: &str| matched(&buffer, route);
    let _write = |(contents, status)| write(stream, contents, status);

    // 路由处理
    if _matched("/") {
        _write(handle_index());
    } else {
        _write(handle_404());
    }
}

// 路由匹配
fn matched(buffer: &[u8; 4096], route: &str) -> bool {
    let s = format!("GET {} HTTP/1.1{}", route, CRLF);
    buffer.starts_with(s.as_bytes())
}

// 读取本地文件内容
fn file_return(file_name: &str) -> String {
    let mut file = File::open(file_name).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    contents
}

fn status(code: i32, text: &str) -> String {
    format!("HTTP/1.1 {} {}{}", code, text, CRLF)
}

// 将响应写出到流
fn write(mut stream: TcpStream, contents: String, status: String) {
    let content_type = format!("Content-Type: text/html;charset=utf-8{}", CRLF);
    let server = format!("Server: Rust{}", CRLF);
    let content_length = format!("Content-Length: {}{}", contents.as_bytes().len(), CRLF);
    let response = format!(
        "{0}{1}{2}{3}{4}{5}",
        status, server, content_type, content_length, CRLF, contents
    );
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

打开命令行,进行release编译(最大优化,文件大小168kb):

rustc  -C opt-level=3 -C debuginfo=0

如果直接执行rustc ,进行的是debug编译,文件大小约为210kb

在这里插入图片描述

编译后将生成(unix系统下无.exe后缀),我们运行它:

./main

接着打开网址:

http://127.0.0.1:8080

即可看到我们的页面!

在这里插入图片描述


可以试试压力测试,看看我们Rust编写的玩具HTTP服务器性能如何。

我选择使用loadtest进行压力测试,这是一个npm包,需要在环境下运行。
npm地址:/package/loadtest

安装:npm install -g loadtest

然后运行测试命令:

loadtest -t 10 -c 100 "http://127.0.0.1:8080/"

简单解释下命令:

  • -t 10 代表测试最长进行10秒
  • -c 100 代表并发数100
  • "http://127.0.0.1:8080/" 测试网址(这里必须以/结尾)

测试结果:

  • 看来吃下100个并发请求对于这个Rust HTTP服务而言轻轻松松,在我的机器配置下每秒大约可以处理4091请求,10秒钟共处理了40915个请求,错误的请求数量为0,每个请求平均花费24ms。

在这里插入图片描述
那么,我直接将并发增加到10000会怎么样呢:

  • 可以看到,在10000并发下,这个程序每秒只能处理3158个请求,每个请求的平均处理时间也变为了2.6s,速度变慢了许多,但是还是没有出现错误的请求,说明这个服务性能依然可以(测试期间,内存占用最高达到200MB

在这里插入图片描述
接着我不断的增加并发数,并发数来到了16000

  • 可以看到,现在每个请求的处理时间约为4s,基本快不行了
    在这里插入图片描述
    并发继续增加,来到17000

  • 可以看到我们的玩具HTTP服务已经彻底崩了,所有的请求都错误,说明已经达到了极限,17000的并发。(应该是我系统的上限了)

在这里插入图片描述

说明我们的Rust语言性能确实是极好的,这个168kb的玩具HTTP服务能抗下16000的并发,已经很不容易了……

当然,并发越高时程序的内存占用越大,这是由于Rust需要建立很多新线程来进行处理。

而且本次我们编写的HTTP服务并没有用到异步,只用到了多线程,Rust的性能并没有完全的展现。

关于Rust的异步可以看看:
/crates/futures
/tokio-rs/tokio