为何使用SslStream关闭Socket时会触发TCP RST而非FIN?
为何使用SslStream作为客户端时关闭Socket会产生TCP RST报文?
问题场景
直接操作Socket调用Shutdown(Shutdown.Both)时,远端会收到标准TCP FIN报文:
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(IPAddress.Loopback, 1234); socket.Send(new byte[] {0xc3, 0x45}); socket.Shutdown(Shutdown.Both);
但通过SslStream以客户端身份完成认证后,同样调用socket.Shutdown(Shutdown.Both),远端收到的却是TCP RST报文:
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(IPAddress.Loopback, 1234); var ns = new NetworkStream(socket, false); // 不拥有Socket所有权 var ssl = new SslStream(ns, true); // 不关闭内部流 ssl.AuthenticateAsClient("foobar.com"); ssl.Write(new byte[] {0xc3, 0x45}); socket.Shutdown(Shutdown.Both);
已通过Wireshark确认报文类型,同时存在以下关键现象:
- 若仅调用
socket.Shutdown(Shutdown.Send),远端能正常收到TCP FIN报文; - 若切换为服务器端调用
AuthenticateAsServer,执行相同操作会得到预期的TCP FIN报文。
原因分析
核心原因在于SslStream客户端完成认证后,内部会维持SSL会话的待处理读取上下文:
- 当直接调用
Shutdown(Shutdown.Both)时,相当于同时强制关闭了Socket的发送和接收通道。发送通道关闭会触发SSL层的关闭通知,但接收通道的突然关闭会打断SslStream内部等待服务器端返回的SSL会话收尾报文; - 此时Socket的接收缓冲区可能还有未被SslStream处理的SSL相关数据,操作系统检测到这种异常中断的连接状态,会直接发送RST报文终止连接,而非执行正常的TCP FIN握手流程。
而直接操作Socket时,不存在SSL层的额外会话上下文,Shutdown(Shutdown.Both)只是单纯触发TCP双向关闭的标准FIN流程。作为服务器端时,SslStream的内部状态逻辑不同——服务器端完成数据发送后,没有客户端那种等待后续SSL报文的上下文,因此Shutdown(Shutdown.Both)能正常触发FIN。
单独调用Shutdown(Shutdown.Send)时,仅关闭发送通道,接收通道仍保持开放,SslStream可以正常处理剩余的SSL收尾数据,之后TCP层就能正常发送FIN,这也解释了为什么单独关闭发送能得到预期结果。
解决方案
要在客户端场景下触发正常的TCP FIN报文,需先完成SSL层的优雅关闭,再操作Socket:
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(IPAddress.Loopback, 1234); var ns = new NetworkStream(socket, false); var ssl = new SslStream(ns, true); ssl.AuthenticateAsClient("foobar.com"); ssl.Write(new byte[] {0xc3, 0x45}); // 1. 先完成SSL层的优雅会话关闭 ssl.Shutdown(); // 2. 再关闭Socket的双向通道 socket.Shutdown(Shutdown.Both); // 3. 最后释放Socket资源 socket.Close();
通过先让SSL层完成自身的会话收尾流程,再触发TCP层的关闭操作,就能避免RST报文的产生,实现正常的连接终止。
内容的提问来源于stack exchange,提问作者uriDium
相关产品推荐
相关产品推荐

