【问题标题】:select on socket slow in Linux在 Linux 中选择慢速套接字
【发布时间】:2016-09-01 11:32:35
【问题描述】:

我遇到了一个奇怪的问题,即在 Linux 中的套接字上 select 花费了意想不到的时间。

  • 服务器一直在接收数据,接收套接字缓冲区大小为65536。
  • 一个客户端一直在发送数据,发送套接字缓冲区大小为4096。

一般来说,数据传输非常快。但是:客户端中的一个选择来测试写入是否不会阻塞需要很长时间(发送数据而不调用select之前:0.5s,发送相同的数据在实际发送数据之前调用select:5s)。该问题特定于缓冲区大小。如果我将客户端中的发送缓冲区增加到 4*4096,问题就会消失。

现在我想知道为什么特定缓冲区大小的选择需要这么长时间。示例代码在这里:http://pastebin.com/PqisLnLU

相同的代码在 Windows 甚至 Linux 的 Windows 子系统上运行而没有这些奇怪的行为。

谢谢!

【问题讨论】:

  • “客户端中的一个选择来测试写入是否不会阻塞需要很长时间(没有选择:0.5s,选择 5s)” - 你怎么能有一个没有选择的选择... ?
  • 谢谢,已解决问题。

标签: c linux sockets


【解决方案1】:

您看到了Nagle's algorithm 的效果,它用于以延迟为代价提高 TCP 吞吐量。

写入相对较小,并且会延迟,以防在不久的将来写入更多数据,然后可以将这些数据捆绑在一个 IP 数据包中。当您在发送之前使用select 时,您不会发送更多(因为发送缓冲区仍然是满的),因此在发送数据包之前会有很大的延迟(并且缓冲区被清空)。相反,当您不使用 select 时,缓冲区已满,因此当您 send 更多时,它会立即通过网络堆栈分流。

(我猜你是在同一台机器上运行你的客户端和服务器程序,所以它们之间的“网络”连接实际上是环回接口,它具有相当高的 MTU;否则,Nagle 的算法可能不会在这里成为一个问题)。

当您充分增加缓冲区大小时,在填充缓冲区期间的某个时间点会达到合适的 IP 数据包大小,并且数据会立即通过网络推送(并在确认接收时从发送缓冲区中清除) - 所以没有延迟。

尝试禁用 Nagle 算法(在客户端):

#include <netinet/tcp.h>

...

value = 1;
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(int)))
{
    printf("\n Error : SetSockOpt TCP_NODELAY Failed \n");
}

您会看到使用select 的变体与没有select 操作的变体一样快。

【讨论】:

  • 是的,这解决了这个问题,感谢您指出这一点!但是为什么在 Windows 上选择这么快(我想这很难回答,因为我们都没有 Windows 的源代码)?在两个系统上 TCP_NODELAY 都是 0。
  • @RobertK 对,我真的不知道;这是一个不同的 TCP/IP 实现。 Windows 可能会在发送缓冲区满后立即推出数据包,这是非常合理的行为。
  • @RobertK 我应该补充一点,linux 实际上设置的缓冲区大小是请求大小的两倍。如果将缓冲区大小设置为 2048(然后加倍),您会得到有趣的行为,即使用或不使用 select 总是(同样)慢。在这种情况下,我相信这是因为只有在收到对等方的确认包时才能清除发送缓冲区;您正在强制对每个数据包进行往返延迟。
  • 也许答案在support.microsoft.com/en-us/kb/214397 "如果堆栈合并了一个大于最大传输单元 (MTU) 的数据缓冲区,则立即发送一个完整大小的数据包,而无需等待来自远程主机。在以太网上,TCP/IP 的 MTU 为 1460 字节。"
  • @RobertK 确实如此,尽管这句话本质上只是对 Nagle 算法的一部分的描述。也许关键是当我在 linux 上运行程序时,我是在同一台主机上运行的,所以数据包通过环回接口 (lo),根据ifconfig 的 MTU 为 65536。在 Windows 上可能是被视为与以太网相同,或者至少具有类似的小 MTU。如果您还没有这样做,您可能想尝试在不同的主机上运行客户端和服务器程序。
【解决方案2】:

我想知道为什么特定缓冲区大小的选择需要这么长时间

因为客户端的发送缓冲区太小了。 4096 字节小于我所知道的任何操作系统的默认值。十多年前,Linux 上的默认值约为 52k。除非您的发送方的套接字发送缓冲区的大小与接收方的套接字接收缓冲区的大小相当,否则您不可能“填充管道”。

我还会质疑您为什么在客户端中使用非阻塞 I/O 和 select()。除非您连接到多个服务器,否则真的没有意义。只需使用专用线程并让它阻塞。

【讨论】:

  • 这没有回答问题(这是为什么需要这么长时间?)
  • "除非您的发送方的套接字发送缓冲区的大小与接收方的套接字接收缓冲区的大小相当,否则您不可能'填充管道'"我不知道什么误解会导致您建议这样做是这样的。
猜你喜欢
  • 2014-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 2019-11-24
相关资源
最近更新 更多