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

时间:2018-02-15 16:22:58

标签: sockets winsock winsock2 iocp

我理解如果我通过文档建议的函数指针发出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

  

如果提供了接收缓冲区,则重叠操作不会   完成,直到接受连接并读取数据。使用   getsockopt函数与SO_CONNECT_TIME选项一起检查a   连接已被接受。

     

如果套接字未连接,则getsockopt返回0xFFFFFFFF。   检查重叠操作是否有的应用程序   完成后,结合SO_CONNECT_TIME选项即可   确定已接受连接但没有数据   接收。

     

建议通过关闭来终止此类连接   接受套接字,强制AcceptEx函数调用完成   有错误。

现在,这似乎表明我应该强行关闭套接字。但是,我的书" Microsoft Windows网络编程 - 第二版"陈述类似的事实,但接着说

  

作为警告,在任何情况下都不应该提出申请   关闭在没有的AcceptEx调用中使用的客户端套接字句柄   被接受,因为它可能导致内存泄漏。为了表现   原因,与AcceptEx调用相关的内核模式结构   关闭未连接的客户端句柄时不会清除   直到建立新的客户端连接或直到收听   套接字已关闭。

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

两个问题:

1)如果套接字尚未完全完成AcceptEx,我会从getsockopt返回0xFFFFFFFF。这使它成为永久关闭的候选人。但我怎么知道这个州坐了多久呢?我无法添加自己的时序逻辑,因为我不知道接受的时间是因为我的完成端口例程还没有完成!

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

2 个答案:

答案 0 :(得分:1)

  

1)如果套接字尚未完全完成AcceptEx,我会回来   来自0xFFFFFFFF的{​​{1}}。这使它成为强行的候选人   闭。

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

  

但我怎么知道它坐了多久   州?我无法添加自己的时序逻辑,因为我不知道什么时候该   接受是因为我的完成端口例程还没有完成!

但是0xFFFFFFFF getsockopt并返回套接字已连接的秒数:

因此,如果此号码为SO_CONNECT_TIME - 0xFFFFFFFF仍在等待连接,则不得关闭/取消。否则(我们得到另一个值) - 这是客户端已经连接的秒数。看example of code

因此您可以定期检查套接字 - 如果您从AcceptEx获得 N !=-1)秒 - 这意味着客户端已经 N 秒已连接到您的套接字但尚未发送任何数据。确切地说(当 N 变得太大时)使其成为强行关闭的候选者。但不是getsockopt( s, SOL_SOCKET, SO_CONNECT_TIME, (char *)&seconds, (PINT)&bytes)值。

  

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

你理解错了。两篇文章之间没有矛盾:

  当未连接客户端句柄时,

...将不会被清除   关闭......

请注意,此处说明在处理未连接状态时关闭使用-1 (0xFFFFFFFF)的句柄。

  

建议这样( 已连接 但未收到任何数据)   通过关闭接受的套接字来终止连接

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

所以你真的需要关闭已经连接的套接字,这里太长没有收到任何数据。套接字连接多久(以秒为单位) - 您通过AcceptEx

获得

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

说出来
  

2)当我弄清楚是否需要关闭套接字时,我该怎么做?是   GetTickCount64够了吗?

closesocket()需要且足够

答案 1 :(得分:0)

好的,我在查看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了解为何需要这样做。

最后一件事是从SOCKET调用完成时保留的任何存储中移除AcceptEx