发送后如何正确关闭套接字(使用 IOCP)?
How close a socket (with IOCP) properly after sending?
当我在发送请求的数据后需要关闭连接时,我遇到了使用 IOCP(重叠 IO 模式)的 Winsock2 问题。
我发现如果我发送一些数据并在发送后立即关闭套接字,那么数据将不会(或部分)发送,因为在关闭之前没有时间发送数据包。
我还发现有多种断开连接的方法,比如WSA_SendDisconnect()
,我现在就用。
现在的实现是这样的:
发送数据
设置一个标志表示发送后打算关闭连接
当关于发送事件的 IOCP 事件发生时如果设置了标志并且bytecount > 0
,那么我再次发送一个清空缓冲区,只是为了确保所有数据都已发送
当关于发送事件的 IOCP 事件发生时如果设置了标志并且bytecount == 0
然后我清除标志 和 调用
WSA_SendDisconnect()
缓冲区为空,发送断开消息
- 当关于发送事件的 IOCP 事件发生时 没有设置标志和
bytecount == 0
然后我调用 closesocket()
并销毁上下文。
通过这个程序,我几乎可以确定套接字只有在所有数据发送完毕后才会断开连接,实际上它工作得很好,但是当我在一些非常罕见的情况下对程序进行 10-15 次压力测试时1000000 次测试出了点问题。 (很难确定确切的问题,该程序用作 Web 服务器,我正在使用 apachebench 和 siege 对其进行压力测试,测试后我得到一个摘要,有时我会看到 10-15请求失败。)
我很确定失败是因为套接字无法在关闭之前发送整个数据包,所以我在 socketclose()
之前放置了一点延迟,从那以后我得到了零失败请求,但是当然,这不是解决方案,只是一种过滤掉可能导致问题的方法。
我实现的方法显然缺少一种正确的方法来检测套接字是否在关闭之前发送了所有内容,即使我仅在最后一个虚拟写入完成后才开始关闭。
只有在所有发送缓冲区实际写入网络后,才关闭套接字的最佳解决方案是什么?
Ps:我试过 NoDelay 并设置 LingerState,但没有帮助。
好的,我可以肯定地说这个问题在经过大量测试后已经解决了,这个解决方案在我的网络服务器上运行了几个月,在任何情况下都没有任何问题。答案很有帮助,但最终解决方案如下:
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch (SocketException ex)
{
// handle exception
}
finally
{
socket?.Close();
}
“技巧”是启动与 SocketShutdown.Both
的断开连接,如果套接字仍然存在,最后将其关闭。我注意到有时(我真的不知道为什么)关闭方法会自动处理套接字,因此 socket.Close()
抛出 NullReferenceException,但有时不会。我在套接字对象引用后使用 ?
解决了这个问题,因此如果它为 null,则不会调用 Close()
方法。
它在 Windows 和 Linux (debian) 的 dotnet core 上运行良好。
为什么只有这个有效?不知道,但有了这个,我没有遇到失败的请求、数据包丢失或不正确关闭的连接。刚刚好。
当我在发送请求的数据后需要关闭连接时,我遇到了使用 IOCP(重叠 IO 模式)的 Winsock2 问题。
我发现如果我发送一些数据并在发送后立即关闭套接字,那么数据将不会(或部分)发送,因为在关闭之前没有时间发送数据包。
我还发现有多种断开连接的方法,比如WSA_SendDisconnect()
,我现在就用。
现在的实现是这样的:
发送数据
设置一个标志表示发送后打算关闭连接
当关于发送事件的 IOCP 事件发生时如果设置了标志并且
bytecount > 0
,那么我再次发送一个清空缓冲区,只是为了确保所有数据都已发送当关于发送事件的 IOCP 事件发生时如果设置了标志并且
bytecount == 0
然后我清除标志 和 调用WSA_SendDisconnect()
缓冲区为空,发送断开消息- 当关于发送事件的 IOCP 事件发生时 没有设置标志和
bytecount == 0
然后我调用closesocket()
并销毁上下文。
通过这个程序,我几乎可以确定套接字只有在所有数据发送完毕后才会断开连接,实际上它工作得很好,但是当我在一些非常罕见的情况下对程序进行 10-15 次压力测试时1000000 次测试出了点问题。 (很难确定确切的问题,该程序用作 Web 服务器,我正在使用 apachebench 和 siege 对其进行压力测试,测试后我得到一个摘要,有时我会看到 10-15请求失败。)
我很确定失败是因为套接字无法在关闭之前发送整个数据包,所以我在 socketclose()
之前放置了一点延迟,从那以后我得到了零失败请求,但是当然,这不是解决方案,只是一种过滤掉可能导致问题的方法。
我实现的方法显然缺少一种正确的方法来检测套接字是否在关闭之前发送了所有内容,即使我仅在最后一个虚拟写入完成后才开始关闭。
只有在所有发送缓冲区实际写入网络后,才关闭套接字的最佳解决方案是什么?
Ps:我试过 NoDelay 并设置 LingerState,但没有帮助。
好的,我可以肯定地说这个问题在经过大量测试后已经解决了,这个解决方案在我的网络服务器上运行了几个月,在任何情况下都没有任何问题。答案很有帮助,但最终解决方案如下:
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch (SocketException ex)
{
// handle exception
}
finally
{
socket?.Close();
}
“技巧”是启动与 SocketShutdown.Both
的断开连接,如果套接字仍然存在,最后将其关闭。我注意到有时(我真的不知道为什么)关闭方法会自动处理套接字,因此 socket.Close()
抛出 NullReferenceException,但有时不会。我在套接字对象引用后使用 ?
解决了这个问题,因此如果它为 null,则不会调用 Close()
方法。
它在 Windows 和 Linux (debian) 的 dotnet core 上运行良好。
为什么只有这个有效?不知道,但有了这个,我没有遇到失败的请求、数据包丢失或不正确关闭的连接。刚刚好。