【问题标题】:Blocking recv call hangs if server is down如果服务器关闭,则阻止 recv 调用挂起
【发布时间】:2011-01-15 18:31:45
【问题描述】:

另一个套接字问题。

在我的客户端代码中,我正在发送一些数据包并期待来自服务器端的一些响应:


发送()

recv()

在 send() 之后,服务器立即崩溃并重新启动。与此同时,recv() 正在等待。但是即使在服务器启动后,接收呼叫也会挂起。我添加了 SIGPIPE 信号处理,但它仍然无法识别套接字已损坏。

当我取消操作时,我从 recv() 收到错误,表明已发出中断。

谁能帮我纠正这个错误?

这是在 Solaris 机器上运行的共享库中。

【问题讨论】:

    标签: c sockets solaris


    【解决方案1】:

    在 Solaris 上使 recv() 调用 nono-blockign 的另一种方法是使用 fcntl() 将套接字描述符设置为非阻塞:

    fcntl(sockDesc, F_SETFL, O_NONBLOCK);

    这可以与 select() 一起使用,以保护您的 recv() 免受错误的 select() 返回值的影响(如果 select() 返回正数并且套接字上没有数据)。

    【讨论】:

      【解决方案2】:

      正如其他人所提到的,您可以使用 select() 设置套接字变得可读的时间限制。

      默认情况下,当套接字接收缓冲区中有一个或多个字节可用时,套接字将变为可读。我说“默认”是因为这个数量可以通过使用 SO_RCVLOWAT 套接字选项设置套接字接收缓冲区“低水位标记”来调整。

      下面是一个函数,您可以使用它来确定套接字是否准备好在指定的时间限制内被读取。如果套接字有可读取的数据,它将返回 1。否则超时返回0。

      代码基于 Unix Network Programming (www.unpbook.com) 一书中的示例,该示例可以为您提供更多信息。

      /* Wait for "timeout" seconds for the socket to become readable */
      readable_timeout(int sock, int timeout)
      {
          struct timeval tv;
          fd_set         rset;
          int            isready;
      
          FD_ZERO(&rset);
          FD_SET(sock, &rset);
      
          tv.tv_sec  = timeout;
          tv.tv_usec = 0;
      
       again:
          isready = select(sock+1, &rset, NULL, NULL, &tv);
          if (isready < 0) {
              if (errno == EINTR) goto again;
              perror("select"); _exit(1);
          }
      
          return isready;
      }
      

      像这样使用它:

      if (readable_timeout(sock, 5/*timeout*/)) {
          recv(sock, ...)
      

      您提到在客户端处理 SIGPIPE,这是一个单独的问题。如果您得到这意味着您的客户端正在写入套接字,即使在收到来自服务器的 RST 之后也是如此。这是与对recv() 的阻塞调用问题不同的问题。

      可能出现的情况是服务器崩溃并重新启动,失去其 TCP 状态。您的客户端将数据发送到服务器,该服务器发回 RST,因为它不再具有连接状态。您的客户端忽略 RST 并尝试发送更多数据,这是第二次 send() 导致您的程序接收 SIGPIPE 信号。

      调用 recv() 时遇到了什么错误?

      【讨论】:

      • 我没有收到任何错误,recv() 只是阻塞。如果 select() 对我的应用程序可行,我会尝试。谢谢。
      • 我测试过,发现 select() 是唯一的选项,它在我的测试场景下运行良好。
      【解决方案3】:

      问题是连接从未真正关闭。 (没有发送 FIN 包等,另一端就走了。)

      您要做的是设置套接字上的接收超时,使用setsockopt(3)SO_RCVTIMEO 作为选项名称。

      【讨论】:

      • SO_RCVTIMEO 在 Solaris 中不受支持 :( 还有其他方式吗?
      【解决方案4】:

      您可能应该设置一个超时延迟来处理这种情况。它可以通过使用 setsockopt 并在您的套接字上设置 SO_RECVTIMEO 标志轻松完成:

        struct timeval tv;
        tv.tv_sec = 30;
        tv.tv_usec = 0;
        if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof tv))
        {
          perror("setsockopt");
          return -1;
        }
      

      另一种可能性是使用非阻塞套接字并使用 poll(2) 或 select(2) 管理读/写内容。你应该看看Beej's Guide to Network Programming

      【讨论】:

      • 但无论如何都要注意 SO_RCVTIMEO,因为它似乎并非在所有 unix 系统上都得到普遍支持。在这种情况下,setsockopt 会失败,您将不得不使用 select 或 poll。
      • 另一个要尝试的是 SO_KEEPALIVE,当然如果这是 TCP :)
      • 我知道这些选项只是考虑是否有其他方法。这意味着除了 SO_CVTIMEO 之外别无他法。
      • SO_RCVTIMEO 在 Solaris 中不受支持 :( 还有其他方式吗?
      • 好吧,非阻塞套接字和 select(2) 来处理超时。查看此页面,它将向您解释如何执行此操作:rhoden.id.au/doc/sockets2.html
      猜你喜欢
      • 1970-01-01
      • 2011-12-14
      • 1970-01-01
      • 2012-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多