You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自制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内部缓冲区无关:

  1. HTTP响应格式不符合规范
    你返回的响应中使用单\n作为换行符,但HTTP 1.1协议明确要求必须用\r\n作为头部和响应体的分隔符。客户端可能因等待正确的响应结束标记而卡住,导致请求堆积,最终出现卡顿突增的现象。

  2. 未保留请求剩余数据
    当httparse::Status::Complete返回时,你忽略了_body_start参数——这个值标记了请求体的起始位置,意味着buf中可能还存在当前请求的剩余数据(比如POST请求的body,或者TCP粘包带来的后续请求数据)。直接调用buf.clear()会丢失这些数据,导致下一次请求解析错误,甚至阻塞。

  3. 禁用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 10:54:54