Rust TcpStream调用read_to_string方法双向阻塞问题咨询
问题根因
- 标准库的
read_to_string方法会持续从流中读取数据,直到收到*流结束标记(EOF)*才会停止并返回,不会因为单次TCP数据包接收完成就结束读取。 - 客户端完成请求数据的
write操作后,没有主动关闭TCP连接的写方向,服务端的read_to_string始终无法收到EOF,会一直阻塞在读取步骤,不会执行后续返回结果的逻辑。 - 服务端阻塞不返回数据,客户端的
read_to_string同样等待不到EOF,也会进入阻塞状态,最终形成双向死锁。
解决方案
根据使用场景可以选择两种不同的修复方案:
方案1:关闭半连接(适配一问一答短连接场景)
客户端写完所有请求数据后,主动关闭TCP连接的写方向,通知服务端请求已发送完成,服务端即可正常结束读取逻辑。
修改客户端write操作后的代码即可:
stream .write(String::from("What is the best number?").as_bytes()) .unwrap(); // 新增:关闭写方向,发送EOF给服务端 stream.shutdown(std::net::Shutdown::Write).unwrap(); println!("> Done");
注意:生产环境建议用
write_all代替write,避免因为内核缓冲区不足导致部分数据未发出。
方案2:约定通信边界(适配长连接复用场景)
如果需要保持TCP连接进行多轮通信,不要频繁断开重建连接,可以提前约定消息边界,不需要依赖EOF判断读取结束:
- 定长头方案:发送端先发送固定长度(比如4字节)的整数标记后续消息的字节长度,接收端先读取长度,再读取对应长度的字节即可。
- 分隔符方案:约定特殊字符作为消息结束标记(比如换行符
\n),发送端在每条消息末尾追加分隔符,接收端读到分隔符就停止读取。
以分隔符方案为例,修改代码如下:
服务端改用read_line读取到换行符就返回:
use std::{ io::{BufRead, BufReader, Write}, net::TcpListener, }; fn main() { let listener = TcpListener::bind("0.0.0.0:6400").unwrap(); for stream in listener.incoming() { let mut stream = stream.unwrap(); let mut reader = BufReader::new(&stream); let mut buf = String::new(); println!("Waiting for query..."); reader.read_line(&mut buf).unwrap(); println!("> Received query: {}", buf.trim_end()); let query_res = String::from("42\n"); println!("Writing..."); stream.write_all(query_res.as_bytes()).unwrap(); println!("> Done"); } }
客户端发送请求时末尾追加换行符,读取返回也用read_line:
use std::{io::{BufRead, BufReader, Write}, net::TcpStream}; fn main() { let mut stream = TcpStream::connect("127.0.0.1:6400").unwrap(); let mut reader = BufReader::new(&stream); println!("Sending query..."); stream .write_all(String::from("What is the best number?\n").as_bytes()) .unwrap(); println!("> Done"); println!("Receiving..."); let mut buf = String::new(); reader.read_line(&mut buf).unwrap(); println!("> Received: {}", buf.trim_end()); }
内容的提问来源于stack exchange,提问作者Sereyn
相关产品推荐
相关产品推荐

