在工作中,我们正在为一种 3D 引擎从 TCP 协议转移到 UDP 协议,因为当数据包丢失时我们会挂起 1 秒。由于我们的引擎不是为处理丢包或乱序到达而设计的,因此我们使用一个库 (Lidgren) 来执行可靠的 UDP。到目前为止,这按预期工作,但在延迟方面比 TCP 稍慢(20% 到 30%)。我的一个同事认为这是因为数据包的可靠性和排序涉及 UDP 的 CPU 开销,而它由 TCP 的网络适配器直接处理。那么,当使用 TCP 而不是 UDP 时,网络适配器是否在做一些特殊的工作来节省 CPU 的工作?
在硬件级别处理的 TCP 可靠性?
这是列出 NIC 硬件可以具有的 TCP 卸载功能类型的好地方。
卸载的最基本形式是校验和卸载。它在一定程度上提高了性能,但幅度不大。校验和是在硬件中很容易完成的事情。然而,校验和卸载在捕获 .pcap 时可能会显示奇怪的校验和,因此它对用户不是完全透明的。.pcap 问题的解决方案是关闭校验和卸载。
大型发送卸载之所以有效,是因为应用程序 write() 的缓冲区通常很大,例如 64 KB。让我们假设,TCP 窗口和拥塞控制允许立即发送 24 KB。操作系统构造了一个 24 KB 的虚拟 TCP 段,并且这个 24 KB 的虚拟 TCP 段仅通过操作系统堆栈一次。然后 NIC 将 24 KB 的大型虚拟段分解为较小的物理段。这里的想法是通过处理大型虚拟数据包而不是小型物理数据包来减少软件开销。每个数据包的处理开销然后适用于更大的数据块。因此,效果类似于运行大型 MTU。
大接收卸载是相反方向的大发送卸载,并与 NIC 的中断缓解功能一起使用。在这种情况下,这个想法是 NIC 不会为每个到达的数据包中断操作系统,而是实际上等待一段时间以等待更多数据包到达。如果进一步的数据包似乎是属于同一连接的连续数据包,它们将被组合成一个更大的虚拟段(假设是一个 32 KB 的段)。然后操作系统的堆栈将处理一次大的 32 KB 虚拟段,并且数据包处理开销将分摊到 32 KB 而不是通常的 1500 字节。这是大发送卸载的接收端等价物。但是,此功能会破坏路由器,因此不能在进行 IP 转发的机器上使用。
所有这些先前列出的 TCP 卸载功能都是无状态的。它们很好,因为它们实际上不需要对操作系统的 TCP 堆栈进行任何修改。有一种更完整的 TCP 卸载方式,称为TCP 卸载引擎。其中,TCP连接的部分状态被卸载到网卡,由网卡进行部分TCP包处理。但是,问题是不同的制造商对网卡应该做什么有不同的看法。因此,在实践中,如果有 10 个 NIC 供应商,您需要 10 个不同的操作系统 TCP 堆栈副本,每个副本都经过定制修改以与特定的 NIC 供应商合作。不会发生!此外,一旦规则被硬编码到 ASIC,您就无法修复规则中的错误。
在使用良好网络接口卡的现代 Linux 系统中,使用了校验和卸载、大发送卸载和大接收卸载。但是,任何标准 Linux 内核都不支持 TCP 卸载引擎。同样可能也适用于 Windows。
网络适配器是第 1/2 层设备,它真正不知道或不关心作为有效负载携带的第 3 层(IP 等)或第 4 层(TCP、UDP 等)协议。第 2 层(以太网等)协议。NIC 很乐意携带类似 IPX 之类的东西,它使用 SPX 作为其第 4 层协议。
编辑:
高速适配器(1 Gb 及以上)确实具有特殊的硬件来处理 NIC 本身上的 TCP,从而节省 CPU 周期。这通常在服务器上发现,因为它们比用户工作站处理更多的流量。如果“TCP 卸载”导致应用程序问题,可以通过适配器设置禁用它。
恕我直言,我假设 Lidgren 正在第 7 层(应用层)上工作。这会增加 CPU 开销和延迟。数据包仍由操作系统支持的较低层正常处理,因为 Lidgren 不会改变操作系统处理 UDP 的方式。因此,要在更高层上处理额外的检查,需要时间并使用额外的资源。不太确定,但也许lidgren 在由应用程序处理的数据中添加了额外的标头,为了处理它,您需要 CPU,而延迟可能会增加。