【问题标题】:how can a tcp socket read call never returntcp 套接字读取调用如何永远不会返回
【发布时间】:2013-02-27 12:58:07
【问题描述】:

客户报告了一个我无法理解的错误。基于 TCP 的客户端连接到从其接收数据的服务器,很少发送任何内容。通常一切正常,但一旦在蓝月亮上,就会发生这样的情况:

  • 服务器发送一些数据
  • 客户端接收数据
  • 客户端正在处理数据
  • ...同时服务器发送更多数据
  • 客户端完成处理
  • 客户端尝试从套接字读取数据
  • 客户端在处理后永远挂在第一个 read() 语句上
  • 服务器关闭连接
  • 客户端仍然挂起

这是建立 tcp 连接的方式(剥离所有日志、返回检查等)

ret = inet_pton(AF_INET, conn->address, &addr.sin_addr);
addr.sin_port        = htons(conn->port); /* Server port */
addr.sin_family      = AF_INET;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(sock, (struct sockaddr *) &addr, sizeof(addr));

这里是读取包装器:

int32_t _readn ( int fd, uint8_t *vptr, int32_t n )
{
  int32_t  nleft;
  int32_t  nread;
  uint8_t*     ptr;

  ptr = vptr;
  nleft = n;
  while (nleft > 0) {
    if ((nread = read (fd, ptr, nleft)) < 0) {
      if (errno == EINTR) {
        nread = 0;
      } else {
        return E_NETWORK_ERROR;
      }
    } else if ( nread == 0 ) {
      break;
    }
    nleft -= nread;
    ptr   += nread;
  }
  return  (n-nleft);
}

读取调用是否有可能永远阻塞,即使在连接关闭后也是如此?

我的包装器中是否有某种我没有注意到的棘手错误可能导致这种情况?我应该为连接时的套接字设置一些标志吗?

【问题讨论】:

  • 是否有理由不使用它非阻塞并在 fd 上放置 select 代替?
  • 不,没有。这样做可能是一个解决方案。我可以想到其他一些方法,这将是一个很好的解决方法。但我想知道问题的根源。

标签: c sockets tcp client-server buffer


【解决方案1】:

问题的根源是如果没有数据要读取,读取就会阻塞。例如。如果写入的字节数少于预期的 n 个字节。这称为阻塞读取。

如Jite所说,要发现是否有数据,使用select

最后,您的防火墙可能会断开实时连接。一些防火墙被配置为切断打开时间超过给定时间的连接,例如30分钟。然而,这可能不是你所拥有的。

【讨论】:

  • No data=block 不是源 - 它是结果。源头仍未被发现。
  • @DariuszWawer 你从哪里得到n?你怎么知道需要多少字节?
  • 应用层协议提供该信息。我首先读取了 3 个 common-for-all-messages 标头字节,据此我决定要读取多少数据。
  • @DariuszWawer,号码正确吗?另外,你怎么知道是否有三个头字节?是否有特殊的“流结束”消息,或者它只是停止发送?心理调试告诉我,服务器可能在发送更多数据之前正在等待回复消息,并且不知何故,您的应用程序级协议已不同步,因此客户端正在等待未到来的更多数据。所以我建议查看实际数据——它实际上是一个完整的信息吗?预期长度是否正确?
  • 如果没有有意义的消息,每两秒发送一个heartbit。我用文档仔细检查了我期望的数据量。没关系。早些时候,相同长度的相同消息被正确接收。
【解决方案2】:

我最终使用了一个基于选择的函数来检查数据是否可用。

虽然神秘数据丢失背后的原因仍然未知(未确认服务器错误),但这似乎可以解决问题:

int32_t isReadDataAvailableOnSocket ( int sock, uint32_t waitTimeUs )
{
  fd_set fds;
  int16_t ret = 0;
  struct timeval timeout;
  struct timeval* timeoutPtr = NULL;

  if (waitTimeUs>0) {
    timeout.tv_sec = waitTimeUs / 1000000;
    timeout.tv_usec = waitTimeUs % 1000000;
    timeoutPtr = &timeout;
  }

  FD_ZERO ( &fds );
  FD_SET ( sock, &fds );

  ret = select ( sock+1, &fds, NULL, NULL, timeoutPtr );
  if (ret == -1) {
    WARN("select failed for udp socket=[%d]", sock);
    return E_NETWORK_ERROR;
  }
  if ( ! FD_ISSET(sock, &fds) )
  {
    return E_NO_DATA;
  }
  else
  {
    return 0;
  }
}

【讨论】:

  • 网络丢失数据。 TCP 使用“一遍又一遍地喊它直到它到达”的方法来应对这种情况,但是如果网络非常不满意,这可能需要无限的时间才能成功……
  • @DonalFellows 这不是无限的。尝试将超时并最终失败。
  • 发送端将失败。接收方将永远等待。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-06
  • 2017-07-26
相关资源
最近更新 更多