为什么Windows环境下tokio::net::TcpListener的连接会自动断开?
问题诱因
- 你的代码本身存在逻辑缺陷:仅对单个连接执行一次读取+回写操作,没有循环逻辑持续处理收发。单次读写完成后
main函数直接执行完毕,Tokio运行时销毁、进程退出,连接自然会被强制断开。 - 跨平台表现差异来自Windows与Linux下telnet客户端的发送逻辑不同:
- Windows自带的telnet默认是行缓冲模式,只有按下回车键时才会把整行输入一次性发送给服务器,此时服务器
read调用立刻拿到完整数据,执行回写后进程直接退出,所以你会看到输入内容后连接立刻断开。 - Linux下的telnet/常用测试工具默认是逐字符发送,或者你测试时没有注意到单次回显后连接实际也会断开,因此误以为运行完全正常。
- Windows自带的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保活参数,步骤如下:
- 首先在
Cargo.toml添加依赖:
tokio = { version = "1.0", features = ["full"] } socket2 = "0.5"
- 在拿到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
相关产品推荐
相关产品推荐

