发送后如何正确关闭套接字(使用 IOCP)?
Posted
技术标签:
【中文标题】发送后如何正确关闭套接字(使用 IOCP)?【英文标题】:How close a socket (with IOCP) properly after sending? 【发布时间】:2019-03-27 06:20:53 【问题描述】:当我需要在发送请求的数据后关闭连接时,我在使用 IOCP(重叠 IO 模式)时遇到了 Winsock2 问题。
我发现如果我发送一些数据并在发送后立即关闭套接字,那么数据将不会(或部分)发送,因为在关闭之前没有时间发送数据包。
我还发现有多种断开连接的方法,例如我现在使用的WSA_SendDisconnect()
。
现在的实现是这样的:
发送数据
设置一个标志,表示发送后关闭连接的意图
当关于发送事件的 IOCP 事件发生时如果设置了标志并且bytecount > 0
,那么我再次发送一个空缓冲区,只是为了确保所有数据都已发送
当关于发送事件的 IOCP 事件发生时如果设置了标志并且bytecount == 0
然后我清除标志并调用
WSA_SendDisconnect()
缓冲区为空,发送断开消息
bytecount == 0
,然后我调用 closesocket()
并销毁上下文。
通过这个过程,我几乎可以确定只有在发送完所有数据后套接字才会断开连接,实际上它工作得很好,但是当我在一些非常罕见的情况下对程序进行压力测试时,在 1000000 次测试中进行了 10-15 次出了点问题。 (很难确定确切的问题,该程序作为 Web 服务器工作,我正在使用 apachebench 和 siege 对其进行压力测试,测试后我得到一个总结,有时我看到 10-15 个失败的请求.)
我很确定失败是因为套接字在关闭之前无法发送整个数据包,所以我在socketclose()
之前放置了一点延迟,因为我收到了零个失败的请求,但当然,这不是一个解决方案,只是一种过滤可能导致问题的方法。
我实现的方法显然缺乏正确的方法来检测套接字是否在关闭之前发送了所有内容,即使我只是在最后一次虚拟写入完成后才开始关闭。
只有在所有发送缓冲区都实际写入网络后才关闭套接字的最佳解决方案是什么?
Ps:我尝试过使用 NoDelay 并设置 LingerState,但它们没有帮助。
【问题讨论】:
XY 问题。在发送 FIN 之前,TCP 套接字完全清除了数据。你的问题是基于一个错误的前提。通过 TCP 发送一个空缓冲区完全没有任何作用。 似乎在写入发送缓冲区后立即使用 Winsock 和 IOCP 调用 socketclose() 在刷新之前关闭套接字。我很清楚不仅通过 TCP 发送空包,而且通过任何以太网层发送空包什么都不做。我只想确保数据包序列以一个 0 字节长的数据包结尾,它立即返回一个成功的写入事件,而不会对实际流量产生任何影响,这样我就可以关闭套接字。它不仅仅是我程序流程中的一个标记。 实际上发送一个空的UDP数据报确实有效。无论如何,在最后一次写入的 I/O 完成之前,您应该做的就是不要关闭。这应该不难安排。 用TF_REUSE_SOCKET
呼叫DisconnectEx
。当此请求完成时,您可以关闭套接字
@beatcoder "我还没有找到任何实用的解决方案来检测数据是否已从缓冲区中刷新" - 因为不存在这样的信号。如果你真的需要这个,你将不得不直接监控网络硬件,例如使用 libpcap,以查看数据何时到达硬件。
【参考方案1】:
好的,我可以肯定地说这个问题在经过大量测试后得到了解决,这个解决方案在我的网络服务器上运行了几个月,在任何情况下都没有任何问题。答案很有帮助,但最终的解决方案如下:
try
socket.Shutdown(SocketShutdown.Both);
catch (SocketException ex)
// handle exception
finally
socket?.Close();
“诀窍”是与SocketShutdown.Both
断开连接,如果套接字仍然存在,则最后关闭它。我注意到有时(我真的不知道为什么)关闭方法会自动释放套接字,所以socket.Close()
抛出了 NullReferenceException,但有时不会。我在套接字对象引用后使用?
解决了这个问题,所以如果它为空,则不会调用Close()
方法。
它在 Windows 和 dotnet core 的 Linux (debian) 上运行良好。
为什么只有这个有效?不知道,但是有了这个,我没有失败的请求、丢包或不正确关闭的连接。刚刚好。
【讨论】:
以上是关于发送后如何正确关闭套接字(使用 IOCP)?的主要内容,如果未能解决你的问题,请参考以下文章