欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

TCP协议下Socket接收比较慢点原因

程序员文章站 2022-03-16 17:02:58
在做一个游戏,发现阻塞和异步方式接受服务端的包都很反应很慢(不是网速问题),本机访问本机没这个问题,局域网有感觉200ms左右的延迟,部分机型感觉明显,部分不明显。找了很多资料,查到下面的文章...

在做一个游戏,发现阻塞和异步方式接受服务端的包都很反应很慢(不是网速问题),本机访问本机没这个问题,局域网有感觉200ms左右的延迟,部分机型感觉明显,部分不明显。找了很多资料,查到下面的文章,总算明白了。

https://support.microsoft.com/zh-cn/kb/214397

设计问题-通过使用 Winsock TCP 发送较小的数据段

 电子邮件

 打印

重要说明:本文是由 Microsoft 机器翻译软件进行的翻译并可能由 Microsoft 社区通过社区翻译机构(CTF)技术进行后期编辑,或可能是由人工进行的翻译。Microsoft 同时向您提供机器翻译、人工翻译及社区后期编辑的文章,以便对我们知识库中的所有文章以多种语言提供访问。翻译的文章可能存在词汇、句法和/或语法方面的错误。Microsoft 对由于内容的误译或客户对内容的使用所导致的任何不准确、错误或损失不承担责任。

概要

您需要通过 TCP 发送较小的数据的数据包,Winsock 应用程序设计时尤其重要。不会不考虑到延迟的确认,Nagle 算法以及 Winsock 缓冲的交互设计可以显著影响性能。本文讨论了这些问题,使用了几个案例研究,并派生一连串 Winsock 应用程序从高效地发送较小的数据的数据包的建议。

背景

当 Microsoft TCP 堆栈接收数据包时,200 毫秒延迟计时器响起。当最终发送一个 ACK 时,延迟计时器重置,并且接收到下一个数据包时,将启动另一个 200 毫秒延迟。为了提高在互联网和内联网应用程序的效率,Microsoft TCP 堆栈使用以下标准来决定什么时候发送一个 ACK 数据包接收到的数据 ︰

如果延迟计时器过期之前收到第二个数据包,将确认发送。

如果有数据要发送 ACK 的方向相同,收到第二个数据包,并延迟计时器过期之前,确认是 piggybacked 与数据段并立即发送。

当延迟计时器到期时,将确认发送。

若要避免 congest 网络的小型数据包,Microsoft TCP 堆栈通过 Nagle 算法默认情况下,将合并来自多个发送呼叫和延迟直到上一个数据包的 ACK 发送接收从远程主机发送的较小的数据缓冲区。Nagle 算法的两个例外情况如下 ︰

如果堆栈已合并数据的缓冲区最大传输单位 (MTU) 比更大,大的数据包而无需等待来自远程主机的确认立即发送。在以太网上,tcp/ip 的 MTU 为 1460 字节。

应用 TCP_NODELAY 套接字选项,以禁用 Nagle 算法,以使较小的数据的数据包会传输到远程的主机,而不会延迟。

为了优化性能,在应用层,Winsock 副本从应用程序的数据缓冲区发送到 Winsock 内核缓冲区调用。然后,堆栈使用自己 (如 Nagle 算法) 的试探法确定何时把数据包在网络上。您可以更改的 Winsock 内核缓冲分配到套接字使用 SO_SNDBUF 选项 (它是 8k,默认情况下)。如有必要,多 SO_SNDBUF 缓冲区大小 Winsock 可以极大地缓冲。在大多数情况下,在应用程序完成发送只表示应用程序中的数据缓冲区发送调用复制到 Winsock 内核缓冲区,并不表示数据已达到网络媒体。唯一的例外是当您禁用了 Winsock 缓冲通过将 SO_SNDBUF 设置为 0。

Winsock 使用下列规则来指示完成发送给应用程序 (具体取决于调用发送方式,完成通知可能会返回一个阻塞调用,发送信号事件或调用通知函数,该函数等) ︰

如果套接字仍然位于 SO_SNDBUF 配额,Winsock 拷贝数据从应用程序发送并指示完成发送给应用程序。

如果套接字已 SO_SNDBUF 配额之外,没有堆栈内核缓冲区中仍然只有一个以前缓冲的发送,Winsock 拷贝数据从应用程序发送并指示完成发送给应用程序。

如果套接字是 SO_SNDBUF 配额之外,还有多个以前缓冲中的堆栈内核缓冲区发送 Winsock 会复制从应用程序发送的数据。直到堆栈完成足够发送放回 SO_SNDBUF 配额或只有一个未完成发送条件内的套接字,Winsock 并不表示完成发送给应用程序。

案例研究 1

概述 ︰

Winsock TCP 客户端需要将 10000 记录发送到 Winsock TCP 服务器,存储在数据库中。记录的大小因 100 字节长到 20 个字节。为了简化应用程序逻辑,设计,情况如下 ︰

客户端没有阻止发送只。该服务器将执行仅阻止接收。

客户端套接字将 SO_SNDBUF 设置为 0,这样单个数据段中发出的每个记录。

服务器在循环中调用接收。发布在接收缓冲区是 200 个字节,因此可能收到一个接收调用中的每个记录。

性能 ︰

在测试期间,开发人员查找客户端可以只发送到服务器的每秒五条记录。总 10000 记录最大值在 976 64k 字节的数据 (10000 * 100 / 1024年),花费超过半个小时将发送到服务器。

分析 ︰

由于客户端不会设置 TCP_NODELAY 选项,Nagle 算法强制 TCP 堆栈来等待一个 ACK 之前可以将另一个数据包发送线路上。但是,客户端已禁用通过 SO_SNDBUF 选项设置为 0 的 Winsock 缓冲。因此,10000 发送调用必须发送和确认分别。每个 ACK 是延迟的 200 毫秒,因为服务器的 TCP 堆栈上发生以下情况 ︰

当服务器收到一个数据包时,其 200 毫秒延迟计时器响起。

服务器不需要发送任何内容,因此不能 piggybacked ACK。

客户端将不会发送另一个数据包,除非得到确认前一个包。

在服务器上的延迟计时器过期并将确认发送回。

如何改进 ︰

有此设计两个方面的问题。首先,没有延迟计时器问题。客户端需要能够到 200 毫秒内服务器发送两个数据包,因为默认情况下,客户端使用 Nagle 算法,应该只需使用默认 Winsock 缓冲,并将 SO_SNDBUF 设置为 0。一旦 TCP 堆栈已合并比最大传输单位 (MTU) 更大的缓冲区,一个全尺寸的数据包被发送立即而无需等待来自远程主机的确认。

其次,这种设计为每个记录的这种小尺寸调用一个发送。发送此大小的小不是非常有效的。在这种情况下,开发人员可能需要填充到 100 个字节的每个记录并发送 80 记录从一个客户端一次发送呼叫。要让服务器知道将总共发送多少条记录,客户端可能想要从沟通开始修复大标头包含要执行的记录数。

案例研究 2

概述 ︰

Winsock TCP 客户端应用程序打开的 Winsock TCP 服务器应用程序提供股票报价服务的两个连接。第一个连接用作命令通道将股票代码发送到服务器。第二个连接作为数据信道用于接收股票报价。在建立两个连接之后,客户端向服务器通过命令通道发送股票代码和等待股价要回来通过数据通道。只在收到第一个股票报价之后,它会向服务器发送下来,股票代码。在客户端和服务器未设置 SO_SNDBUF 和 TCP_NODELAY 选项。

性能 ︰

在测试期间,开发人员查找客户端仅能获得每秒五个报价。

分析 ︰

这种设计一次只允许一个出色的股票报价请求。第一个股票发送通过命令通道 (连接) 到服务器,并且响应立即重新从服务器发送到客户端通过数据通道 (连接)。然后,客户端立即发送第二个股票代码请求中发送呼叫请求缓冲区复制到 Winsock 内核缓冲区发送立即返回。但是,客户端的 TCP 堆栈不能发送请求从其内核缓冲区立即因为第一个通过发送命令通道未尚未确认。200 毫秒延迟计时器在后服务器命令通道过期,则第一个符号请求的确认会回到客户端。然后,第二个报价请求已成功发送到服务器 200 女士为第二个股票报价恢复后立即通过数据通道因为到目前为止,在客户端数据信道延迟计时器已过期的延迟之后。服务器收到前一报价响应确认。(请记住,客户端无法发送 200 毫秒,第二个股票报价请求这样为延迟计时器过期并向服务器发送一个 ACK 客户端上显示时间)。因此,客户端获取的第二个报价响应并且可以发布另一个报价请求,遵守相同的周期。

如何改进 ︰

两个连接 (通道) 设计在此处是不必要的。如果只有一个连接用于股票报价请求和响应,可以引述响应 piggybacked 报价请求的确认,并立即回来。为了进一步提高性能,客户端可以”多路传输”股票报价的多个请求到一个发送呼叫的服务器和服务器可能还”多路传输”多个报价响应到客户端一次发送调用。如果由于某种原因确实需要两个单向通道设计,双方应设置 TCP_NODELAY 选项,以便无需等待前一个数据包的 ACK,可以立即发送小数据包。

建议 ︰

而在编造这些两个案例研究,它们有助于阐明某些最坏的情况。在设计应用程序,其中涉及到大量的小型数据段发送和 recvs,应考虑以下准则 ︰

如果的数据段不急于,应用程序应合并它们到更大的数据块要传递到发送调用。因为可能会被复制到 Winsock 内核缓冲区发送缓冲区,该缓冲区不应太大。略少于 8k 时通常是有效。只要 Winsock 内核获取比 MTU 大块,它会发送出多个大包和剩余的最后一个数据包。除的最后一个数据包的发送端不会命中 200 毫秒延迟计时器。最后一个数据包,它正好是一个奇数据包中,仍可能会有延迟的确认算法。如果发送端堆栈获取 MTU 大于另一个块,它仍然可以绕过 Nagle 算法。

如果可能,请避免使用单向数据流的套接字连接。通过单向套接字的通信,这是更容易受到 Nagle 和延迟确认算法。如果通信遵循请求和响应流,则应使用单个插座进行发送和 recvs,以便可以在响应 piggybacked ACK。

如果要立即发送所有较小的数据段,设置在发送端的 TCP_NODELAY 选项。

要保证网络上发送一个数据包,当发送完成由 Winsock,除非您不应该 SO_SNDBUF 设置为零。实际上,默认 8k 缓冲区已试探性地确定适用于大多数情况下,您不能更改它除非进行了测试,新的 Winsock 缓冲区设置使您可以更好的性能,而不是默认。此外,将 SO_SNDBUF 设置为零很是有用的应用程序执行批量数据传输的。甚至,实现最大效率使用时应结合双缓冲 (在任意给定时间多个未完成发送) 然后重叠 I/O。

如果不需要保证数据传递,使用 UDP。