TCP2
TCP2
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
TCP为什么可靠?
- TCP通过三次握手、四次挥手保证连接的可靠性
- 然后通过校验和、序列号确认机制、超时重传、流量控制、拥塞控制等机制保证数据的可靠传输
1. 重传机制
1.1 超时重传
重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK
确认应答报文,就会重发该数据,也就是我们常说的超时重传。
RTT
(Round-Trip Time 往返时延):数据发送时刻到接收到确认的时刻的差值
RTO
(Retransmission Timeout 超时重传时间)超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。
实际上「报文往返 RTT 的值」是经常变化的,因为我们的网络也是时常变化的。也就因为「报文往返 RTT 的值」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值。
1.2 快速重传
当发送端同时收到三个ACK2
,发送端就知道Seq2
没有送到,于是就会立刻重传丢失的Seq2
。快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
快速重传解决了超时重传中需要计算超时时间的问题,但它也有别的问题——当Seq2
和Seq3
都丢失了,发送端收到3个连续的ACK2
,它是不知道Seq3
是否丢失的,这时解决方法有两种:
- 只重传
Seq2
一个报文,那么Seq3
的重传还得等到发送端收到3个连续的ACK3
,效率很低 - 重传
Seq2
后的所有报文,虽然能够同时重传丢失的Seq2
和Seq3
,但是后面没有丢失的报文还是会被重传一份,浪费资源
1.3 SACK (Selective Acknowledgment, 选择性确认)
既然快速重传中发送端不知道哪些报文丢失,那让接收方告诉它不就好了。
SACK允许接收方告诉发送方哪些报文已收到,哪些丢失了,以减少不必要的数据传输,解决了快速重传中的问题
这种方式需要在 TCP 头部「选项」字段里加一个 SACK
的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据
1.4 Duplicate SACK
用于通知发送方哪些数据被重复接收了,有助于发送方了解是 数据丢失 或 ACK
丢失 或 网络延迟导致的重传
2. 滑动窗口
2.1 发送方滑动窗口
SND.WND
:表示发送窗口的大小(大小是由接收方指定的);SND.UNA
(Send Unacknoleged):是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。SND.NXT
:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。指向 #4 的第一个字节是个相对指针,它需要
SND.UNA
指针加上SND.WND
大小的偏移量,就可以指向 #4 的第一个字节了。
2.2 接收方滑动窗口
RCV.WND
:表示接收窗口的大小,它会通告给发送方。RCV.NXT
:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要
RCV.NXT
指针加上RCV.WND
大小的偏移量,就可以指向 #4 的第一个字节了。
2.3 发送方和接收方的滑动窗口大小是相等的吗?
并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。
当接收方读取数据速度非常快时,它的窗口很快就会空出来,通过TCP报文段中的Windows
字段告诉发送方自己的接收窗口大小,这个传输过程是有时延的,故两者关系是约等于。
3. 流量控制
发送端根据接收端的实际接收能力控制发送的数据量(由滑动窗口大小来保证)
3.1 窗口关闭
当接收方接收窗口rwnd
= 0时,窗口关闭,发送方不再发送数据。当接收方处理完数据,给发送方发送ACK
并告知发送方自己的接收窗口rwnd
增大了,但如果此时该ACK
丢失了,发送方就不知道接收方窗口已非0,接收方也还在等待发送方发送数据,造成死锁的现象。
解决方案:TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
4. 拥塞控制
流量控制是防止发送方数据填满接收方缓存,但它无法控制整个网络,当网络拥堵时,继续发送大量数据会导致数据包延时、丢失等,拥塞控制就是为了防止发送方的数据填满网络。
发送方通过维护一个拥塞窗口cwnd
,该窗口随网络拥堵情况而动态变化。由于我们前面已知swnd
约等于rwnd
,那么加入拥塞窗口后,swnd
= min(cwnd
, rwnd
)
下面给出拥塞控制算法:
4.1 慢启动
TCP连接建立后,先不要发大量的数据,初始化cwnd
= 1MSS
,每收到一个ACK
,cwnd
+= 1MSS
,直到cwnd
达到慢启动阈值ssthresh
,进入拥塞避免算法
慢启动过程窗口cwnd
的增长是指数级别的
4.2 拥塞避免算法
进入拥塞避免算法后,它的规则是:每当收到一个 ACK
时,cwnd
增加 1/cwnd
。
接上前面的慢启动的例子,现假定 ssthresh
为 8
:
- 当 8 个
ACK
应答确认到来时,每个确认增加 1/8,8 个 ACK 确认cwnd
一共增加 1,于是这一次能够发送 9 个MSS
大小的数据,变成了线性增长。
就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制,也就进入了「拥塞发生算法」。
4.3 拥塞发生算法
重传机制主要有两种:
超时重传:
ssthresh
=cwnd
/ 2,cwnd
= 1,进入慢启动快速重传:
cwnd
=cwnd
/ 2,ssthresh
=cwnd
,进入快速恢复算法
4.4 快速恢复算法
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像超时重传那么强烈。
进入快速恢复之前,cwnd
与ssthresh
已经被更新了:
cwnd = cwnd/2
,也就是设置为原来的一半;ssthresh = cwnd
;
然后进入快速恢复:
cwnd
=ssthresh
+ 3+3的原因
发送方收到3个重复的
ACK
,说明有3个数据已经被成功发送到接收方,它们已经离开网络了,这3个报文段不再消耗网络资源而是停留在接收方的接收缓存中,可见现在网络中不是堆积了报文段而是减少了3个报文段,因此可以适当把拥塞窗口扩大些重传丢失的数据(即被重复的
ACK
)若再收到重复的
ACK
,cwnd
=cwnd
+ 1(+1的道理同上)若收到新的
ACK
,说明恢复过程已结束,令cwnd
=ssthresh
,再次进入拥塞避免算法为什么要
cwnd
=ssthresh
?如果不将拥塞窗口设置回ssthresh的话,而是在原先的基础上进行拥塞避免,线性增长的话(起点较高,因为原先就是在这个节点附近触发的拥塞),会容易再次导致网络拥塞,导致丢包,又再次触发相应的快速重传快速恢复机制,导致发生概率较高,这就严重拖慢了协议的性能,因此需要将拥塞窗口先回退到一个较低的水平,一次先少发点数据,这样尽量避免拥塞重传,提高性能。