套接字发送调用被阻塞太久

3

我每10秒钟在套接字(阻塞)上发送2个应用程序数据字节,但是在上面的最后一次中,发送调用被阻塞了40多秒。

  • 2012-06-13 12:02:46.653417|INFO|发送前
  • 2012-06-13 12:02:46.653457|INFO|发送后(2)
  • 2012-06-13 12:02:57.566898|INFO|发送前
  • 2012-06-13 12:02:57.566962|INFO|发送后(2)
  • 2012-06-13 12:03:08.234060|INFO|发送前
  • 2012-06-13 12:03:08.234101|INFO|发送后(2)
  • **2012-06-13 12:03:19.010743|INFO|发送前
  • 2012-06-13 12:04:00.969162|INFO|发送后(2)**

机器(Linux)上TCP默认发送缓冲区大小为65536。

2个字节的数据是与服务器进行心跳,并且服务器期望客户端至少每15秒发送一次HB。

另外,我没有禁用 Nagle 算法。

问题是 - 发送调用是否可以阻塞40秒?而且只是偶尔发生,它会在运行了接近12小时之后发生。

我知道发送调用只是将数据复制到TCP发送缓冲区。

publish 每10秒钟被调用一次。不是逐渐变慢的发送调用。它突然发生一次,然后由于接收方套接字关闭,应用程序退出。

int publish(char* buff, int size) const {
      /* Adds the 0x0A to the end */
      buff[size]=_eolchar;

      if (_debugMode)
      {
          ACE_DEBUG((MY_INFO "before send\n"));
      }

      int ret = _socket.send((void*)buff, size+1);

      if (_debugMode)
      {
          ACE_DEBUG((MY_INFO "after send (%d)\n", ret));
          //std::cout << "after send " << ret << std::endl;
      }

      if (ret < 1)
      {
          ACE_DEBUG((MY_ERROR "Socket error, FH going down\n"));
          ACE_OS::sleep(1);
          abort();
      }
      return ret;
 }

这个问题具体是什么?有时候数据包可能会延迟... - peacemaker
请提供执行发送调用的代码。 - peacemaker
publish() 函数被调用的频率是多少?您测试过 ACE_DEBUG 调用需要多长时间吗?您是否注意到随着时间的推移会出现减速,还是只有一个 40 秒的块然后回到正常状态? - peacemaker
发布函数每10秒钟被调用一次。不是逐渐减慢发送调用。它突然发生一次,然后由于另一端的套接字关闭,应用程序退出。ACE_DEBUG仅用于打印跟踪信息,即使没有ACE_DEBUG,问题也会发生。 - Medicine
从这个信息来看,我只能说可能发送正在阻塞一段时间,这有时会发生取决于数据包丢失等情况。 - peacemaker
@Medicine 嘿!你也是ACE用户!!:-) 很高兴见到你..;-) - yves Baumes
2个回答

3
使用阻塞的send()调用时,从应用程序的角度来看,您可以将远程TCP缓冲区、网络和本地发送TCP缓冲区视为一个大缓冲区。也就是说,如果远程应用程序在从其TCP缓冲区中读取新字节时延迟,最终您的本地TCP缓冲区将变得(几乎)已满。如果您尝试发送一个新的有效负载以溢出TCP缓冲区,那么send()实现(内核系统调用)将不会将焦点返回到您的应用程序,直到TCP缓冲区获得足够的空间来存储该有效负载。

只有当远程应用程序没有读取足够的字节时才能达到这种状态。在测试环境中的典型情况是,远程应用程序在断点上暂停... :-) 这就是我们所说的慢消费者问题。如果您分享这种诊断结果,那么有多种方法可以解决这个问题:

1. 如果您控制远程应用程序,则使其足够快,以便本地应用程序不会被阻塞。
2. 如果您没有控制远程应用程序,则可能有多个答案:

a. 对于您自己的需求,阻塞最多可达40秒是可以的。
b. 如果不行,则需要使用非阻塞版本的send()系统调用。从这里开始,有多种可能的策略;我下面描述一种。(请稍等!:-))

您可以尝试使用动态数组来充当虚拟发送TCP FIFO,并在发送调用返回EWOULDBLOCK时增长。在这种情况下,您可能需要使用select()系统调用来检测远程应用程序是否跟上了步伐,并先将未看到的数据发送给它。这可能比您在这里拥有的简单的publish()函数更加棘手(虽然大多数网络应用程序中很常见)。您还必须知道,没有保证动态缓冲区增长到您不再具有任何空闲内存的点,然后您的本地应用程序可能会崩溃。实时网络应用程序中的典型策略是为缓冲区选择一个任意的最大大小,在达到该大小时关闭TCP连接,从而避免本地应用程序耗尽空闲内存。明智地选择该最大值,因为它取决于潜在的慢消费者连接数量。

另外需要注意的是,另一种典型的“慢消费者”场景是两个应用程序通过非常缓慢/长时间的广域网通信。我曾经遇到过这种情况,从日本到澳大利亚的广域网连接速度非常缓慢,远程应用程序还好,但网络实在太慢了。 - yves Baumes
这可能是因为Naggle算法没有关闭吗?我只发送了2个字节的数据(加上TCP头)。 (但是在12小时内,每10秒钟,数据以微秒时间发送到远程主机,因此我怀疑Naggle算法在这里不是问题)。远程主机的接收窗口大小为8192字节。 - Medicine
@Medicine 你能够查看远程应用程序端的日志文件吗? - yves Baumes
噢,@Medicine。你是在市场准入开发团队工作吗?我也是。 - yves Baumes
很酷啊,我刚从OMS转到了市场数据团队,因此会有系统编程方面的问题 ;) - Medicine
显示剩余6条评论

1
以下(还有其他我现在不想提及的)被认为是阻塞系统调用:
send、connect、recv、accept。
这意味着它们可以一直阻塞,直到指定的任务完成。 所以,是的,send 可以阻塞 40 秒甚至更长时间,具体取决于发送数据所需的时间;尽管我无法知道在您的特定情况下为什么会阻塞那么长时间。
如果您想避免此阻塞,我建议您了解异步套接字和 I/O。 它们可能会解决您问题的一部分。

谢谢。发送需要很长时间,这应该意味着底层网络连接不好,对吗?可能是严重的数据包丢失吧? - Medicine
当数据包丢失过多时,我会说(虽然不确定)在某个时刻会发生TCP RESET。然后你应该看到send()返回一个错误。 - yves Baumes
@Adel,不确定非阻塞套接字在这里有什么帮助,因为如果我不能在5秒钟内实际发送数据到远程主机,连接将被断开。我不是想获取在阻塞套接字中丢失的CPU周期。 - Medicine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接