传输控制协议 TCP,把网际层不可靠的数据报传输服务(IP协议),转化为了可靠的面向连接的传输服务。
6.2.1 TCP服务模型
TCP 服务的特点:
- 面向连接的传输:传输前先建立连接,传输完毕后释放连接。
- 端到端通信(port - port):TCP不支持广播和组播,只能是1对1的。
- 高可靠性:TCP段顺序到达,即使出现丢失也必然重传(逻辑上视为不会丢失)
- 全双工传输:发方和收方建立连接后,建立了对称的两条路径,可以实现双向传输。
- 采用字节流方式:以字节为单位,传输字节序列。
字节流方式:
todo
6.2.2 TCP数据传输机制
1.TCP的段结构
TCP用于大数据量传输,应用层把很长的报文交给传输层后,传输层将其分段,称为TCP报文段。
TCP报文段的结构如下图:
最大报文段长度, MSS
MSS(maximum segment size): TCP段的众多选项之一。
MSS 是:在某个TCP连接中,端系统(主机/网关)能够接收的TCP段的数据部分的最大长度。单位字节。
TCP在三次握手中,每一方都会建议对方:发来的TCP段采用建议的MSS值。如果对方不同意该期望值,则对方会采用默认值。(IPv4, 576-20-20 = 536 bytes, IPv6 = 1220)
同一连接中,不同方向的TCP段可以有不同的MSS值。
MSS 长度并不包括TCP段的头部长度,和,IP 分组的头部长度。
IP层具有一个与MSS相似的概念---MTU(Maximum Transfer Unit)。
下图反映了 MSS与MTU 的关系:
MSS值太小或太大都不合适。
太小,例如MSS值只有1byte,那么为了传输这1byte数据,至少要消耗20字节IP头部+20字节TCP头部=40byte,这还不包括其二层头部所需要的开销,显然这种数据传输效率是很低的。
过大,导致分组封装很大,那么在IP传输中分片的可能性就会增大,接受方在处理分片包所消耗的资源和处理时间都会增大。如果分片在传输中还发生了重传,网络开销还会进一步增大。
因此合理的MSS是至关重要的。MSS的合理值应为保证分组不分片的最大值。对于以太网MSS可以达到1460byte.
RTT,往返时延
往返时延 RTT(Round-Trip Time) 或 RTD(Round-trip Delay Time) : 一个信号从源端到目的端的时间 + 该信号的应答信号从目的端返回源端的时间。
传输轮次:发出一个TCP段,收到一个TCP确认段。
6.2.6 TCP拥塞控制
从整体上来讲,TCP拥塞控制窗口变化的原则是AIMD(the Additive-Increase/Multiplicative-Decrease),即加法增大倍数减小。
AIMD原则可以较好地保证流之间的公平性,一旦某个连接出现丢包,那么立即将其 ssthresh 减半 将 cwnd退避到1 mss,给其他新建的流留有足够的空间,从而保证整个传输的公平性。
拥塞窗口 cwnd
拥塞窗口,cwnd, congestion window。
每个TCP连接中的发送方都有一个拥塞窗口,用于限制“待发而未确认的段”的数量。
拥塞窗口不等于TCP段中的win窗口——滑动窗口(rwnd)!
滑动窗口是由接收方维护的接收窗口,用于在ACK确认段中告知发送方:你还可以发送多少个报文段。
而拥塞窗口是发送方维护的发送窗口,它与接收方的滑动窗口和整个网络当前的拥塞状态都有关系。比如:有可能接收方的滑动窗口很大,但当前网络发生了拥塞,导致发送方的拥塞窗口(发送窗口)只能取很小的一个值。
发送方确定根据拥塞窗口和接收方的接收窗口,来确定发送窗口: \(发送方的发送窗口 = min(拥塞窗口 cwnd,接收方窗口 win)\)。
拥塞窗口会随着接收窗口、网络的拥塞状态而变化。
假设接收窗口足够大,发送方就会根据网络拥塞状况来调整拥塞窗口的大小:
- 只要网络没有出现拥塞,就把拥塞窗口增大一些,以便把更多的TCP段发送出去。
- 如果出现拥塞,就减小拥塞窗口,减少注入到网络中的TCP段。
尽管拥塞窗口的单位是字节,为了减少复杂度,理解算法的核心内容,讨论中的拥塞窗口数就用 MSS(Sender Max) 作为单位。
慢启动(Slow Start),cwnd 指数增大
慢启动:TCP连接的几种拥塞控制策略之一。
发送方刚开始发送数据时,并不清楚网络当前的拥塞情况,如果立即注入大量TCP段到网络中就可能引起拥塞,较好的方法是由小到大逐渐增大发送窗口(即拥塞窗口)。
在开始时,设置 cwnd = MSS * 很小的倍数,假设为 1 MSS
在每收到一个确认段后,把拥塞窗口扩大一倍。(这里有很多细节,简单理解为翻倍即可)。
在发生下列事件前,拥塞窗口会迅速的增大,数据传输率也相应的增大。
但发生下列事件时,拥塞窗口就会减小:
- TCP段丢失(被中间节点丢弃,说明发生了拥塞):此时就需要减少注入网络的负载。
- 接收方通知:接收窗口不允许继续发送了:调整拥塞窗口,甚至停发等待接收窗口变大。
- cwnd = ssthresh ,拥塞窗口达到慢启动门限了:停止慢启动过程(指数增大),进入拥塞避免阶段(线性增大)。
慢启动门限 ssthresh
慢启动的 cwnd 可以很快的增长,从而尽快最大程度的利用网络带宽,但 cwnd 不能一直无限增大,否则会发生拥塞。
TCP设置了一个变量:慢启动门限,ssthresh(slow start threshhold) 。
大多数TCP实现中,ssthresh = 65536 byte。
根据是否发生重传,来判断拥塞
TCP如何判断网络是否发生了拥塞呢?通过是否发生了重传来判断。
我们直到,重传可能发生在2种情况下:
- 段丢失:因为拥塞,中间节点资源不足,只能丢弃段。每一个发出的TCP段都有一个重传定时器(RTO,Retransmission Time Out),若直到RTO超时都尚未收到ACK确认段,那么发方就应对该报文段进行重传。
- 段错误:因为位错误,无法通过校验,节点将其丢弃,不发ACK确认段,time out后导致重传。
在某些情况下,比如无线网络中,传输错误确实可能发生,但相对来说,现在的网络,尤其是有线网,错误率非常低,因此一旦发生重传,则可以理解为就是发生了拥塞。
事实上,除了超时,后面会讲到的3 ACK,也会导致重传,它同样属于拥塞。因此把 重传 视作拥塞。
cwnd>=ssthresh,进入拥塞避免(congestion avoidance),cwnd 加法增大
一旦 cwnd >= ssthresh ,就结束慢启动阶段,进入拥塞避免阶段。
拥塞避免算法:
每次收到ACK确认段后,cwnd 只增加 1 个报文段的长度。
cwnd = cwnd + 1 MSS
( 具体实现有差异,有的版本是 cwnd = cwnd + 1mss / cwnd, 但都是线性递增的)
注意:
TCP连接中,发送方并不是从应用层每收到一个段就立即发送出去,而是可以积累到一定量后再发。
接收方也并不是每收到一个段,就立即发回ACK确认信息,同意可以对多个段只确认一次,只要ACK没有超时,这多个段就算接收成功了。
cwnd的值随着传输轮次线性增加,逐步调整发送窗口,在“最大化利用带宽”和“发生拥塞”之间寻找平衡点,避免了cwnd 增长过快导致网络拥塞很快发生的情况。
在拥塞避免阶段,随着 cwnd 的增大,最终也会发生拥塞,表现为确认段的超时,此时:
- ssthresh = cwnd / 2
- cwnd =1 MSS
- 开始慢启动阶段
快速重传, fast retransmit
通常情况下,TCP发送方使用一个计时器来识别段丢失:如果超过RTT时间没有收到该段的确认段,则认为该段已经丢失,应重传该段。
而快速重传则使得发送方无需等待RTT时间,即刻判断段丢失,从而更快的重传丢失段。
快速重传机制利用了TCP段的发方序列号和收方确认号的关系:
发方TCP段带有序列号 SEQ(sequence number),收方确认段带有确认号ACK(acknowledgement number)。
确认号的意义:收方告知发方,“序列号 < 确认号”的所有段都已正确接收,期待发送“序列号=确认号”的段。
假设,收方收到SEQ=1的段,随即返回 ACK = SEQ + 1 = 2的确认段,通知发方:SEQ=1的段已经接收,请发送SEQ=ACK=2的段!
再假设,发方发出了SEQ=2,3,4的3个TCP段,却都因为拥塞在网络中丢失了,而随后SEQ=5,6的段却抵达了收方。
收方对SEQ=5的段发送确认段时,按照ACK的含义,只能令ACK=2(即SEQ=1已正确接收,期待发送SEQ=2的段)。同理,SEQ=6的段的确认段也只能令 ACK=2。
这样一来,发方就会收到2个“ACK=2的确认段”,即“重复的确认段”。
一旦出现这种情况,发方就可以判断出:SEQ=2的段丢失了!此时无需继续等待 SEQ=2的段的确认段超时,可以立即重发 SEQ=2的段,SEQ =3,4 的段同理。这就是快速重传!
在某些实现中,发方收到3个相同ACK的确认段,即可立即重传SEQ=ACK的段。
如果在任意阶段发生了段丢失,
注意:段丢失发生在慢开始阶段,或拥塞避免阶段,都会触发 TCP Tahoe/ Reno #### TCP Tahoe
- 快速重传丢失的段
- ssthresh = cwnd / 2
- cwnd = 1 MSS
- 开始慢启动
TCP Reno,快速恢复
- ssthresh = cwnd / 2
- cwnd = cwnd / 2 + 3 = ssthresh + 3
- 快速重传丢失的段
- cwnd = ssthresh
- 开始拥塞避免(线性递增 cwnd)
快速恢复的“数据报守恒”原则
快速恢复的思想是“数据包守恒”原则:
同一个时刻在网络中的数据包数量是恒定的,只有当“老”数据包离开了网络后,才能向网络中发送一个“新”的数据包,如果发送方收到一个重复的ACK,那么根据TCP的ACK机制就表明有一个数据包离开了网络,于是cwnd加1。
如果能够严格按照此原则,那么网络中很少会发生拥塞,事实上拥塞控制的目的也就在修正违反该原则的地方。
具体来说快速恢复的主要步骤是:
- 当收到3个重复ACK时,把ssthresh设置为cwnd的一半(ssthresh = cwnd /2),把cwnd设置为ssthresh的值加3 ( cwnd = cwnd /2 + 3 ),然后重传丢失的报文段。
加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。 - 如果再收到重复的ACK,拥塞窗口增加1(如果丢失的段后有多于3个段抵达收方,或者快速重传的段被确认了,就还会收到重复的ACK)。
- 当收到新的ACK确认段时,把cwnd设置为第一步中的ssthresh的值(cwnd = ssthresh)。如果确认了新的段,说明快速重传的段已确认,进入拥塞避免状态。
实例讲解
下图中:
- 建立连接时,ssthresh = 16,cwnd = 1
- 点1之前是慢开始阶段,cwnd 增长很快,每经过一个轮次就翻倍,
- 当到达点1 时,进入拥塞避免,cwnd 每经过一个轮次加一,线性增长
- 到达点2时,网络发生拥塞,出现确认段 time out,此时将 ssthresh = cwnd /2 ,cwnd =1, 开始慢启动阶段,即点2-点3的阶段
- 点2-点3的慢启动阶段,cwnd 倍增,直到 cwnd >= ssthresh,
- 点3开始进入拥塞避免
- 点4,出现段丢失(可能是拥塞,也可能是网络错误,但都视为拥塞),此时采用快速恢复手段,ssthresh = cwnd /2, cwnd = cwnd +3, 快速重传丢失段,cwnd = ssthresh, 开始拥塞避免
- 点5,快速恢复后的拥塞避免阶段。
到达点2的时候,网络发生拥塞了,网络出现了超时。就将 ssthresh 设置为 cwnd 的一半,并将 cwnd 重新设置为1 ,重新开始慢开始算法。到点3 时,达到了阈值,继续采用了拥塞避免算法。