这个简单的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