关闭尚未完成 AcceptEx 的套接字 - 如果以及如何?
Posted
技术标签:
【中文标题】关闭尚未完成 AcceptEx 的套接字 - 如果以及如何?【英文标题】:Closing sockets that haven't completed AcceptEx - if and how? 【发布时间】:2018-02-15 16:22:58 【问题描述】:我了解,如果我按照文档建议通过函数指针发出 AcceptEx
调用,那么如果我指定接收器缓冲区大小,则调用将在发送一些数据之前不会完成:
if (!lpfnAcceptEx(sockListen,
sockAccept,
PerIoData->Buffer,
DATA_BUFSIZE - ((sizeof(SOCKADDR_IN) + 16) * 2), /* receive buffer size */
sizeof(SOCKADDR_IN) + 16,
sizeof(SOCKADDR_IN) + 16,
&dwBytes,
&(PerIoData->Overlapped)
))
DWORD dwLastError = GetLastError();
// Handle error
来自MSDN
如果提供了接收缓冲区,重叠操作将不会 完成,直到接受连接并读取数据。使用 带有 SO_CONNECT_TIME 选项的 getsockopt 函数检查是否存在 已接受连接。
如果套接字未连接,getsockopt 返回 0xFFFFFFFF。 检查重叠操作是否存在的应用程序 完成,结合 SO_CONNECT_TIME 选项,可以 确定连接已被接受但没有数据 收到了。
建议通过关闭 接受的套接字,它强制完成 AcceptEx 函数调用 有错误。
现在,这似乎表明我应该强制关闭套接字。但是,我的书“Microsoft Windows 的网络编程 - 第二版”陈述了类似的事实,但接着说
作为一个警告,应用程序不应在任何情况下 关闭在 AcceptEx 调用中使用的客户端套接字句柄,该句柄尚未 被接受,因为它可能导致内存泄漏。为了表现 原因,与 AcceptEx 调用关联的内核模式结构 未连接的客户端句柄关闭时不会被清理 直到建立新的客户端连接或直到监听 套接字已关闭。
所以我不现在应该关闭它??我很困惑。
两个问题:
1) 如果一个套接字还没有完全完成AcceptEx
,我会从getsockopt
得到0xFFFFFFFF。这使其成为强制关闭的候选者。但是我怎么知道它在这种状态下坐了多久呢?我无法添加自己的计时逻辑,因为我的完成端口例程尚未完成,所以我不知道何时进行了接受!
2) 当我确定是否需要关闭套接字时,我该怎么做? closesocket()
够了吗?
【问题讨论】:
【参考方案1】:1) 如果一个套接字还没有完全完成
AcceptEx
,我会回来0xFFFFFFFF
来自getsockopt
。这使它成为强制的候选人 关闭。
没有。这是错误的。如果你得到0xFFFFFFFF
这意味着客户端没有连接到套接字。它仍在等待连接。只有当我们决定完全停止监听端口时,我们才需要停止此操作。否则我们不需要关闭这个套接字或取消这个 i/o
但是我怎么知道它已经在这 状态?我无法添加自己的时序逻辑,因为我不知道何时 接受是因为我的完成端口例程尚未完成!
但是 getsockopt
和 SO_CONNECT_TIME
并返回套接字已连接的秒数:
所以如果这个号码是0xFFFFFFFF
- AcceptEx
仍然等待连接并且不能被关闭/取消。否则(我们得到另一个值) - 这是客户端已经连接的秒数。看example of code
因此您可以定期检查套接字 - 如果您从 getsockopt( s, SOL_SOCKET, SO_CONNECT_TIME, (char *)&seconds, (PINT)&bytes)
获得 N (!=-1
) 秒 - 这意味着客户端已经 N 秒连接到您的套接字但尚未发送任何数据。正是这(当 N 变得太大时)使其成为强制关闭的候选者。但不是-1 (0xFFFFFFFF)
值。
所以我现在不应该关闭它??我很困惑。
你理解错了。两段文字之间没有矛盾:
...未连接客户端句柄时不会被清理 关闭...
注意这里说的是关闭句柄使用AcceptEx
,而它仍处于未连接状态。
建议这样(已连接但未收到数据) 通过关闭接受的套接字来终止连接
所以这里说一下关闭已经连接套接字。
所以你真的需要关闭已经连接的套接字,因为太长时间没有收到数据。套接字连接了多长时间(以秒为单位)-您通过SO_CONNECT_TIME
但是,根据我的选择,在AcceptEx
中使用接收缓冲区不是一个好主意。客户端连接后更好的显式调用WSARecv
。是的,这是对内核的额外调用。但另一方面,如果您在AcceptEx
中使用接收缓冲区 - 您需要在每个侦听套接字上定期调用getsockopt
(这是对内核的调用!)。所以改为在套接字上调用一次,其中 AcceptEx
已完成 - 您将需要在每个 T 时间段内对 getsockopt
进行 N 次调用。当AcceptEx
在客户端连接后完成时 - 您可以自己节省连接时间并自己定期检查这个时间。但是为此,您不需要调用内核,这会更快。您可以通过GetTickCount64
发言的时间
2) 当我确定是否需要关闭套接字时,我该怎么做?是
closesocket()
够了吗?
是的,closesocket()
够用了
【讨论】:
是的,你对内核调用的数量提出了一个很好的观点。我想从学术的角度来理解这一点,现在我明白了,我可以考虑你的选择。显然,这是两者中更好的。所以谢谢!【参考方案2】:好的,我在检查 Len Holgate 在this link 发布的代码后自己发现了这一点。
基本上,我们需要存储所有SOCKET
对象(我们创建这些对象以传递给我们如上所示获得的AcceptEx
函数指针)以便遍历它们。 Microsoft Windows 编程 - 第二版 告诉我们,当我们希望接受的连接数多于未完成的 AcceptEx
调用时,是迭代挂起连接的好时机。我们可以通过以下方式判断是否是这种情况:
WSAEVENT NewEvent = CreateEvent(0, FALSE, TRUE, 0); // Auto reset event
WSAEventSelect(sockListen, NewEvent, FD_ACCEPT);
if (::WaitForSingleObject(NewEvent, INFINITE) == WAIT_OBJECT_0)
// Need to post an AcceptEx
请注意使用自动重置事件,而不是 WSACreateEvent()
创建的手动事件。现在,在发布AcceptEx
之后,我们可以遍历挂起的套接字,检查每个套接字的连接持续时间:
// Get the time for which this socket has been connected
::getsockopt(sock, SOL_SOCKET, SO_CONNECT_TIME, (char *)&nSeconds, &nBytes);
//
// If we decide the socket has been open for long enough, set SO_LINGER then close it
//
LINGER lingerStruct; // *
lingerStruct.l_onoff = 1; // Leave socket open...
lingerStruct.l_linger = 0; //...for 0 seconds after closesocket() is called
::setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *)&lingerStruct, sizeof(lingerStruct));
closesocket(sock);
(*) 请参阅 here 了解为什么需要这样做。
最后要做的是在AcceptEx
调用完成时从我们保存的任何存储中删除SOCKET
。
【讨论】:
你需要什么WSAEventSelect
和事件对象?这是一个糟糕的选择。不需要任何事件和选择。仅使用 iocp。
因为我怎么知道我有比AcceptEx()
发出的呼叫更多的客户想要连接?
您可以自行确定min
和max
监听套接字。并从(min+max)/2
AcceptEx
呼叫开始收听。当AcceptEx
完成时 - 你减少收听次数。当它在每个新的AcceptEx
上变成<min
时,您创建新的套接字并在其上调用AcceptEx
。当客户端断开连接时-您查找侦听计数-如果是<=max
-您重用此套接字并再次调用AcceptEx
。如果>max
- 你关闭套接字。这是通常的池策略以上是关于关闭尚未完成 AcceptEx 的套接字 - 如果以及如何?的主要内容,如果未能解决你的问题,请参考以下文章