影响 TCP 网络时延的因素
硬件速度
网络和服务器的负载
请求和响应报文的尺寸
客户端和服务器之间的距离
TCP 协议的技术复杂性
TCP 协议产生的时延
TCP 连接建立握手;
TCP 慢启动拥塞控制;
数据聚集的 Nagle 算法;
用于捎带确认的 TCP 延迟确认算法;
TIME_WAIT 时延和端口耗尽.
TCP 连接建立
TCP 连接的建立, 需要经历 3 个报文的交互过程, 沟通相关连接参数, 这个过程称为三次握手(three-way handshake).
因此, 如果在每次发送数据之前, 都重新建立一次 TCP 连接, 那么建立连接的耗时将对性能产生较大的影响(特别是在发送少量数据的情况下).
三次握手四次挥手(参考自 coolshell)
优化方法
建立长连接, 多次数据的发送复用同一条连接.
TCP 慢启动
如果在发送方和接收方之间存在多个路由器和速率较慢的链路, 此时多个发送方一开始便向网络发送多个报文段, 由于受网络传输和服务端处理能力的影响, 一些中间路由器必须缓存分组, 并有可能最终耗尽存储器的空间, 因而更多的报文发送将使网络出现拥塞.
TCP 慢启动(slow start), 就是用于防止因特网的突然过载和拥塞的一种流量控制机制.
慢启动为发送方的 TCP 增加了一个窗口: 拥塞窗口(congestion Windows), 简称 cwnd.
刚建立连接时, 拥塞窗口被初始化为 1 个报文段. 每收到一个 ACK, 拥塞窗口就增加一个报文段. 发送方取拥塞窗口与接收方通告窗口中的最小值作为发送上限.
也就是说, TCP 连接会随着时间进行自我 "调谐", 起初会限制连接的最大速度, 如果数据成功传输, 会随着时间的推移提高传输的速度.
注: 拥塞窗口是发送方使用的流量控制, 而通告窗口则是接收方使用的流量控制.
优化方法
采用长连接, 避免每次建立连接后的慢启动.
Nagle 算法
在广域网上, 小分组会增加网络拥塞出现的可能性. Nagle 算法 (根据其发明者 John Nagle 命名) 旨在收集这些小分组, 以一个分组的方式发出去, 以提高网络效率.
该算法要求一个 TCP 连接上最多只能有一个未被确认的未完成的小分组, 在该分组的确认到达之前不能发送其他的小分组.
该算法的优越之处在于它是自适应的: 确认到达得越快, 数据也就发送得越快.
Nagle 算法会引发以下性能问题
当报文无法填满一个分组时, 需要等待其他额外数据;
Nagle 算法会阻止数据的发送, 直到有确认分组抵达为止, 但确认分组自身会被延迟确认算法延迟 100 ~ 200 毫秒.
算法伪代码
- if there is new data to send
- if the Windows size >= MSS and available data is >= MSS
- send complete MSS segment now
- else
- if there is unconfirmed data still in the pipe
- enqueue data in the buffer until an acknowledge is received
- else
- send data immediately
- end if
- end if
- end if
优化方法
对于实时性要求较高的应用场景, 可以通过设置 TCP_NODELAY 参数来关闭 Nagle 算法, 提高性能.
延时确认算法
通常 TCP 在接收到数据时并不立即发送 ACK; 相反, 它推迟发送, 以便将 ACK 与需要沿该方向发送的数据一起发送.
有时称这种现象为数据捎带 ACK, 由于确认报文通常很小, 所以 TCP 允许在发往相同方向的输出数据分组中对其进行 "捎带".
绝大多数实现采用的时延为 200ms, 也就是说, TCP 将以最大 200ms 的时延等待是否有数据一起发送.
通常, 延迟确认算法会引入相当大的时延.
优化方法
根据所使用操作系统的不同, 可以调整或禁止延迟确认算法.(这个方法我没尝试过)
TIME_WAIT 状态
TIME_WAIT 状态也称为 2MSL 等待状态.
当某个 TCP 端点关闭 TCP 连接时, 会在内存中维护一个小的控制块, 用来记录最近所关闭连接的 IP 地址和端口号.
这类信息只会维持一小段时间, 通常是所估计的报文段最大生存时间的的两倍(称为 2MSL, 通常为 2 分钟左右), 以确保在这段时间内不会创建具有相同地址和端口号的新连接.
实际上, 这个算法可以防止在两分钟内创建, 关闭并重新创建两个具有相同 IP 地址和端口号的连接.
将 2MSL 的值取为 2 分钟是有历史原因的. 很早以前, 路由器的速度还很慢, 人们估计, 在将一个分组的复制副本丢弃之前, 它可以在因特网队列中保留最多一分钟的时间. 现在, 最大分段生存期要小得多了.
报文段最大生存时间 MSL(Maximum Segment Lifetime), 是指任何报文段被丢弃前在网络中的最长生存时间.
RFC 793 [Postel 1981c]指出 MSL 为 2 分钟. 然而, 实现中的常用值是 30 秒, 1 分钟或 2 分钟.
优化方法
打开 tcp_tw_reuse, 让程序可以重用处于 TIME_WAIT 状态的端口. 如果使用 tcp_tw_reuse, 必需设置 tcp_timestamps=1(默认值).(这个对于快速重启某些服务很有用, 特别是服务端程序)
打开 tcp_tw_recycle, 让处于 TIME_WAIT 状态的套接字更快的回收.
贴一个 nagle 算法更详细的讲解
Nagle 算法用于对缓冲区内的一定数量的消息进行自动连接. 该处理过程(称为 Nagling), 通过减少必须发送的封包的数量, 提高了网络应用程序系统的效率.
1. Nagle 算法的规则
(可参考 tcp_output.c 文件里 tcp_nagle_check 函数注释):
1)如果包长度达到 MSS(MSS 是最大分段大小 Maxitum Segment Size ,MTU 是最大传输单元 Maxitum Transmission Unit), 则允许发送;
2)如果该包含有 FIN, 则允许发送;
3)设置了 TCP_NODELAY 选项, 则允许发送;
4)未设置 TCP_CORK 选项时, 若所有发出去的包均被确认, 或所有发出去的小数据包 (包长度小于 MSS) 均被确认, 则允许发送.
对于规则 4), 就是说一个 TCP 连接上最多只能有一个未被确认的小数据包, 在该分组的确认到达之前, 不能发送其他的小数据包. 如果某个小分组的确认被延迟了, 那么后续小分组的发送就会相应的延迟. 也就是说延迟确认影响的并不只是被延迟确认的那个数据包, 而是后续所有的应答包.
2. Nagle 算法的门槛
实际上 Nagle 算法并不是很复杂, 他的主要职责是数据的累积, 实际上有三个门槛:
1)缓冲区中的字节数达到了一定量;
2)等待了一定的时间(一般的 Nagle 算法都是等待 200ms);
3)紧急数据发送.
这三个门槛的任何一个达到都必须发送数据了. 一般情况下, 如果数据流量很大, 第二个条件是永远不会起作用的, 但当发送小的数据包时, 第二个门槛就发挥作用了, 防止数据被无限的缓存在缓冲区不是好事情哦.
3. Nagle 算法的选项配置
TCP_NODELAY 和 TCP_CORK 都是禁用 Nagle 算法, 只不过 NODELAY 完全关闭而 TCP_CORK 完全由自己决定发送时机. Linux 文档上说两者不要同时设置.
3.1 TCP_NODELAY 选项
设置该选项: setsockopt(s,IPPRO_TCP,TCP_NODELAY,(const char*)&on,sizeof(int));
读取该选项: getsockopt(s,IPPRO_TCP,TCP_NODELAY,(char*)&on,&optlen);
默认情况下, 发送数据采用 Nagle 算法. Nagle 算法是指发送方发送的数据不会立即发出, 而是先放在缓冲区, 等缓存区满了再发出. 发送完一批数据后, 会等待接收方对这批数据的回应, 然后再发送下一批数据.
Nagle 算法适用于发送方需要发送大批量数据, 并且接收方会及时作出回应的场合, 这种算法通过减少传输数据的次数来提高通信效率.
如果发送方持续地发送小批量的数据, 并且接收方不一定会立即发送响应数据, 那么 Nagle 算法会使发送方运行很慢.
3.2 TCP_CORK 选项
TCP 链接的过程中, 默认开启 Nagle 算法, 进行小包发送的优化. 优化网络传输, 兼顾网络延时和网络拥塞. 这个时候可以置位 TCP_NODELAY 关闭 Nagle 算法, 有数据包的话直接发送保证网络时效性.
在进行大量数据发送的时候可以置位 TCP_CORK 关闭 Nagle 算法保证网络利用性. 尽可能的进行数据的组包, 以最大 mtu 传输, 如果发送的数据包大小过小则如果在 0.6~0.8S 范围内都没能组装成一个 MTU 时, 直接发送. 如果发送的数据包大小足够间隔在 0.45 内时, 每次组装一个 MTU 进行发送. 如果间隔大于 0.4~0.8S 则, 每过来一个数据包就直接发送.
Nagle 组织包的长度是由系统决定的, 有时候我们知道我们会每个 1 分钟产生 1 字节, 共 1000 字节. 如果完全由 Nagle 算法来发送的话, 可能还是会 1 字节 1 字节发送[这是一种极端情况, 假设返回 ACK 时间不是很长]. 这个时候首先设置 TCP_CORK 能够阻塞住 TCP[尽量阻塞住], 等我们 write 完 1000 字节之后, 取消 TCP_CORK, 这个时候就能够将 1000 字节一次发出.
总结:
TCP_CORK 选项与 TCP_NODELAY 一样, 是控制 Nagle 化的.
1)打开 TCP_NODELAY 选项, 则意味着无论数据包是多么的小, 都立即发送(不考虑拥塞窗口).
2)如果将 TCP 连接比喻为一个管道, 那 TCP_CORK 选项的作用就像一个塞子.
设置 TCP_CORK 选项, 就是用塞子塞住管道, 而取消 TCP_CORK 选项, 就是将塞子拔掉.
当 TCP_CORK 选项被设置时, TCP 链接不会发送任何的小包, 即只有当数据量达到 MSS 时, 才会被发送.
一般当数据传输完成时, 通常需要取消该选项, 以防被塞住, 这样才可以让不够 MSS 大小的包能及时发出去.
来源: http://www.bubuko.com/infodetail-3158781.html