TCP

ack是接收端期望接收到的数据序号,等于已接收到序号+1。

1. 有连接的:建立和终止

三次握手

  1. SYN ACK+SYN ACK
  2. 被动连接端的ACK通过SYN捎带
  3. 交换双方的: 初始序列号 / MSS / 窗口大小(?)

四次挥手

  1. FIN ACK FIN ACK
  2. 单向关闭
  3. 半关闭。通过半关闭向另一端告知数据发送完毕。

状态变换:

Alt text

Alt text

主动关闭方的状态:

  • FIN_WAIT_1 :发送 FIN 后等待对方的 ACK
  • FIN_WAIT_2 :收到对方对自己 FIN 的 ACK 后等待对方的 FIN
  • TIME_WAIT :收到对方的 FIN 并发送 ACK 后进入该状态

MSS

根据物理层的MTU计算,避免IP层分段

2MSL(TIME_WAIT)状态

  1. 主动关闭端在发送对FIN的最后一个ACK后,等待2MSL时间。MSL, max segment lifetime,一个segment在网络中存活的最长时间。

原因:

  • 被动关闭端可能没有收到ACK而重发FIN,如果不等待,这个重发的FIN可能将新的连接关闭掉;
  • 网络中可能还有在传输的旧的重传报文段,可能被当做新连接的数据。

等2MSL:一个ACK+FIN的最大时间,如果在这个时间段内没有收到重发的FIN,认为对方已经收到ACK正确关闭了。

  1. 处于2MSL状态的端口不能被使用。客户端每次连接随机选一个端口,因此没有影响;而服务器端固定端口,因此在服务器端主动关闭,必须等待一段时间才能重新绑定到原端口。设置选项SO_REUSEADDR可以取消这个设定。
  2. 在此期间拒绝任何收到的数据。

RST

  1. TCP模块会在自己认为的异常时刻发送RST;
  2. 不会对RST发ACK,立即丢弃缓冲区中的数据;
  3. 用RST而不是FIN关闭连接,称为abortive release(相对orderly release);api会通知应用层连接是异常关闭的。
  4. 常见的产生时机:
    • 连接到/发送数据到没有使用的端口,收到RST
    • 关闭连接时接收缓冲区还有数据没被消费,将发送RST
    • 向一个已经close()的socket发送数据,将收到RST —- close和shutdown虽然都是发送FIN关闭连接,但前者的语义是关闭读写,后者是关闭写,但依然能读。

同时打开

两端都是主动打开。SYN SYN+ACK / SYN SYN+ACK。4个segment。

Alt text

同时关闭

两端都是主动关闭,FIN ACK / FIN ACK,两端最后都进入2MSL状态。

Alt text

并发TCP服务器

Listen状态的socket在接收连接请求后,内核会创建一个新的established状态的socket;原socket依然是listen状态;二者在服务器端使用同一个端口。

backlog队列

  1. 服务器端的TCP模块内有一个连接队列,保存所有已被TCP接受(三次握手完成),但未被应用层接受的连接;队列的长度由应用层指定,称为backlog;
  2. TCP连接的建立应用层得到一个已经建立的连接,是一个生产者消费者模型;应用层无法拒绝客户端的连接请求。
  3. 客户端发起连接成功后,该连接在服务器端可能只在backlog队列中,此时客户端如果发送数据,将被缓存在TCP接收buffer中;
  4. backlog队列满后,不会回应发来的FIN,让客户端重试。如果backlog队列一直满,客户端终将超时。

2. 滑动窗口

urgent mode

URG标志位为1时,说明报文段是紧急数据,需要尽快被应用进程接收和处理。普通数据在接收端是按序交付给应用进程的,紧急模式在两端间建立了一个独立于普通数据的逻辑信道,接收端通常将普通数据和紧急数据分开存放,二者之间不遵守有序的规则,允许越过普通数据直接读取后面的紧急数据。这种数据也被称为”带外数据“(out-of-band data),但实际上,带外数据和普通数据是共享物理信道传输的。

delayed ack

接收端不立即发送ack,而是等待一段时间,和数据一起发送,或和另一个ack合并成一个发送。

滑动窗口的工作方式

窗口通知:
发送端维护发送窗口大小(不在包中传输),接收端在ACK中告知接收窗口大小;

发送窗口初始是发送缓冲区大小,接收窗口初始是接收缓冲区大小;缓冲区决定窗口的最大值;

发送窗口一般包括3个部分,从左到右:

  1. 已发送但未收到ACK的数据
  2. 可以立即发送的数据
  3. 空闲空间;

接收窗口就是接收缓冲区还剩多少空间,接收端处理能力越强,从缓冲区提取数据的速度就越快,接收窗口就越大;

发送窗口大小由接收窗口决定,发送端收到ACK后:

  1. 丢弃缓存中对应的数据,左沿向右移动;(收缩)
  2. 根据ACK告知的接收窗口看是否需要移动右沿;已发送未 ACK 的数据 + 可立即发送数据 + 空闲 = 接收窗口;(扩张)

作用:

  1. 提高效率,可以同时发送多个数据;
  2. 流量控制,适配不同处理能力的发送端和接收端

最优窗口大小(即发送/接收缓冲区大小)的计算:

  • 尽量将两端之间的信道填满;
  • 填满时,在信道上传输的数据 = 带宽(数据传输速度) * RTT,两个缓冲区应至少为这个大小

问题1:零窗口

接收端接收缓冲区满时,ACK中接收窗口为0,阻止发送端发送数据。发送方需要在接收方缓冲区空出来时得到通知,因此在发现零窗口后会进行窗口探测,即定时发送含有1字节内容的segment,通过其ACK查询接收方的接收窗口。

问题2:糊涂窗口综合症 (silly window syndrome)

现象:大量的小segment被传输(payload太小),造成网络利用率低下

发送端和接收端都有可能引起这种情况:

  1. 发送端每次只发送少量数据;
  2. 接收端的处理能力不够或应用层没有即时从接收缓冲区中取数据,接收窗口一直很小,发送端只能发小segment。

解决:
* 发送端: nagle算法 *

  1. 只针对小segmentstop-wait协议,大segment不受影响;
  2. 在前一个 小segment(小于MSS)的ack未到来前,缓存并合并其他要发送的小segment;
  3. 如果ack回来的很快,合并不了多少数据;
  4. ack通常是delayed,会导致数据发送的延时;不适合实时性的应用(可以取消);
  5. 目的是减少segment数目。

* 接收端: *
在接收端,当接收窗口小于一定阈值(如MSS一半)时,无论是数据确认ACK,还是对窗口探测的回应ACK,都宣告接收窗口为0,阻止发送端发送小报文段。

3. 可靠性:超时重传

每个segment发送后都会有一个计时器负责接收ACK,如果超时,则重新发送该segment。

首先要预测当前发送的segment的RTT:
R = αR + (1-α)M,M是测量到的RTT,R是估计的RTT。

然后计算RTO(Retransmission TimeOut):
简单版:RTO = R*β
复杂版,将RTT的估计值,RTT的偏差的估计值也考虑在内:

Err = M - R  #Err是实际RTT和估计RTT的差值
R = R + g*Err = (1-g)*R + g*M #计算估计RTT,和之前的算法一模一样
D = D + h*(|Err| - D) # 计算估计偏差,”估计偏差“指的是 估计RTT 和 真实RTT  的差值。这个算法和估计RTT的计算方法是一样的
RTO = R + 4D #

多次重传时RTO的exponential backoff(指数退避)

4. 顺序性

数据乱序到达,接收端可以不丢弃,而是缓冲起来,组装成有序后交付给应用进程。

快速重传

接收端在接收到乱序segment时,用重复ACK提醒发送端空洞的存在,使之发送丢失数据,保证可靠性。
例:发送端发送了5/6/7/8/9,假如接收端按顺序接收5/9/7/8,6丢失了,则在接收到9/7/8后均会发送ACK=6;当发送端收到3个重复的值为6的ack时认为6丢失了,打断6的超时timer并立即重新发送。
为什么阈值是3呢?这是一个权衡,重复ACK可能是由段丢失引起的,但也可能只是段乱序到达了而已,比如5/7/8/6/9,在7和8处会发送两个重复的ACK,但是紧接着6就到了,接收端重组后一切正常,此时也不会触发发送端的快速重传

优点:接收端利用空洞后的segment比超时更快地检测到丢失片段,效率更高。

5. 拥塞控制

问题:堵塞 –> 数据丢失 –> 超时重传 –> 更堵塞

解决办法:

  1. 探测堵塞;
  2. 发现堵塞时控制自己发送数据的速度。

探测堵塞

上文已经提到了各种推测segment丢失的办法:超时 / 重复ACK,一旦出现段丢失则认为堵塞

但是注意,这两种情况下的阻塞程度不一样:

  1. 超时:严重阻塞,发送了若干段但是一个ACK都没回来;
  2. 重复ACK:轻微阻塞,或者没有阻塞。因为后续的段到达了,只有中间某些段没有到达。

控制速度

单纯的滑动窗口中,发送方通过接收方通告的接收窗口大小调整发送窗口,从而控制发送速度;为了拥塞控制,TCP还会在发送端维护一个拥塞窗口真实发送窗口= min(接收窗口,拥塞窗口)

拥塞控制有3个阶段:

  1. 慢启动(slow start)
  2. 拥塞避免(congestion avoidance)
  3. 快速恢复(相对快速重传而言)

// cwnd: 拥塞窗口大小

慢启动

系统从slow start阶段开始。在这一阶段cwnd被置为1,只能发送1个segment;每收到1个ack,都给拥塞窗口+1,即下次可以发送2个段,下下次可以发送4个段,即乘性倍增。

拥塞避免

当cwnd增长到某个阈值ssthresh(slow start threshhold)时,拥塞窗口进入congestion avoidance状态。此时发送窗口在每个窗口中所有片段都传输完毕后,将拥塞窗口+1,线性缓慢增长。

在congestion avoidance状态下,一旦出现探测到拥塞,ssthresh 立即更新为当前cwnd的一半,随后分两种情况:如果是ACK超时检测到的拥塞,则进入slow start;如果是重复ACK,则进入快速恢复阶段

快速恢复

  1. cwnd增加3个segment的长度(加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络),以后每收到一个重复的ACK,就再增加1个segment长度。这是因为一个重复ACK说明有一个段已被发送出去,可以发送另一个段了;

  2. 当收到一个非重复ACK时,说明对方的数据接收完整了,发送端将cwnds复原为sthresh(已经减半了),重新回到congestion avoidance状态。

分开处理的原因正如前所述,两种情况表达的拥塞程度不同。对超时表达的严重堵塞,需严厉地立刻降低发送速率,因此直接进入慢启动阶段;对重复ACK,说明双方仍然有数据流动,我们不希望执行slow start突然减少数据流。

如果在slow start阶段就出现丢失数据,则slow start立刻开始,ssthresh更新为cwnd的一半。

Alt text

总体来说,拥塞窗口的状态变迁是一个试探性的过程。slow start阶段起步速度低,不太可能出现拥塞,因此发送速度可以快速攀升;congestion avoidance阶段速度接近饱和,采用更保守的方式,速度缓慢增长逼近极限。一旦探测到拥塞,就将slow start的拐点降低为当前拥塞窗口的一半,且如果是严重堵塞,则立即重置,避免网络负担;如果是轻微拥塞,则快速恢复。

Loading Disqus comments...
目录