【问题标题】:shutdown() and closesocket() shortly after send() on SOCKET在 SOCKET 上的 send() 之后不久 shutdown() 和 closesocket()
【发布时间】:2018-06-27 20:10:46
【问题描述】:

我使用 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 */

主套接字上的选择接受循环:

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 或关闭函数会导致在控制通道上发送 RELEASE 信号。由于 ATM 使用单独的信号和数据通道,RELEASE 信号可能会在最后一个数据到达其目的地之前到达远端,从而导致该数据丢失。一种可能的解决方案是在最后发送的数据与 ATM 套接字的 closesocket 或关闭函数调用之间设置足够的延迟。

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

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

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

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

【问题讨论】:

  • 让客户端关闭连接?
  • 客户端是否发送确认数据已收到,然后关闭连接?或者,正如@Sid 所建议的那样;让客户端关闭连接。
  • DisconnectExTF_REUSE_SOCKET 标志完美运行。你可以用它来代替shutdown。在这种情况下,您也不能关闭但重用相同的套接字
  • @SidS,但是如果客户端保持连接试图执行拒绝服务怎么办? RbMm,谢谢你,我试试这个方法。
  • 这实际上在 Winsock 文档中有所介绍:Graceful Shutdown, Linger Options, and Socket Closure

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


【解决方案1】:

常见的方法是一个部分只发出一个 SD_SEND 关闭,然后对等端将以 0 长度读取结束,并且能够关闭,因为通信通道上仍然没有任何东西可以保留。可以在我的另一个 answer 上找到更详细的解释和一些参考资料。

【讨论】:

    【解决方案2】:

    我决定使用的解决方案:

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

    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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-13
      • 2014-08-17
      • 1970-01-01
      • 1970-01-01
      • 2011-02-04
      相关资源
      最近更新 更多