关闭尚未完成 AcceptEx 的套接字 - 如果以及如何?

Closing sockets that haven't completed AcceptEx - if and how?

我知道如果我发出 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

If a receive buffer is provided, the overlapped operation will not complete until a connection is accepted and data is read. Use the getsockopt function with the SO_CONNECT_TIME option to check whether a connection has been accepted.

If the socket is not connected, the getsockopt returns 0xFFFFFFFF. Applications that check whether the overlapped operation has completed, in combination with the SO_CONNECT_TIME option, can determine that a connection has been accepted but no data has been received.

It is recommended such connections be terminated by closing the accepted socket, which forces the AcceptEx function call to complete with an error.

现在,这似乎表明我应该强行关闭套接字。然而,我的书 "Network Programming for Microsoft Windows - Second Edition" 陈述了类似的事实,但接着说

As a word of warning, applications should not under any circumstances close a client socket handle used in an AcceptEx call that has not been accepted because it can lead to memory leaks. For performance reasons, the kernel-mode structures associated with an AcceptEx call will not be cleaned up when the unconnected client handle is closed until a new client connection is established or until the listening socket is closed.

所以我现在应该关闭它??我很困惑。

两个问题:

1) 如果套接字没有完全完成 AcceptEx,我从 getsockopt 返回 0xFFFFFFFF。这使它成为强行关闭的候选者。但是我怎么知道它处于这种状态多久了?我无法添加自己的时序逻辑,因为我不知道什么时候进行了接受,因为我的完成端口例程尚未完成!

2) 当我确定是否需要关闭套接字时,我该怎么做? closesocket() 够吗?

好的,我在检查 Len Holgate 在 this link 上发布的代码后自己发现了这一点。

基本上我们需要存储所有 SOCKET 对象(我们创建的对象传递给我们如上所示获得的 AcceptEx 函数指针)以便遍历它们。 Programming for 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

注意使用 auto-reset 事件,而不是 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 从我们保存它的任何存储中删除。

1) If a socket has not fully completed AcceptEx, I get back 0xFFFFFFFF from getsockopt. This makes it a candidate for forcibly closing.

没有。这是错误的。如果你得到 0xFFFFFFFF 这意味着客户端没有连接到套接字。它仍在等待连接。我们需要停止这个操作,只有当我们决定完全停止监听端口时。否则我们不需要关闭这个套接字或取消这个 i/o

But how am I supposed to know how long it has been sitting in this state? I can't add my own timing logic because I don't know when the accept was made because my completion port routine hasn't completed!

getsockoptSO_CONNECT_TIME 和 returns 套接字已连接的秒数:

所以如果这个数字是 0xFFFFFFFF - AcceptEx 仍然等待连接并且不能是 closed/canceled。否则(我们得到另一个值)——这是客户端已经连接的秒数。看example of code

所以你可以定期检查套接字 - 如果你从 getsockopt( s, SOL_SOCKET, SO_CONNECT_TIME, (char *)&seconds, (PINT)&bytes) 开始 N (!=-1) 秒 - 这意味着客户端已经 N 秒连接到您的套接字但尚未发送任何数据。正是这个(当 N 变得太大时)使它成为强制关闭的候选者。但不是 -1 (0xFFFFFFFF) 值。

So I'm not supposed to close it now?? I'm confused.

你理解错了。两段文字之间没有矛盾:

... will not be cleaned up when the unconnected client handle is closed ...

注意这里说的关闭句柄使用了AcceptEx,而它仍然处于未连接状态。

It is recommended such (connected but no data has been received) connections be terminated by closing the accepted socket

所以这里说关闭已经连接套接字。

所以你确实需要关闭已经连接 的套接字,其中太长时间没有收到数据。套接字连接多长时间(以秒为单位) - 你通过 SO_CONNECT_TIME


但是根据我的选择,在 AcceptEx 中使用接收缓冲区不是一个好主意。客户端连接后更好的显式调用 WSARecv。是的,这是对内核的额外调用。但从另一方面来说,如果您在 AcceptEx 中使用接收缓冲区 - 您需要在每个侦听套接字上定期调用 getsockopt(这是对内核的调用!)。因此,改为对套接字进行一次调用,其中 AcceptEx 已完成 - 您将需要每隔 T 调用 getsockopt N时间段。当 AcceptEx 在客户端连接后完成时 - 您可以自己节省连接时间并定期检查自己的时间。但为此你不需要调用内核,这样会快得多。时间你可以通过 GetTickCount64

2) When I figure out if I need to close the socket, how do I do it? Is closesocket() enough?

是的 closesocket() 是需要而且足够了