慢启动和丢包重传
在TCP连接刚开始的时候, 不启用延迟确认功能(而是立刻对数据包发送ACK), 这样可以让连接迅速渡过快启动模式
慢启动只会在刚开始和超时重传之后发生. 同时, 如果长时间无操作或者认为cwnd不能反应当前网络状况, 都应该重头开始.
各种版本的TCP
- Tahoe : 最原始的版本(一开始是丢包就恢复到1, 后来改为ssthresh)
- Reno : 增加了快重传: 丢包后的任何一个ACK都意味着可以增加速率, 即使这些ACK是重复的. 任何"新"ACK都意味着可以退出快重传模式
缺陷: 如果有多个包丢失, 那么针对一个包的ACK会不会让连接退出快重传? (称为部分ACK). 因此Reno采用了超时计时器, 针对所有丢失包进行计时. 新问题: 如果不够3个重复ACK来激发快重传, 那么超时计时器就会被出发, 最终回到了慢启动 - newReno: 追踪最高序号的包, 只有收到了最高序号的包的ACK, 才意味着退出快重传
SACK重传:
更高效, 高效重传真正需要重传的内容, 但是可能因为过度发包又引发了拥塞. SACK只是知道了要发什么, 缺乏信息得知发送多少和何时发送. 因此SACK需要记录飞行中的真正可抵达的数据的大小来估计本次重传的大小.
FACK 转发确认:
遇到重传状况时, 只要收到了两个重复的ACK, 就允许发送一个包, 从而避免了刚开始RTT/2时间的等待. 缺点: 如果乱序抵达会让FACK更加激进. Linux中已经启用了FACK功能
有限传输:
当窗口很小的时候, 可能没有足够的包来激发快速重传, 因此当窗口小时, 只要收到了连续的重复ACK 就允许发送下一个包. 这样保证了网络中存在足够的包(来激发快重传).
拥塞窗口校验CWV
问题: 如果在发送过程中, 停止一段时间, 那么恢复后会引发一个"剧烈的重传"或者"双方寂静". 原因: 双方都使用着过时的ssthreash
Congestion Window validation 算法: 记录最后一个发送的包的时间, 如果超过一个RTO没有回应, 就更ssthreath改为max(ssthreath, 0.75*cwnd)
cwnd不断变小, 这种思想是: 减缓过去的ssthreath带来的影响, 平滑之
处理假RTO
问题: 如果出现了伪丢包(临时超时, 后来又得到了), 那么ssthreath不应该降下去, 从而浪费了很多不必要的重传.
思想:
- cwnd = 飞行大小 + min(bytes_acked, IW). 新加入的IW不会对现有的网络造成过大的压力,
- 使用一个值来记录ssthreath降之前的值, 同时 同时如果发现了是"伪丢包"的情况, "retract"之前的算法, 把ssthreath变回去.
实战
本地链路丢包
如果发送方由于系统调用等其他因素一段时间没有发送, 再发送时, TCP可能会将大量的包丢给下面, 如果IP层处理不及时, 就会选择直接丢弃, 这些丢弃在Wireshark是看不到的.称为"本地拥塞", 可以在linux下使用tc -s -d命令查看接口上又多少包被丢掉了-
拉伸的ACK: 有可能一个ACK同时确认了两个最大的TCP包, 最有可能的原因是上一个TCP的ACK包在传输过程中丢失了. 不过这个同时确认两个TCP包的ACK可能会引发拥塞避免过程.
image.png 一次超时过后, TCP变成了慢启动的状态. 虽然Receiver还会发送SACK报文, 但是Sender不一定持续相信. 这就是TCP"倾向于遗忘"的特性. 因为接收端是允许缩小接收窗口的, 如果缩小了, 那么接收方说得就是"食言".
又一次超时过后, 虽然收到了Receiver对这个报文的ACK. 但是慢启动行为不能undo, 因此又慢骑电动了
TCP的信息共享性
TCP和相同Endpoints刚刚关闭的连接共享ssthreath和cwnd变量, 有利于前者的生成. 默认在Linux中开启
TCP的友善性
可以设置一个友善度, 通过RTT, 包大小, 丢包率, 重传超时时间来设定这个友善值, 意味着TCP和其他TCP/非TCP共享一条链路时的退避程度, 使得这种设置友善性的终端对于链路有更好的利用率.
高时延带宽积TCP
两个问题: cwnd的快速增长和慢启动的初始大小.
思想: cwnd增长时要考虑当前的cwnd大小
慢启动的初始大小可以设置很大.
低丢包率的时候增长更激进
研究发现, 低RTT在相同条件下能获得更好的效率, 当然这也好推测, 因为高RTT会消耗更多网络资源(路由), 理应更低效.
BIC-TCP: 一种意境被应用到linux的用于高时延带宽积网络的算法. 该算法有两种算法(不可同时启用):
- 二分查找法. 假设无丢包时的window是最小, 刚丢包的时候window是最大. 那么二分查找最合适的window大小, 缺点是越靠近最佳值, 这个算法的步进越小, 仿佛和其他标准的TCP算法是反着来的哈..
- 加性增长法: 二分查找法刚开始的时候, window直接变到了mid点, 这时候会发送大量的数据到网络中. 加性增长法严格限制新注入进来的数据包的量. 在探测max window的时候和标准TCP一样, 线性增长, 这样只要到达了最佳点附近, 大概率会出现丢包.
CUBIC算法: BIC算法在特定情况下过于激进. CUBIC在window的增长上既能体现"激进性", 又能体现"保守性"
基于延迟的拥塞控制
除去上文中提及的算法, 都是基于丢包和超时重传, 还有一种基于ECN(报告网络阻塞状况的选项)的算法: 基于延迟的拥塞控制.
即使没有ECN, 终端也可以根据RTT激增来判断是出现了阻塞.
Vegas算法: 相比于传统TCP算法喜欢将链路"注满", Vegas更希望"保持排空", 发现一点拥塞就降速. 缺点: 如果反向的链路状态不好, Vegas算法会被迷惑, 从而降低发送速率.
未来 : 结合延迟和丢包的控制算法, 两种都用?
缓冲爆炸
大量的内存反而会导致TCP性能下降?
是这样的, TCP的性能调优是为了让瓶颈尽可能的满着, 但是过大的buffer会延迟 反馈问题所在的位置. 同时, 小上行(比如同时开着别的上传进程) , 会导致接收方的消息难以反馈到发送方.
一个提议的解决方案就是使用上文的基于延迟的拥塞控制, 但是可能会导致延迟震荡时性能不好.
ECN精准拥塞通知
普通的路由器是被动(passive)的, 拥塞的时候只知道丢包, 不拥塞的时候FIFO. 主动的(Active)的路由器应当会报告链路质量, 同时只能排序进来的TCP包. ECN技术常用在网络供应商的路由器. ECN可以在网络层实现, 也可以在传输层TCP实现.
好处: 过度拥塞的时候, 两个Endpoint不要再快速重传, 直接慢启动
相关攻击
这部分格外的有趣. 主要是和自私的客户端相关.
对于一个片的逐个ACK
对于收到的一个大包, 分成小ACK回应, 会让发送方认为这个对方网络状况良好(因为TCP基于收到的ACK包的总数来估计网络状况),cwnd激增, 这个客户端受到"更不公平更好"的待遇. 解决方法是基于发送的数据总大小来估算cwnd而不是收到的ack包.
重复ACK
用于快恢复的时候, 客户端发送超多的ACK, 来让服务器尽快提升cwnd.
姑且的解决方法时在快恢复的时候限制最大发送速率.
提前ACK
客户端提前ACK(即使还没收到), 发送方收到了自己还没发送的序号的包的ACK只会简单丢弃, 因此这个方法也能让发送方快速扩大cwnd.
问: namespace假如客户端丢数据了怎么办? 这个方法特别适用于HTTP. 因为这些数据都是可重传, Session层可以保证完整接收.
结语
让一个TCP协议同时兼容上述这么多的特性属实不容易, 因此Linux的TCP源码已经超过了20000行, 因此使用可视化的分析是肯定的 如tcpdump, Wireshark, tcptrace等能够基于时间轴分析.