目录
顺着上一篇TCP/IP 报文&协议学习_ykdsg的博客-CSDN博客 继续研究了下TCP 的相关机制,虽然也不深入。
关于TCP 的异常状态
CLOSE_WAIT:
这是一个在服务端的确认关闭连接的状态。在某些情况下我们会看到服务端有大量的 CLOSE_WAIT 存在,而导致了这一情况发生,大概率是因为客户端发起请求关闭连接后就立刻退出了把服务端晾在那了(即不往下走关闭流程)。在liunx系统中会默认等待7200秒才开始进行连接探测,默认探测大约11分钟(间隔75秒的9个探头)后,空闲连接才会被终止;可以通过某些系统参数进行优化。
sysctl -a |grep live
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
# 被探测前的空闲时间
echo 120 > /proc/sys/net/ipv4/tcp_keepalive_time
# 探测间隔时间
echo 2 > /proc/sys/net/ipv4/tcp_keepalive_intvl
# 探头数
echo 1 > /proc/sys/net/ipv4/tcp_keepalive_probes
TIME_WAIT:
这是一个在客户端最后等待关闭 2MSL 值后进行连接关闭及清理工作。在高并发场景下主动发起FIN报文段断开连接,那么此时就会出现大量的TIME_WAIT。可以通过系统参数优化。
# 打开配置
vi /etc/sysctl.conf
# 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
# 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_tw_recycle = 1
# 修改系統默认的TIMEOUT时间,这个就是MSL时间
net.ipv4.tcp_fin_timeout = 60
# 启用配置
sysctl -p
可靠传输:
RDT(可靠数据传输协议):
定义了如何构造出一个可靠传输信道:
- FSM (Finite-State-Machine, 有限状态机),这个概念定义了发送方和接收方是如何根据事件去流转状态并产生对应的动作。如:何时需要发送ACK,何时需要重传等动作,何时向上交付。
- SEQ(Sequence number,序号)与ACK(acknowledgment,确认),这个我相信大家都清楚按顺序的的确认机制。
- 校验和,校验数据的完整性。
- 定时器与ARQ(Automatic repeat request, 自动重传协议),发送方会为每一个分组数据加入一个倒数的定时器事件,定时器时间到达后仍未收到 ACK 就会立即进入超时重传事件,重新发送分组。
- 冗余数据分组(duplicate data packet),当因为网络延时等原因,发送方未能在超时时间内接收到ACK就会重传数据分组,因此接收方就会收到冗余的分组数据,协议就可以通过序号等方式甄别出已经接收过的分组。
- 流水线协议(pipelining),允许发送方一次性发送多个分组(由窗口长度限制)而无须等待 ACK 才发送另一个分组。
协议特性:
- 分组序号增加必须唯一;
- 协议的发送方和接收方需缓存多个分组。发送方应当能缓存那些已发送但没有确认的分组。接收方需要缓存那些已正确接收的但未交付的分组。
- 差错恢复(处理丢失、损失及延时过大的分组)。两种滑动窗口协议, 窗口协议会发送在窗口长度范围内的所有分组,并等待分组确认,当前面的分组确认完成后就会向前移动,故称为滑动窗口。
- GBN(Go-back-N,回退N步),字母N常被称为窗口长度(window size)。发送方会一次性发送多个分组,如 1 ~ 5个分组,1~2 收到 ACK,但是 3 分组都未收到ACK,发送方会一直等待 3 分组后面的所有分组直至超时重传(重传后面所有分组),在这过程中即使后面的分组到达也会被丢弃,这种方式叫做累积确认。它的弊端是在延迟网络下耗费大量的无用功重传。
- SR(Selective Repeat, 选择重传)让发送方仅重传那些它怀疑在接收方出错的分组而避免了不必要的重传(按需重传)。它也需要通过窗口来限制流水线中未完成、未被确认的分组并且缓存它们(即使失序到达),最后逐个地确认。它的弊端是发送方和接收方要各自维持一个窗口,但是一旦出现ACK分组丢失了,两边的窗口滑动时就会出现不一致。
TCP 确认机制
发送方发送segment 1,接收方收到之后需要ACK确认,这个时候ACK = 2 ,意思是说,segment 1已经成功接收,寡人等待segment 2的到来。如果发送方发送的segment是“1、2、3、4、5、6”,接收方应该收到“2、3、4、5、6、7”,当接收方接到ACK =7,就意味着编号7前面所有segment都成功接收,在这里就是“1、2、3、4、5、6”都成功接收了。
问题是ACK的次数有点多,接收方发送一次ACK耗费CPU,接收方接到一次ACK也耗费CPU,另外ACK报文对网络也是一个小小的负担。于是RFC建议优化ACK方法,每收到2个segment,发送一次ACK,这样就会将ACK数目减半。那么接收方可以这样ACK :“3、5、7。。。”,当发送方接收到ACK=7,表明segment 1、2、3、4、5、6已经到了。
问题来了,如果发送方只有一个segment发送,按照以上规则,接收方接收2个segment才发送一个ACK,既然只有一个segment,那接收方的ACK会不会永远也发不出?这里会造成通信的死锁(deadlock)。TCP会启动一个定时器,只要闹钟响了(timeout)而第二个segment还没有到,这时就顾不了那么多,直接发ACK就好。TCP 慢启动(slow start)就是先发segment 1,直到接到对方ACK了,才会发segment 2、3。有了这个定时器,就可以避免TCP慢启动期间的通信死锁。
假如发送方发送segment “1234567”,接收方接到的是“1235”,接收方接到5的那一瞬间,知道4有可能丢了,也有可能乱序了,至于是哪种情况,接收方无从知晓。
但发送方对于segment 4的状态是完全空白的,既然接收方知道多一点,就把这个信息同步给发送方。
通过什么方法把消息同步给发送方呢?
ACK =4
有同学会迷惑不解,明明收到的是5,应该ACK= 6,为何这里是4?
ACK=6 是什么意思?
表示 “12345”都成功接收,问题是4收到了吗? 没有啊!所以只能ACK=4。
稍后,接收方所有接收到的segment为 “12356”,接收方如何做?
ACK=4
再稍后,接收方所有接收到的segment为 “123567”,接收方如何做?
ACK =4
发送方一直记录自己重复收到某个ACK的次数,Duplicated ACK(4) =3,此时发送方意识到segment 4有可能丢了,此时应该立马主动将segment 4重传出去,而不要被动等待segment 4的重传定时器超时再重传segment 4。
由于这种依赖外界刺激的重传方法比超时重传更快、更及时,美其名曰:快速重传(Fast Re-transmit)。
当重传的segment 4到达接收方时,终于将不连续的segment流 “123567” 修复(Restore)成连续的segment流 “1234567”,通常称这种主动修复字节流的方法为快速修复(Fast Restore)。两者合在一起统称Fast Re-transmit / Fast Restore。
上文说的外界刺激,就是发送方连续收到三次duplicated ACK,立马启动Fast Re-transmit / Fast Restore机制。
如果上文中的接收方所有接收到的segment 不是“123567”,而是”123564”,此时就是乱序的发生,接收方会重新排序成”123456” ,接收方发送ACK =7即可,segment是连续的,无需修复。
在这种情况下,发送方只接到了两次duplicated ACK =4,无需启动Fast Retransmit / Fast Restore机制,因为接收方已经成功修复。
TCP可靠传输
- 在分组传输时使用了SEQ 和 ACK 来构建出有序的字节流以及校验和保证数据的完整性。
- 在分组出现丢失问题时,TCP采用了超时以及重传机制来处理这类问题,这就涉及刚刚提到的定时器和重传协议;它间隔侦测分组的RTT(Round trip time,往返时间)来估算出一个超时时间,而且超时时间需要比RTT大。
- TCP的流水线协议,在窗口内一次性发送多个分组报文,接收方只要是正确接收的报文段(失序的)都会被缓存起来,然后会被SACK(selective acknowledgment, 选择确认)而不采用累积确认,最后再结合SR机制来跳过已经被确认的分组,只重传未被确认的分组。
TCP的滑动窗口的作用:一种是提供可靠性(通过重传方式确保所有分组的ack),还有另一种是提供流控特性(窗口的大小来控制)
流量控制
在TCP中滑口的大小是取决于rwnd(接收窗口)和另一个cwnd(拥塞窗口),当哪个值小哪个值就是滑动窗口,即TCP中window的大小。
rwnd:TCP 为了消除发送方使接收方缓存溢出的可能性,因此需要对双方流量做一个控制达到一个速度匹配的状态,即发送方的发送速率与接收方应用程序的读取速率相匹配。所以TCP在连接两端的发送方各自维护一个rwnd,rwnd的值是由接收方在发送ACK时放入window字段发送给发送方,而且这个值是动态的它的大小是取决于接收方读取缓存数据的速度。
cwnd:在发送方都维护的这么一个窗口变量,其值取决于网络的拥塞程度而且是动态变化,它会对发送方进行一个发送的流程限速。
拥塞控制
拥塞控制就是为了尽量让网络设备接近它最高核载进行工作(压榨它的极限),但又不至于它出现过载的情况。
TCP的拥塞控制四种算法:慢启动、拥塞避免、快速重传、快速恢复
慢启动
假设当前发送方拥塞窗口(cwnd)的值为 1个 MSS值,当传输了第一个报文段并且接收到ACK后,cwnd就会增加 1个 MSS (现在window size 是 2MSS)。那么下一次就会同时发送2个报文段,等待接收到2个报文段后就会再增加2个 MSS。这么循环往复直到cwnd的值变为16,16这个值我们称为ssthresh(慢启动阀值),其实它也是一个动态值等下一阶段拥塞避免时会动态调整。
拥塞避免
经过一番慢启动之后我们便进入拥塞避免,此时的cwnd变成变成了线性加 1,如同时发出16个报文段并收到16个报文段后cwnd才会+1,也就是cwnd变为 17 MSS。
现在假设cwnd是24MSS,在下次报文传输中发送方收到了3个冗余ACK,在此之前我们说过TCP如果收到3个冗余ACK就会进行快速重传。并且ssthresh值就会调整为12MSS (cwnd/2)即cwnd的一半,最后cwnd设置为 1 MSS 并进入快速恢复或慢启动 阶段。
快速重传:
当发送方等待到3个冗余 ACK 的时候就会立刻重传而无需等待分组超时。那为什么是3个冗余ACK?因为 ACK 的发送是由接收方决定什么时候发的这个ACK,所以在延迟网络中会产生一连串的冗余ACK, 这些ACK的产生和前面提到FSM机制有联系,在某个状态下通过不同的事件来决定。(如:序号大的分组先到达了,但期望的分组还没到,这时接收方就会立刻发送一个期望分组的ACK)。
快速恢复:
快速恢复在TCP里面是一个推荐组件,但其实已经纳入在新版的TCP里面而且不断的迭代更新。我们现在来看看最早期的拥塞控制(Tahoe 和 Reno)算法,快速恢复这个机制在Reno算法中才出现的,后面的算法也会沿用这个概念。
这两种算法在对于丢包事件判断都是以RTO(retransmission timeout,超时重传)和冗余ACK为条件,但两者的做法是不同的;
- Tahoe:如果收到三次重复ACK——即第四次收到相同ACK,Tahoe算法则进入快速重传,将慢启动阈值改为当前拥塞窗口的一半,将拥塞窗口降为1个MSS,并重新进入慢启动阶段。由于一丢包就一切重来,导致cwnd又重置为1,十分不利于网络数据的稳定传递。
- Reno:如果收到三次重复ACK,Reno算法则进入快速重传,只将拥塞窗口减半来跳过慢启动阶段,将慢启动阈值设为当前新的拥塞窗口值,进入快速恢复。
网上找的示意图:
TCP的公平退让原则其实就是受到拥塞控制的影响,因为只要出现丢包就会大幅度降低速率。因为拥塞窗口总体看来其实“加性增,乘性减”,所以在多个TCP端在传输的时候就会通过丢包的方式往复的触发拥塞控制,最终达到让速率稳定在一个不丢包的状态。