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

为什么Windows环境下tokio::net::TcpListener的连接会自动断开?

问题诱因
  • 你的代码本身存在逻辑缺陷:仅对单个连接执行一次读取+回写操作,没有循环逻辑持续处理收发。单次读写完成后main函数直接执行完毕,Tokio运行时销毁、进程退出,连接自然会被强制断开。
  • 跨平台表现差异来自Windows与Linux下telnet客户端的发送逻辑不同:
    • Windows自带的telnet默认是行缓冲模式,只有按下回车键时才会把整行输入一次性发送给服务器,此时服务器read调用立刻拿到完整数据,执行回写后进程直接退出,所以你会看到输入内容后连接立刻断开。
    • Linux下的telnet/常用测试工具默认是逐字符发送,或者你测试时没有注意到单次回显后连接实际也会断开,因此误以为运行完全正常。
Windows环境下保持连接的方案

1. 修复基础逻辑,增加循环持续处理收发

单连接持续回显的修复代码如下:

use tokio::{
    io::{AsyncReadExt, AsyncWriteExt}, 
    net::TcpListener,
};

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("localhost:8080").await.unwrap();
    println!("服务器启动,监听8080端口");

    let (mut socket, addr) = listener.accept().await.unwrap();
    println!("客户端连接:{}", addr);
    
    let mut buffer = [0u8; 1024];
    // 增加循环持续处理收发
    loop {
        let bytes_read = match socket.read(&mut buffer).await {
            // 读取到0字节说明客户端主动断开
            Ok(0) => break,
            Ok(n) => n,
            Err(e) => {
                eprintln!("读取失败:{}", e);
                break;
            }
        };

        if let Err(e) = socket.write_all(&buffer[..bytes_read]).await {
            eprintln!("写入失败:{}", e);
            break;
        }
    }

    println!("客户端断开:{}", addr);
}

如果需要支持多客户端同时连接,可对每个连接 spawn 独立异步任务处理:

use tokio::{
    io::{AsyncReadExt, AsyncWriteExt}, 
    net::TcpListener,
};

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("localhost:8080").await.unwrap();
    println!("服务器启动,监听8080端口");

    // 循环监听新连接
    loop {
        let (mut socket, addr) = listener.accept().await.unwrap();
        println!("客户端连接:{}", addr);
        
        // 每个连接启动独立异步任务,不阻塞后续连接接入
        tokio::spawn(async move {
            let mut buffer = [0u8; 1024];
            loop {
                let bytes_read = match socket.read(&mut buffer).await {
                    Ok(0) => break,
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("客户端{}读取失败:{}", addr, e);
                        break;
                    }
                };

                if let Err(e) = socket.write_all(&buffer[..bytes_read]).await {
                    eprintln!("客户端{}写入失败:{}", addr, e);
                    break;
                }
            }
            println!("客户端断开:{}", addr);
        });
    }
}

2. 配置TCP保活(可选,用于长时间无数据传输时保活)

如果需要避免长时间没有数据传输时连接被系统回收,可配置TCP保活参数,步骤如下:

  1. 首先在Cargo.toml添加依赖:
tokio = { version = "1.0", features = ["full"] }
socket2 = "0.5"
  1. 在拿到TCP连接后添加保活配置:
use socket2::{Socket, TcpKeepalive};
use std::time::Duration;

// 接入连接后转换为socket2的Socket实例配置保活参数
let socket = Socket::from_std(socket.into_std().unwrap());
socket.set_tcp_keepalive(&TcpKeepalive::new()
    .with_time(Duration::from_secs(30)) // 30秒无数据就发送保活探测
    .with_interval(Duration::from_secs(10)) // 每次探测间隔10秒
    .with_retries(3) // 重试3次无响应判定连接断开
).unwrap();
// 转换回tokio的TcpStream即可正常使用
let mut socket = tokio::net::TcpStream::from_std(socket.into()).unwrap();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 11:54:03