shutdown() 和 closesocket() 在 SOCKET 上的 send() 之后不久

shutdown() and closesocket() shortly after send() on SOCKET

我使用 WinAPI 为套接字操作编写了 C++ 服务。客户端和服务之间的通信很简单,在连接到服务客户端后发送一些数据,服务随后发送响应。

服务端的主套接字(ListenSocket)是这样创建的:

SOCKET ListenSocket = socket(pAddrResult->ai_family, pAddrResult->ai_socktype, pAddrResult->ai_protocol);
/* err checking */
/* Setting socket I/O mode to non-blocking */
u_long nonBlockingMode = 1;
opResult = ioctlsocket(ListenSocket, FIONBIO, &nonBlockingMode);
/* err checking */

Select-在主套接字上接受循环:

while(...)
{
    if(select(0, &readSet, NULL, NULL, &timeout) == 1)
    {
        ClientSocket = accept(ListenSocket, NULL, NULL); // SOCKET type, declared in global scope.
        /* err checking */
        break;
    }
}

中断循环后,服务开始从 ClientSocket 读取,然后服务在此套接字上发送响应:

 int iSendResult = send(ClientSocket, (const char*) messageBuffer.getData(), messageBuffer.getSize(), 0);
 /* err checking */
 /* FIX sleep(50) */
 int opResult = shutdown(ClientSocket, SD_BOTH);
 /* err checking */
 closesocket(ClientSocket);

closesocket() 处理 returns 到 select-接受循环并等待另一个连接后。

我的问题是,有时客户端接收到的数据不足并获取 "Connection was closed by remote host" 的信息。 经过调试和一些研究后,我在 MSDN 上找到了这条注释:

Using the closesocket or shutdown functions with SD_SEND or SD_BOTH results in a RELEASE signal being sent out on the control channel. Due to ATM's use of separate signal and data channels, it is possible that a RELEASE signal could reach the remote end before the last of the data reaches its destination, resulting in a loss of that data. One possible solutions is programming a sufficient delay between the last data sent and the closesocket or shutdown function calls for an ATM socket.

延迟你说微软? :( 好的,我已经实施了短延迟 (/* FIX sleep(50) */),问题消失了。

但这一点都不让我满意。我想删除这个 sleep() 并确保缓冲区在发送后被完全刷新并且我可以安全地 shutdown()closesocket().

我试图让主套接字成为非阻塞的,希望 send() 会阻塞直到完成,但我错了 - 没有效果。

如果没有 sleep(),我还能做些什么来让它工作?

常见的方式是一个部分只发出 SD_SEND 关闭,然后对等方将以 0 长度读取结束并且能够关闭,因为通信通道上仍然没有任何内容。在我的另一个answer

上可以找到更详细的解释和一些参考资料。

我决定使用的解决方案:

1) 在 send().

之后仅使用 SD_SEND 而不是 SD_BOTH 调用 shutdown()

2) 实现等待非阻塞的循环 recv() return 0 表示客户端正常关闭套接字。当套接字接收到数据、执行最大重试或发生其他错误时,方法退出并出现错误。

while (true)
{
    int result = recv(ClientSocket, &buff, sizeof(buff), 0);

    if (result == 0)
        return 0; // Client gracefully closed connection.       
    else if (result > 0)
        return -1; // Received unexpected data instead of socket closure    
    else
    {
        if (WSAGetLastError() == WSAEWOULDBLOCK)
        {
            if (retryNumber++ == MAX_RETRY_NUMBER)
                return -1; // Client didn't close socket within specified time

            sleep(10); // wait 10ms 
        }
        else
            return -1; // Unexpected error occured
    }
}