【问题标题】:Unexpected behaviour of SO_SNDTIMEO and SO_RCVTIMEOSO_SNDTIMEO 和 SO_RCVTIMEO 的意外行为
【发布时间】:2021-02-18 12:01:27
【问题描述】:

我正在尝试使用setsockoptSO_SNDTIMEOSO_RCVTIMEO 在Linux 上设置阻塞TCP 套接字的超时。但由于某种原因,我在等待recv 呼叫时得到了锁。

考虑最小的例子。为了便于阅读,我将其缩短了一点,完整的代码可在此gist 中找到。我创建了一个服务器套接字并使用setsockopt 设置超时。接下来,服务器与客户端交换消息。一段时间后,客户端中断数据交换并关闭套接字。但是服务器仍在等待阻塞recv。但是,我希望它在超时到期时中止recv

客户端和服务器使用的网络功能:

int set_timeout(int fd, int sec) {
  struct timeval timeout;
  timeout.tv_sec = sec;
  timeout.tv_usec = 0;

  if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
                 sizeof(timeout)) < 0) {
    return -1;
  }
  if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
                 sizeof(timeout)) < 0) {
    return -1;
  }

  return 0;
}

int blocking_recv(int sock, char *data, size_t size) {
  size_t remain_data = size;
  ssize_t recv_nb;
  int offset = 0;

  while ((remain_data > 0) &&
         ((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) {
    remain_data -= recv_nb;
    offset += recv_nb;
  }
  if (recv_nb == -1 || remain_data > 0) {
    return -1;
  }

  return 0;
}

int blocking_send(int sock, char *buf, size_t size) {
  size_t total = 0;
  int remain = size;
  int n;

  while (total < size) {
    n = send(sock, buf + total, remain, 0);
    if (n == -1) {
      break;
    }
    total += n;
    remain -= n;
  }

  return (total == size) ? 0 : -1;
}

服务器:

while (1) {
  struct sockaddr_in caddr;
  size_t len = sizeof(caddr);
  if ((client_fd = accept(server_fd, (struct sockaddr *)&caddr, (socklen_t *)&len)) < 0) {
    continue;
  }

  // Set 3 seconds timeout using SO_{SND,RCV}TIMEO
  if (set_timeout(client_fd, 3) != 0) {
    continue;
  }

  // Send and receive data in the loop. We assume that the client is stuck,
  // we'll break the connection using a timeout.
  while (1) {
    char in_data[data_size];
    char *out_data = "test\0";
    if (blocking_recv(client_fd, in_data, data_size) != 0)
      break;
    if (blocking_send(client_fd, out_data, data_size) != 0)
      break;
  }
}

客户:

int attempts_left = 5;
while (--attempts_left > 0) {
  char in_data[6];
  char *out_data = "test\0";
  if (blocking_send(fd, out_data, 6) != 0)
    break;
  if (blocking_recv(fd, in_data, 6) != 0)
    break;
  sleep(1);
}

close(fd);

可能出了什么问题?

不使用selectpoll和信号,是否可以通过这种方式实现recv的超时?

谢谢!

【问题讨论】:

  • ((recv_nb = recv(sock, data + offset, remain_data, 0)) &gt;= 0) 好吧,你永远不会退出,所以......你只是循环。
  • @KamilCuk 啊,你是对的,这很愚蠢。对不起这个噪音。如果您发布它,我可以接受您的回答。
  • FWIW,超时在这里不会起作用...当客户端关闭套接字时,服务器会收到通知(它基本上会创建一个 EOF 条件),而无需任何等待。

标签: c linux sockets


【解决方案1】:

您正在循环阅读:

while ((remain_data > 0) &&
         ((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) {
    remain_data -= recv_nb;
    offset += recv_nb;
  }

然而 readman read 返回:

 If no messages are available to be received and the peer has
   performed an orderly shutdown, recv() shall return 0.

所以你只是在无休止地循环。

您应该在程序中正确处理errno。当SNDTIMEO 东西超时时,你会得到:the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) ...

当信号中断时仍然可以返回EAGAIN,因此只需通常的select()poll() 3 秒可能会更简单。如果没有,无论如何都要自己测量时间并将超时设置为您想要的最大超时并测量已经过去了多少时间。沿着:

timeout = now + 3 seconds.
// check timeout yourself
while (timeout_not_expired(&timeout)) {
   // set timeout for the __next__ recv operation
   set_recv_timeout(timeout_to_expire(&timeout));
   ret = recv();
   if (ret == -1 && errno == EAGAIN) {
      continue;
   }
   if (ret == -1) {
        /* handle errror */
   }
   if (ret == 0) {
      /* closed */
   }
   /* actually received stuff */
}

使用strace 等工具和调试器来调试您的程序。

【讨论】:

    猜你喜欢
    • 2015-03-30
    • 1970-01-01
    • 2015-06-13
    • 2014-10-19
    • 2017-03-27
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多