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

时间:2018-01-18 17:00:16

标签: c++ c sockets winapi client-server

我使用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-accept循环:

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()进程返回select-accept循环后等待另一个连接。

我的问题是,有时客户端收不到足够的数据并获取“远程主机已关闭连接”的信息。 经过调试和一些研究后,我在MSDN上找到了这个说明:

  

使用带有SD_SEND或SD_BOTH的closesocket或shutdown功能会导致在控制通道上发送RELEASE信号。由于ATM使用单独的信号和数据信道,RELEASE信号有可能在最后一个数据到达目的地之前到达远端,导致数据丢失。一种可能的解决方案是在发送的最后一个数据和ATM套接字的closesocket或shutdown函数调用之间编写足够的延迟。

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

但这根本不能让我满意。我想删除此sleep()并确保在发送后完全刷新缓冲区,并且我可以安全地shutdown()closesocket()

我试图使主套接字无阻塞,希望send()阻止直到完成,但我错了 - 没有效果。

我还能做些什么来让它在没有sleep()的情况下发挥作用吗?

2 个答案:

答案 0 :(得分:1)

常见的方法是,一个部分仅发出SD_SEND关闭,然后对等体将以0长度读取结束,并且能够关闭,因为通信信道上仍然没有任何内容。在我的另一个answer上可以找到一些稍微更详细的解释。

答案 1 :(得分:1)

我决定使用的解决方案:

1)仅在shutdown()之后使用SD_SEND而不是SD_BOTH致电send()

2)实现循环,等待直到非阻塞recv()返回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
    }
}