关闭尚未完成 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

但是我怎么知道它已经在这 状态?我无法添加自己的时序逻辑,因为我不知道何时 接受是因为我的完成端口例程尚未完成!

但是 getsockoptSO_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() 发出的呼叫更多的客户想要连接? 您可以自行确定minmax 监听套接字。并从(min+max)/2 AcceptEx 呼叫开始收听。当AcceptEx 完成时 - 你减少收听次数。当它在每个新的AcceptEx 上变成<min 时,您创建新的套接字并在其上调用AcceptEx。当客户端断开连接时-您查找侦听计数-如果是<=max-您重用此套接字并再次调用AcceptEx。如果>max - 你关闭套接字。这是通常的池策略

以上是关于关闭尚未完成 AcceptEx 的套接字 - 如果以及如何?的主要内容,如果未能解决你的问题,请参考以下文章

IOCP、AcceptEx、重叠和 WSAEINVAL

AcceptEx()同步完成?

AcceptEx与完成端口结合实例

如何知道完成数据包是针对 WSASend() 还是 WSARecv() 还是 AcceptEx()?

TCP 套接字关闭但未被进程检测到

在接受后 TCP IOCP 不会收到