自制HTTP 1.1服务器高请求下卡顿突增问题求助
我正在学习实现HTTP 1.1服务器,遇到了性能异常:
- 复用Socket的高并发请求测试中,性能表现优异;
- 但在多连接高请求(如5个连接处理5万请求)或客户端禁用keep-alive时,服务器会周期性卡顿,请求处理突增;
- 5000用户各发5个请求并复用Socket的场景下,服务器也会阻塞;
- 少连接多请求(启用keep-alive)时性能极佳,推测是因为复用Socket减少了连接建立开销。
我尝试过多种异步运行时(async_std、smol、may、rayon、OS线程、Tokio),甚至may的官方HTTP示例也存在同样问题。用Go标准库编写的Hello World HTTP服务器在相同测试下表现稳定,说明问题出在我的实现上。
测试命令:
# 50000 requests # 5 connections oha -n 50000 -c 5 --latency-correction --disable-keepalive http://localhost:8080
我的Rust服务器代码:
use std::io; use bytes::BufMut; use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; async fn main_async() -> io::Result<()> { let listener = TcpListener::bind("0.0.0.0:8080").await?; while let Ok((mut stream, _)) = listener.accept().await { tokio::task::spawn(async move { let mut buf = Vec::<u8>::new(); loop { let mut raw_headers = [httparse::EMPTY_HEADER; 16]; let mut raw_request = httparse::Request::new(&mut raw_headers); match raw_request.parse(&buf) { Ok(httparse::Status::Complete(_body_start)) => { // Send back static response for now stream .write_all(b"HTTP/1.1 200 OK Content-Length: 11 Hello World") .await .unwrap(); buf.clear(); } Ok(httparse::Status::Partial) => { let mut temp_buf = vec![0; 512]; match stream.read(&mut temp_buf).await { Ok(0) => break, Ok(n) => buf.put(&temp_buf[0..n]), Err(err) => println!("err = {err:?}"), } continue; } Err(_) => todo!() }; } }); } Ok(()) } fn main() { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .worker_threads(16) .build() .unwrap(); rt.block_on(main_async()).unwrap(); }
请问这是标准库TcpListener的内部缓冲区限制导致,还是我的实现存在疏漏?
问题核心是你的实现存在三个关键疏漏,和TcpListener内部缓冲区无关:
HTTP响应格式不符合规范
你返回的响应中使用单\n作为换行符,但HTTP 1.1协议明确要求必须用\r\n作为头部和响应体的分隔符。客户端可能因等待正确的响应结束标记而卡住,导致请求堆积,最终出现卡顿突增的现象。未保留请求剩余数据
当httparse::Status::Complete返回时,你忽略了_body_start参数——这个值标记了请求体的起始位置,意味着buf中可能还存在当前请求的剩余数据(比如POST请求的body,或者TCP粘包带来的后续请求数据)。直接调用buf.clear()会丢失这些数据,导致下一次请求解析错误,甚至阻塞。禁用keep-alive时未主动关闭连接
当客户端禁用keep-alive时,服务器处理完请求后需要主动关闭连接。如果不关闭,客户端会一直等待服务器的后续响应,导致连接资源被占用,新请求无法及时建立。
修正后的代码
use std::io; use bytes::BufMut; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; async fn handle_connection(mut stream: tokio::net::TcpStream) -> io::Result<()> { let mut buf = Vec::<u8>::new(); loop { let mut raw_headers = [httparse::EMPTY_HEADER; 16]; let mut raw_request = httparse::Request::new(&mut raw_headers); match raw_request.parse(&buf) { Ok(httparse::Status::Complete(body_start)) => { // 保留请求剩余数据,用于后续请求解析 let remaining = buf.split_off(body_start); buf = remaining; // 发送符合HTTP规范的响应 let response = b"HTTP/1.1 200 OK\r\nContent-Length: 11\r\nConnection: close\r\n\r\nHello World"; stream.write_all(response).await?; // 根据客户端Connection头决定是否关闭连接 let keep_alive = raw_request.headers.iter() .find(|h| h.name.eq_ignore_ascii_case("Connection")) .map(|h| std::str::from_utf8(h.value).unwrap_or("")) .unwrap_or("") .eq_ignore_ascii_case("keep-alive"); if !keep_alive { stream.shutdown().await?; break; } } Ok(httparse::Status::Partial) => { // 增大缓冲区减少系统调用次数 let mut temp_buf = vec![0; 1024]; match stream.read(&mut temp_buf).await { Ok(0) => break, // 客户端主动关闭连接 Ok(n) => buf.put(&temp_buf[0..n]), Err(err) => { eprintln!("读取错误: {err:?}"); break; } } } Err(err) => { eprintln!("请求解析错误: {err:?}"); break; } }; } Ok(()) } async fn main_async() -> io::Result<()> { let listener = TcpListener::bind("0.0.0.0:8080").await?; while let Ok((stream, _)) = listener.accept().await { tokio::task::spawn(handle_connection(stream)); } Ok(()) } fn main() { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .worker_threads(16) .build() .unwrap(); rt.block_on(main_async()).unwrap(); }
额外优化点
- 将连接处理逻辑抽为单独函数,提升代码可读性;
- 增大读取缓冲区至1024字节,减少系统调用次数;
- 显式处理
Connection请求头,严格遵循HTTP连接管理规则; - 完善错误处理,避免
unwrap导致程序panic; - 保留请求剩余数据,确保TCP粘包场景下的请求解析正确性。
测试修正后的版本,应该能解决周期性卡顿问题,在禁用keep-alive的场景下也能稳定处理请求。
内容的提问来源于stack exchange,提问作者David Alsh

