【问题标题】:tcp or udp, another comparisiontcp or udp,另一个比较
【发布时间】:2012-02-10 22:17:16
【问题描述】:

考虑一个每秒有数千个请求的缓存应用程序,其中客户端使用整数索引请求对象,服务器将对象发送回给它。 (仅限 Linux)

对于 UDP 协议来说,什么会更快:

  • 在一个数据包中发送多个回复?
  • 在一个数据包中发送一个回复?

(适用于双方,服务器或客户端)

缺点:

  • 每个数据包有多个回复:

    • 将需要许多 memcpy() 来组装具有多个回复的数据包。如果 get() 请求向客户端返回 16 字节对象,则在发出 sendto() 系统调用之前,一个 65535 字节的单个数据包必须 memcpy() 大约 4000 个对象。 memcpy() 开销很大
    • 一个 65000 字节的 UDP 包会产生大约 43 个 udp 包头 因为数据包将在 Fragmetns 中传输,因为默认 MTU 大小为 1500。 开销不大
  • 每个数据包单个回复:
    • 每个 get() 都会产生 28 字节的协议开销。对于 16 字节的对象,这将是昂贵的。每秒对象的吞吐量将很低,网络将被数据包饱和。例如,发送 4000 个对象将生成 4000 个 udp 数据包标头,比具有多个回复的 43 个标头要多得多。
    • 此外,过多的 sendto() 系统调用会增加 CPU 消耗,因为内核必须在每个系统调用上保存 CPU 寄存器。

似乎这两种方法都有其缺点,尚不清楚哪种解决方案会更好,但是,千兆以太网的时钟运行在 1GHz,而处理器是例如 3GHz,内存也是 总线比以太网接口的双绞线宽得多。所以多回复 每个数据包会是更好的选择,因为额外的 memcpy() 会减少网络流量,对吗?

现在,如果我们使用 TCP 而不是 UDP:

  • 每个数据包有多个回复:
    • 再次,将需要许多 memcpy() 来组装数据包,该数据包不应大于 65535,然后内核会将其分段以 MTU 大小的数据包发送它与这里有 UDP
  • 每个数据包单个回复:
    • 由于我们的 16 字节对象随机分布在服务器的内存中,我们必须为每个对象发出一个 write() 系统调用,系统调用的数量与 udp 的单个请求情况相同
    • 每个 get() 都会导致 52 字节的开销,因为 TCP 具有更大的标头 + 我们将不得不另外一个大约 40 字节的 ACK 数据包开销。 更大的网络流量

结论:

  1. 对于这个特定的应用程序(从客户端给定的索引向客户端发送简单的数据块)TCP 的性能不会比 UDP 更好。 + 考虑到 TCP 栈的实现比 UDP 复杂得多,在那里执行的指令更多。 使协议运行得更快的关键在于将 UDP 数据包组装到 MTU 中尽可能大
  2. 向系统添加另一个 NIC 的成本低于添加另一个 CPU。减少 memcpy() 的数量是有意义的,但发送一堆小的 UDP 数据包并为网络端每个 udp 标头的开销支付额外费用(+ 通过执行许多 sendto() 系统调用来增加一些开销我认为会低于 memcpy() 的开销)因为这种方式使用一个 CPU 可以通过多个 NIC 发送请求

非常感谢您的 cmets 和您在协议选择方面的经验。

注意: 让我们抛开可靠性问题,假设我们的网络没有错误,即 如果您正确连接它们,大多数情况下都是正确的,如果发生错误,那肯定不是常态

【问题讨论】:

  • 我的解决方法是将数据包保持在 MTU 下,并在每个数据包中批处理一定数量的结果以及识别信息,即 2 of 10 或其他方式以确保您没有丢失任何事物。请记住,UDP 允许丢失数据包并且不会自行重试,而 TCP 将尝试重新发送未确认的数据包。从您的可靠性声明来看,我认为您控制了网络并且没有通过任何第三方/互联网设备?
  • 即使在一个健壮的内部网络上,在 UDP 上可能没有严重的数据“损坏”问题,如果有多个路由或多个 NIC,数据包偶尔会在 UDP 中重新排序并不少见发送端或接收端。最终,您的协议/应用程序对丢包、损坏和重新排序是否稳健?
  • 即使网络没有错误(哈哈!),交换机、路由器、网卡、操作系统也会丢弃数据包。不需要那么大的 UDP 数据包突发来填满缓冲区并导致数据包丢失。
  • 好吧,如果发生丢包,客户端将在超时后重新发送。但既然这不应该发生,为什么要打扰。我在数据中心拥有的最好的服务器有 5 年的正常运行时间,并且只有大约 300 个数据包在其 /sbin/ifconfig 输出中出现错误
  • 我发现这篇文章multicorepacketprocessing.com/tag/udp 说 TCP 比 UDP 快,但这是不正确的,因为使用 UDP 您还可以发送大小为 15000 字节的数据包,因此系统调用的数量将是相同的。读起来很有趣

标签: c linux networking network-programming


【解决方案1】:

这里有几处说法不准确,稍作改动:

  1. 多个对象不需要多个 write() 系统调用,您可以使用 writev() 来满足这个确切的需要。 (适用于 UDP 和 TCP)

  2. 使用 UDP 发送 65K 字节缓冲区不会创建多个 UDP 标头,只会创建多个带有单个 UDP 标头的 IP 标头。此外,对于今天的 1Gbit NIC/s,如果这是用于专用网络,您可以使用 jambo 帧并增加 MTU(超过 1500)。

  3. 1234563流,它没有 UDP 的数据包边界。
  4. 你不能假设可靠性、顺序和负载,在我看来,这使得 UDP 更难管理。当服务器或客户端的流量开始过载时,TCP 将处理连接。使用 UDP,您必须检测此类情况并做出反应。

【讨论】:

  • sendmsg() 可能比 writev() 更合适,但也有一个支持 scatter-gather 的接口。
  • 感谢您指出这一点。当我不需要额外的......只是为了清楚起见时,我个人更喜欢更简单的调用。当需要控制信息或一些标志时,sendmsg 非常有用。或者当您想在每次调用中指定地址时。但即使使用 UDP,如果每次发送都不需要控制和标志,使用一个连接然后多个 writev() 对我来说看起来更干净。
  • @EdwardH 谢谢爱德华,这是一个很大的帮助。我检查了 sendmsg() 它有 iovec 参数,所以我应该在不同的缓冲区中发送/接收整个数据包。但是我必须在一个系统调用中执行此操作,因为 UDP 不支持部分读取。是的,您的观点都是正确的,并且稍微改变一下图片,我将不得不考虑一下,但是 UDP 的数据包头仍然很低,而 UDP 的堆栈只有 2187 行代码。 UDP/TCP之战还在继续……
猜你喜欢
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 2012-03-07
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多