【问题标题】:C++ tcp socket connection retry methodC++ tcp socket连接重试方法
【发布时间】:2015-07-07 02:18:23
【问题描述】:

在开发了一个可以交换一些数据的示例客户端服务器应用程序之后,我正在尝试在其中实现重试机制。目前我的应用程序遵循以下协议:

  1. 客户端连接到服务器(非阻塞模式),超时时间为 3 秒,重复次数为 2。
  2. 开始从客户端发送固定长度的数据。 Send 有一些错误检查它是否正在发送完整的数据。
  3. 从服务器接收响应(超时:3 秒)并验证。如果收到不正确的响应,则重新发送数据并等待响应。如果失败,重复此操作两次。

对于上面的实现代码部分如下所示:

  1. connect() 和 select() 用于打开连接
  2. select() 和 send() 用于数据发送
  3. select() 和 recv() 用于数据接收

现在我正在根据套接字函数的返回类型进行重试,如果 send() 或 recv() 失败,我将重试相同的方法。但不记得 connect()。

我通过在数据传输之间重新启动服务器来测试这个东西,结果客户端无法与服务器通信并在重试几次后退出,我相信这是因为没有 connect() 调用重试方法。

有什么建议吗?

接收套接字数据的示例代码

bool CTCPCommunication::ReceiveSocketData(char* pchBuff, int iBuffLen)
{
  bool bReturn = true;

  //check whether the socket is ready to receive
  fd_set stRead;
  FD_ZERO(&stRead);
  FD_SET(m_hSocket, &stRead);
  int iRet = select(0, &stRead, NULL, NULL, &m_stTimeout);

  //if socket is not ready this line will be hit after 3 sec timeout and go to the end
  //if it is ready control will go inside the read loop and reads data until data ends or
  //socket error is getting triggered continuously for more than 3 secs.
  if ((iRet > 0) && (FD_ISSET(m_hSocket, &stRead)))
  {
    DWORD dwStartTime = GetTickCount();
    DWORD dwCurrentTime = 0;

    while ((iBuffLen-1) > 0)
    {
      int iRcvLen = recv(m_hSocket, pchBuff, iBuffLen-1, 0);
      dwCurrentTime = GetTickCount();

      //receive failed due to socket error
      if (iRcvLen == SOCKET_ERROR)
      {
        if((dwCurrentTime - dwStartTime) >= SOCK_TIMEOUT_SECONDS * 1000)
        {
          WRITELOG("Call to socket API 'recv' failed after 3 secs continuous retries, error: %d", WSAGetLastError());
          bReturn = false;
          break;
        }
      }
      //connection closed by remote host
      else if (iRcvLen == 0)
      {
        WRITELOG("recv() returned zero - time to do something: %d", WSAGetLastError());
        break;
      }

      pchBuff  += iRcvLen;
      iBuffLen -= iRcvLen;
    }
  }
  else
  {
    WRITELOG("Call to API 'select' failed inside 'ReceiveSocketData', error: %d", WSAGetLastError());
    bReturn = false;
  }

  return bReturn;
}

【问题讨论】:

  • 您需要检查 fatal 错误,如果有任何错误,您需要close 连接并创建一个新连接。您不能继续在死连接上发送。 (你也不能 select 连接死机——没有什么可等待的。)
  • @DavidSchwartz:你能告诉我更多关于致命错误的信息吗?你在说哪个 SOCKET_ERROR 代码?我不清楚,何时使用 send/recv 重试以及何时使用 connect() 重试,即从头开始构建套接字。
  • 您应该重试sendrecv 的唯一情况是它是否被信号中断或被阻塞。所有其他错误对连接都是致命的。
  • 对于非阻塞套接字,是否可以调用 select 并检查读/写能力,然后启动 recv/send 并检查 SOCKET_ERROR。如果找到,只需重新连接新的套接字。
  • 不,有两个原因。首先,您可能会被信号打断。其次,点击select 并不能保证后续操作不会因EWOULDBLOCK 而失败。举一个明显的例子,假设你得到一个写入命中,然后尝试写入 64MB。

标签: c++ sockets tcp


【解决方案1】:

目前我的应用程序遵循以下协议:

  1. 客户端连接到服务器(非阻塞模式),超时 3 秒,重试 2 次。

您无法重试连接。您必须关闭连接尝试失败的套接字,创建一个新的套接字,然后再次调用connect()

  1. 开始从客户端发送固定长度的数据。 Send 有一些错误检查它是否发送完整的数据。

这在阻塞模式下不是必需的:POSIX 标准保证阻塞模式 send() 将发送所有数据,或者失败并出现错误。

  1. 从服务器接收响应(超时:3 秒)并验证。如果收到不正确的响应,则重新发送数据并等待响应。如果失败,重复两次。

这是个坏主意。很可能所有数据都将到达,包括所有重试,或者没有。如果您使用这种技术,您需要确保您的交易是幂等的。您还需要密切注意实际的超时期限。一般来说,3 秒是不够的。起点是预期服务时间的两倍。

对于上面的实现代码部分如下所示:

   connect() and select() for opening connection
   select() and send() for data send
   select() and recv() for data receiving

您不需要 select() 处于阻塞模式。您可以使用 SO_RCVTIMEO. 设置读取超时

现在我正在根据套接字函数的返回类型进行重试,如果 send() 或 recv() 失败,我将重试相同的方法。但不记得 connect()。

我通过在数据传输之间重新启动服务器来测试这个东西,结果客户端无法与服务器通信并在重试几次后退出,我相信这是因为没有 connect() 调用重试方法。

如果这是真的,你会得到一个这样说的错误。

【讨论】:

  • 嗯,首先我在 Windows 上,我发现非阻塞模式适合我的目的,因为我需要一些超时和重试方法。是的,到目前为止,在正常测试期间,我在发送/接收方面没有遇到任何失败,因此重试的非阻塞模式并没有多大作用。
  • “多玩”是什么意思?
  • 我的意思是,到目前为止,我在第一次尝试时没有收到任何失败的发送/接收,然后继续重试。为了更好地理解,我发布了我的接收方法的 sn-p。
  • 您应该将select() 放入循环中,并根据重试次数调整超时,而不是自旋循环并手动管理超时。当你得到SOCKET_ERROR.时,你还需要确定实际的错误。这可能很有启发性。
  • 感谢您的评论,我可以看到您的方法可能非常有启发性,因为它将消除手动超时检查。但我现在更关心的是如何处理重试连接。
猜你喜欢
  • 1970-01-01
  • 2016-09-15
  • 2013-01-23
  • 1970-01-01
  • 2012-10-16
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
  • 1970-01-01
相关资源
最近更新 更多