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

Rust实现简易HTTP服务后,如何正确关闭HTTP TCP流?

问题修复方案

两个核心问题导致连接报错

  1. Content-Length计算错误:你在生成响应头部时,用还未包含metrics内容的response_buffer长度来设置Content-Length,导致声明的长度和实际响应长度不匹配。客户端按声明长度读取后,服务器提前关闭连接,触发重置报错。
  2. 未确保数据完全发送且未优雅关闭连接:直接让stream超出作用域关闭,可能导致TCP缓冲区的数据未发送完成就强制断开,引发客户端报错。

修复后的完整代码

use std::io::{self, Write, Read, Shutdown};
use std::net::{TcpListener, TcpStream};

// 基础HTTP头部(不含Content-Length)
const BASE_HTTP_HEADERS: &str = "HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4; charset=utf-8; escaping=underscores
Connection: close
";

fn handle_client(mut stream: TcpStream, registry: &Registry, method: &Method) -> io::Result<()> {
    metrics.inc_requests(method.clone());
    println!("Connection established!");

    // 先生成metrics响应体内容
    let mut metrics_body = String::new();
    encode(&mut metrics_body, registry)?;

    // 构建完整响应:基础头部 + Content-Length + 空行 + metrics内容
    let content_length = metrics_body.len();
    let response = format!(
        "{BASE_HTTP_HEADERS}Content-Length: {content_length}\n\n{metrics_body}"
    );

    // 确保所有数据写入流并刷新到网络
    stream.write_all(response.as_bytes())?;
    stream.flush()?;

    // 优雅关闭:先关闭写端,告知客户端不再发送数据
    stream.shutdown(Shutdown::Write)?;

    // 读取客户端剩余数据(完成TCP四次握手,避免RST)
    let mut buf = [0; 1024];
    while let Ok(n) = stream.read(&mut buf) {
        if n == 0 { break; }
    }

    Ok(())
}

fn main() -> io::Result<()> {
    let listener = TcpListener::bind("localhost:3003")?;
    let method = Method::Get;
    let registry = Registry::default();

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                if let Err(e) = handle_client(stream, &registry, &method) {
                    eprintln!("Failed to handle client: {}", e);
                }
            }
            Err(e) => eprintln!("Failed to accept connection: {}", e),
        }
    }
    Ok(())
}

关键修复说明

  • 正确计算Content-Length:先生成metrics内容,再用其长度设置Content-Length,保证声明长度与实际响应体完全一致。
  • 使用write_all和flush:write_all确保所有字节写入流,flush强制将缓冲区数据发送到网络,避免数据滞留。
  • 优雅关闭TCP连接:
    • shutdown(Shutdown::Write)发送FIN包,告知客户端服务器已完成数据发送。
    • 读取客户端剩余数据,等待客户端发送FIN包,完成四次握手后再关闭连接,避免发送RST包导致客户端报错。

验证方法

修复后用nc测试,Content-Length的值应与metrics内容的字符数完全匹配,客户端读取完所有数据后会正常断开连接,不会再出现连接重置类报错。

内容的提问来源于stack exchange,提问作者RandomInsano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:53:12