笔者最近在学习 TCP 协议时,翻阅了《数据通信与网络》一书,结合网络上的资料,整理了以下笔记。
1. TCP 提供的服务
- 端对端通信(Process to process communication) : 接收和发送双方都有 Sending and Receiving Buffers。
- 数据流服务(Stream delivery service)
- 双工通信(Full-duplex communication)
- 多路复用(Multiplexing)
- 面向连接(Connection-oriented service)
- 可靠的服务(Reliable service)
2. TCP 特点 (Features)
- 命名系统 (Numbering System) :
- 字节数 (Byte number):
- 每一个 byte in a segment 有一个 byte number;
- 传输过程中的第一个 segment 中的第一个 byte 的 byte number 是随机确定的,也被称为 Initial Sequence Number (ISN);
- 之后的所有 byte 都会在第一个 byte number 的基础上确定自己的 number。
- 序列号 (Sequence number): 就是每个 segment 的第一个 byte 的 byte number。
- 确认号(Acknowledgement number): 指的是接收方希望接受到的下一个 byte number。
- 字节数 (Byte number):
- 捎带确认 (Piggybacking):将返回信息延迟发送,和下一个数据传输包合并在一起发送,这样可以提高效率。
3. Segment
- MSS(Maximum Segment Size)
- 确定机制:MSS 是在三次握手时协商确认的,发送端发出 SYN 报文,其中 option 选项填充的 MSS 字段一般为1460,接收端收到 SYN 报文后,会发送 SYN+ACK 报文应答,option选项填充的 MSS 字段一般也为1460;协商双方会比较 SYN 和 SYN+ACK 报文中 MSS 字段大小,选择较小的 MSS 作为发送 TCP 分片的大小。这里通 过比较,协商双方的 TCP MSS 都是1460。参考资料:TCP 协议中MSS的理解
待续
4. A TCP Connection
- 建立连接:3次握手 (Three-Way Handshaking)
- 1)发送端–SYN–>接收端;2)接收端–SYN+ACK–>发送端;3)发送端–ACK–>接收端。
- ACK 不消耗序列号,而 SYN 和 SYN+ACK 需要消耗一个序列号。
- 为什么是三次握手而不是两次或者四次?因为三次握手正好可以让接收和发送方互相确认初始序列号,而两次是不能达到互相确认,四次是多余了。
- 断开连接:
- 三次挥手:1)主动关闭端–FIN–>被动关闭端;2)被动关闭端–FIN+ACK–>主动关闭端;3)主动关闭端–ACK–>被动关闭端。
- 半关(Half-Close):
- 也就是常说的四次挥手,其将三次挥手中的第二步分为两步,所以有了四次挥手;
- 1)主动端–FIN–>被动端;2)被动端–ACK–>主动端;被动端继续传输未传完的数据给主动端,主动端继续确认数据;3)被动端–FIN->主动端;4)主动端–ACK–>被动端。
5. 流量控制(Flow Control)
- 控制过程:
- 接收端首先确定双方窗口的大小(在三次握手中确认);
- 当发送窗口发送数据后,接受窗口接收到数据,接收窗口会变小(left wall closes),并将变小后的窗口大小发送给发送端,发送端会据此调整窗口大小(left wall closes/right wall shrinks),并确认上一次发送的数据被接收;
- 只有当接收端进程使用了数据之后,接收窗口才会打开(right wall opens),接着通知发送窗口也打开(right wall opens);
- 一般情况下,需要避免发送窗口的 shrinking;
- 窗口关闭 (Window Shutdown):若接收端在一段时间内不想接收到发送端发送的数据,可以将rwnd设置为0,从而使发送窗口变为0,即关闭发送窗口。
- Silly Window Syndrome:
- 现象描述:发送或接收端处理数据的速度很慢,导致每次传输的有效数据很小,这样会导致网络传输的低效。
- 由于发送端进程制造数据慢导致的 syndrome,可用 Nagle’s Algorithm 解决,发送端在等待数据达到最大限度后或者接收到确认信息时发送segment。
- 由于接收端进程消耗数据慢导致的 syndrome,可用:
- Clark’s solution: 接收端收到数据后返回确认信息,但是在缓冲区能够容纳一个最大的 segment 或一半的缓冲区空余之前,保持发送窗口大小为0;
- Delay Acknowledgment:接收端收到数据后不立即返回确认信息,在缓冲区有一定空余容量后,再返回确认信息。(这种方法可以减少网络的 trafic,但是可能导致发送端重传未确认的数据包,TCP 定义确认信息的延迟不得高于 500 ms)
6. 错误控制(Error Control)
-
错误包括:丢失、损坏、乱序、重复。
-
Checksum:
- 16-bit;
- 如果一条 segment 的 checksum 不正确,则会被抛弃,并标记为丢失。
-
Acknowledgment:
- 种类:
- Cumulative Acknowledgment(ACK):
- 确认报文,表明接收端发送想要得到的下一个序列号;
- 这种普通 ACK 是快重传中使用的。
- Selective Acknowledgment(SACK):
- 用来提供乱序和重复数据包的信息,处于 TCP header 的 option 中。
- 当发送端收到接收端返回的SACK后,就知道哪些报文是接收端已经收到的,进而将接收端没有收到的报文进行重传。注意:SACK协议需要通信双方都支持(通过三次握手明确)。参考资料:TCP 的那些事 | SACK
- SACK 是另外一种重传的方式,相对于快重传而言。
- Cumulative Acknowledgment(ACK):
- 何时生成ACK?(ACK 的触发机制)
- Rule 1:发送数据的同时要附带 ACK(因为双方有可能互为接收方和发送方),这样可以减少 traffic(捎带确认思想);
- Rule 2:发送端发完数据后,接收到有序的 segment,此时不立即返回 ACK segment,而是开启 ACK-Delaying Timer 等待 500 ms,在等待期间若收到又一个有序的 segment 则触发 Rule 3 (直接确认后面这个 segment), 这样可以减少确认信息的发送;
- Rule 3:当收到一个有序的 segment,而前一个 segment 还未确认时,直接返回后一个 segment 的确认信息,这样可以少发一个 ACK,美滋滋;
- Rule 4:当接收端收到一个乱序的 segment (说明有序的 segment 可能延迟或丢失或损坏被接收端抛弃),立即返回一个 ACK,声明想要接收到的下一个 segment 的正确序列号 (Sequence Number) ;
- Rule 5:当丢失的 segment 重传被接收,立即返回一个 ACK,确认接下来需要的正确序列号 (此时需要包括丢失期间保存的乱序 segments);
- Rule 6:当接收到重复的 segment,立即返回一个 ACK,确认接下来需要的正确序列号。
- 种类:
-
重传(Retransmission):
- 触发条件: segment 丢失 (lost) 或者 因为损坏 (corrupted) 被接收端抛弃 (discarded);
- RTO 重传(Retransmission Time-Out) :
- 计时时间根据 Round-trip Time (RTT) 确定;
- 发送端对每一个 TCP 连接都会维护一个 RTO 计时器,一旦计时器到时 (Time-out),就会触发等待确认队列中的第一个 segment 的重传,然后 RTO 计时器会开始新一轮计时 (Restart);
- 等待确认列队若清空了 (全都被确认),则计时器关闭 (Stop),等待队列加入新的待确认 segment 了,则重新开始计时器 (Start)。
- 快重传(Fast Retransmit):
- 当接收端收到三条一样的 ACK 时,将待确认队列中的首个 segment 重传(注意此时 RTO 计时器未到时);
- 为什么是三条重复 ACK,而不是一条、两条或四条? 因为出现一条或两条重复 ACK 时发送端不能判断是包延迟还是丢失(丢失和损坏这里可以看成是一样的)造成的,此时延迟的概率较大,而出现三条重复 ACK 时,包丢失的概率则变得很大了,适合触发重传,如果再等到收到四条重复 ACK,响应时间则过长,所以收到三条 duplicated ACK 时更适合触发重传。
-
乱序(Out-of-Order Segments) :
- 引起乱序的原因:有序包的丢失、延迟、损坏,都可引起乱序。
- 接收端将乱序数据包储存起来,等待正确顺序的数据包到达。TCP 会确保进程消耗的数据是有序的。
-
延迟(delay)和重复(duplicated) :因为 TCP 依赖于 IP,IP的运送路线不固定,所以会导致延迟,而造成丢失的假象,判定为丢失后,会触发重传,此时发送方因为延迟重复发送了 segment。接收端处理的方式简单粗暴,就是将重复的 segment 抛弃,然后根据 rule 6 发送 ACK。
-
ACK 丢失:
- 自动修复 :因为 ACK 包含的是需要接收的下一个 segment 的序列号,所以就算一个 ACK 丢失了,后面的 ACK 依然可以告诉发送端下一个需要的序列号;
- 触发发送端重传后接收端 ACK 重传 :因为发送端在计时器到时前未接收到 ACK,触发了重传,过程和 segment 丢失类似。
- 死锁 :若先前接收端关闭了发送窗口,现在接收端想开启发送窗口,于是发送了一个 rwnd 不为0 的 ACK,但是这个关键的 ACK 丢失了,此时发送端和接收端都静静等待对方的数据或指令,这就形成了死锁。解决方法 :Persistence Timer。
7. 拥塞控制(Congestion Control)
拥塞控制的目标:在网络不拥塞时,加快数据传输,在检测到网络拥塞时,减缓数据传输。
-
拥塞窗口(Congestion Window):
- 作用:和流量控制不同(通过 rwnd 设置,只控制收发两端的拥塞),拥塞窗口是为了避免网路中的拥塞(路由器的拥塞)。网络拥塞会导致包丢失等差错,从而导致数据重传,使网络拥塞问题将更加严重。
- **IP 没有拥塞控制?**理论上路由器的拥塞问题属于 IP 协议的范围,但是 IP 并没有解决这个问题,所以 TCP 需要自己解决。
- 拥塞窗口缩写(cwnd):收发窗口的大小实际同时由 cwnd 和 rwnd 属性值控制,实际窗口大小等于两个属性值中的最小值。
-
拥塞检测(Congestion Detection):
- 拥塞信号一:RTO time-out,即在计时器时间内没有收到 ACK,这种情况会触发超时重传,并被视为当前网络拥塞严重的信号(Strong Congestion);
- 拥塞信号二:Three Duplicated ACKs,即收到三个和前一个重复的 ACK(所以加上第一个总共时收到四个),这种情况会触发快重传,并被视为网络轻微拥塞的信号(Weak Congestion)
-
策略(Congestion Policies):
- 慢启动(Slow Start):
- 指数增长(Exponential Increase):传输一开始时 cwnd = 1个MSS,每当收到一个 ACK,cwnd 便会加1个MSS,因此 cwnd 会呈指数增长。
- 作用:因为不清楚网络拥塞情况,通过由小到大渐渐增大拥塞窗口显得更为合理。
- 慢启动上限(ssthresh):当 cwnd 的大小超过了 ssthresh,便进入拥塞避免阶段。
- 拥塞避免(Congestion Avoidance):
- 线性增长(Additive Increase):只有当待确认列表中所有的包都被确认后,cwnd 加1个MSS。
- 快恢复(Fast Recovery):
- 触发条件:快恢复在 TCP 中是可选的,旧版本中没有采用,新版本中采用了。当收到三个重复的 ACK 时,开启快恢复。
- 意义:在触发快重传后,通过适当增大cwnd,保持网络吞吐量。如果保持或大幅度降低原有cwnd,后面的包就发不出去了,因为前面的包还需要被确认,进而导致网络震荡过于激烈。
- 线性增长:每收到一个重复 ACK,cwnd 增长 1 个 MSS。
- 慢启动(Slow Start):
-
常见 TCP 介绍:
-
Taho:
- Taho TCP 中没有快恢复,只有慢启动和拥塞避免(老版本);
- 无差别对待两种拥塞信号;
- 支持快重传,不支持 SACK;
- 机制:
- 处于慢启动阶段,若检测到拥塞信号,则将 ssthresh 设为当前拥塞窗口的一半,并重新开始慢启动(cwnd = 1 MSS);若 cwnd 超过 ssthresh,则进入拥塞避免阶段。
- 处于拥塞避免阶段,若检测到拥塞信号,则将 ssthresh 设为当前拥塞窗口的一半,并重新开始慢启动。
- 存在问题:
- 无论哪个阶段,遇到快重传或超时重传的情况后(在恢复丢失数据包期间),不能发送新的数据包(因为 cwnd=1),这段时间的吞吐量为0。如果利用这段时间来发送适量的新数据包,可以大大的提高丢包时的传输效率。参考材料:TCP快速重传与快速恢复机制
- 另外,在快重传时,Taho会重传待确认的所有包,因为它使用的是普通的 ACK(不包含 SACK),也就不包含乱序、重复等信息,发送端不能判断上一个数据窗口中哪些包是丢失的(即不能判断三个重复 ACK 是哪些包返回的),而且因为快重传机制必须等到三个重复 ACK,如果只重传待确认列表的首个包,返回的ACK可能确认的是上一个数据窗口其中的一个包,此时快重传无法触发,会陷入两端长时间等待并最终超时。参考资料:TCP 的那些事 | 快速重传
-
Reno:
-
差别对待两种拥塞信号(新版本,目前使用最多);
-
支持快重传,不支持 SACK;
-
机制:
- 处于慢启动阶段:
- 若检测到超时,重新开始慢启动,ssthresh=cwnd/2,cwnd=1MSS;
- 若检测到3个重复 ACK,进入快恢复阶段,ssthresh=cwnd/2,cwnd=ssthresh+3MSS;
- 若 cwnd 超过 ssthresh,进入拥塞避免。
- 处于拥塞避免阶段:
- 若检测到超时,重新开始慢启动,ssthresh=cwnd/2,cwnd=1MSS;
- 若检测到3个重复 ACK,进入快恢复阶段,ssthresh=cwnd/2,cwnd=ssthresh+3MSS;
- 处于快恢复阶段:
- 若检测到超时,重新开始慢启动,ssthresh=cwnd/2,cwnd=1MSS;
- 若检测到1个重复 ACK,cwnd+=1MSS;
- 若检测到新的 ACK,进入拥塞避免阶段,cwnd=ssthresh;
- 处于慢启动阶段:
-
存在的问题:在快恢复阶段,检测到新ACK就会转入拥塞避免,Reno 设计时希望这里的新 ACK 确认的是丢失包之后以及第一个重复 ACK 之前的全部包,但可能存在新 ACK 仅仅确认了前面提到的部分包的情况,即同一数据窗口中有多个包丢失。Reno 遇到这种情况还是会转入拥塞避免,因为还存在需要重传的包,还会触发快重传,继而又进入快恢复,这样会使传输效率大打折扣,因为每一次触发快重传都会使 ssthresh=cwnd/2。
-
-
NewReno:
- 对 Reno 存在的上述问题进行了优化,当进入快恢复时,会记录此时待确认包的最大序列号,只有新 ACK 确认了这个最大序列号,才会退出快恢复。每收到一个新 partial ACK 时,RTO重置,cwnd=cwnd/2。
- 不支持SACK。
- 存在问题:一个 RTT 只能重传一个丢失包。
-
SACK:
-
性能分析:参考:TCP快速重传与快速恢复机制
- Tahoe没有快速恢复机制,在丢包后,它不仅重发了一些已经成功传输的数据,而且在恢复期间吞吐量也不高。
- 利用SACK option携带的信息,我们能够提前知道哪些数据包丢失了。NewReno每个RTT内只能恢复一个丢失的数据包,所以如果丢失了N个数据包,那么Fast Recovery就要持续N*RTT的时间,当N比较大时,这是一段相当长的时间。而SACK则没有这个限制,依靠SACK option的信息,它能够同时恢复多个数据包,更加快速和平稳的恢复。
- 当发生同一窗口多个丢包时,SACK和NewReno最终都能够较为快速和平稳的恢复过来。而Reno则经常出现超时,然后再用慢启动来恢复,这个时候Reno的表现就如同Tahoe,会造成已接受数据的重复传送。Reno恢复期间会出现吞吐量低、恢复时间长、不必要重发数据、恢复结束后阈值过低等一些问题,严重的影响性能。
-
待解决
:
- Reno 和 NewReno 在快恢复阶段是否会重传待确认的所有包?
- Reno 和 NewReno 超时重传是要重传整个待确认队列吗?快重传只重传队列中第一个包?
未完待续