TcpClient关闭/释放前偶发未完成写入操作的原因及解决方案是什么?
问题诱因
核心问题确实和连接释放速度过快直接相关,本质上是两个层面的问题叠加导致的:
- TCP协议层面:你当前使用的
TcpClient默认关闭了*滞留(Linger)*选项,调用Close/Dispose时会直接发送RST包强制断连,操作系统内核缓冲区中还没来得及发送、或者还没收到对端ACK的小包会被直接丢弃。你单次发送的仅20字节属于极小报文,即使开了NoDelay,从用户态写入到内核确认发送完成也需要毫秒级的时间,发完立刻断连就有概率丢最后1~2个包。 - 硬件设备层面:你使用的无修改权限的硬件TCP栈处理能力通常偏弱,部分低性能硬件还会在连接断开时直接丢弃网卡缓冲区中还没来得及上报到业务层的数据,你发完立刻断连就会导致硬件没来得及读完最后几条指令。
你加Thread.Sleep(10)能解决问题的本质就是给内核发数据、硬件处理数据留了足够的缓冲时间。
更优解决方案
不要依赖固定时间Sleep(不同网络环境下需要的等待时长不稳定,也浪费性能),可以用以下更可靠的方案:
- 配置TcpClient滞留选项
在创建TcpClient的时候直接配置滞留规则,让关闭连接时最多等待指定时长把数据发完再断连:
TcpClient client = new TcpClient(); client.NoDelay = true; // 开启滞留,最多等待1秒发完残留数据再断开连接 client.LingerState = new LingerOption(true, 1); client.Connect(ipEndPoint);
这个方案不需要修改现有业务逻辑,改一行配置就能解决99%的场景问题。
- 主动半关闭确认(更可靠)
在所有指令发送完成后、释放资源前,先主动半关闭发送通道,触发内核把所有缓冲区数据发完,再等待极短时间再释放:
// 所有Send调用完成后执行 client.Client.Shutdown(SocketShutdown.Send); // 可选等待100ms以内的时间即可,比固定Sleep 10ms适配性更强 Task.Delay(50).Wait();
如果硬件有指令回复,最优方案是每发一条指令就等待硬件的回复确认,再发下一条,完全不需要盲等。
- 代码小优化
你当前的Send方法每次都调用client.GetStream(),其实可以在TcpSender构造函数里一次性获取NetworkStream存起来,不需要每次调用Send重复获取,不会影响功能但可以减少不必要的调用开销。
内容的提问来源于stack exchange,提问作者Powerslave
相关产品推荐
相关产品推荐

