我在一个TCP代理服务器上工作,其中一个客户端连接到它,它启动一个到后端的新连接,并转发所有数据包(双向)。处理这一部分的函数如下所示。
pub fn route(source: TcpStream, worker: Worker) -> Result<()> {
// `source` and `destination` are both `TcpStream`
let mut source = source;
let mut destination = TcpStream::connect(worker.address)?;
let mut source_copy = source.try_clone()?;
let mut destination_copy = destination.try_clone()?;
let src2dst = std::thread::spawn(move || {
std::io::copy(&mut source_copy, &mut destination_copy).unwrap();
});
let dst2src = std::thread::spawn(move || {
std::io::copy(&mut destination, &mut source).unwrap();
// source.shutdown(Shutdown::Both).unwrap(); <--- if this line is commented, it will stuck
});
src2dst.join().unwrap();
dst2src.join().unwrap();
Ok(())
}但是,在其当前形式中,此函数将被卡住。特别是,如果我在destination停止写入时关闭了source,它就不会阻塞。但是我仍然不确定为什么它能工作(或者为什么它不能)。我目前只使用它来代理HTTP流量,并且它似乎没有问题。但我不确定它是否适用于通用TCP。这样做的正确方法是什么?
发布于 2021-10-28 22:53:31
std::io::copy从读取器读取,直到它获得文件结束条件。在TCP连接上发出文件结束状态的信号的方法是发送一个FIN包,这是通过调用shutdown关闭连接的写半部分在软件中实现的。
HTTP允许重用单个连接来逐个发送多个请求。请求中有足够的信息供接收方确定请求的结束位置,同样,响应也有足够的信息。在你的例子中,后端似乎关闭了它的写连接,这会导致你的io::copy返回。如果超文本传输协议响应的报头中有Connection: close,那么您必须关闭写半部分,否则客户端将挂起等待响应体,因为它从未接收到FIN数据包。
TL;DR:当A到B的拷贝正常返回时,您应该关闭B的写半部分,以转发文件末尾状态(FIN数据包)。这适用于两个方向的副本,并且通常对TCP有效。关闭读的那一半套接字似乎没有必要(它不会生成任何TCP数据包)。
https://stackoverflow.com/questions/69761475
复制相似问题