面试准备(1)--TCP协议

TCP协议简介

简介

TCP协议栈位于IP层(网络层之上),应用层之下,是主机之间可靠传输的最基础网络协议。一个单独的TCP数据包被称为报文段,下图展示了其在不同层级的协议下数据包的覆盖结构:

1
[链路帧| IP头 | TCP头 |  应用层协议的数据	]

TCP是需要建立连接的全双工协议(两边可以互相发送和接收数据),为了实现数据的可靠传输,TCP在其头部中添加了大量的元数据信息(包括基础的的20字节以及附加的其他部分),下面挑重点进行叙述

1byte 2byte 3byte 4byte
源端口号 (2B) 目的端口号(2B)
序号SEQ (4B)
确认号ACK (4B)
首部长度 保留位以及标志位 接收窗口号 (2B)
校验和 (2B) 紧急数据指针 (2B)

懒得画图了,就随便整个表大概看看的样子。

  1. 源端口号 + 目的端口号 由于TCP是点对点的,因此两个主机之间可能存在多个TCP连接,为了分辨,需要标识具体通信的是哪两个进程,端口号就是和进程一一绑定的东西(端口号范围是0~65535,因此2Byte最好)
  2. 序列号SEQ和确认号ACK:由于TCP是全双工通信,因此才有两个序号,分别是作为发送方视角和作为接收方视角的两个序号,其中:
    1. SEQ:当前TCP报文段发送的一段数据的第一个字节在整个数据流中的起始序号(如数据总长1MB,分1024次发送,每次发送1024字节,那么SQL一次为0、1024、2048,….)
    2. ACK:希望从对方收到的下一个字节的序号(如数据总长1MB,对方每次发送1024字节,则我方的ACK号分别为0,1024、2048)
  3. 首部长度。元数据,在部分特殊情况下TCP不只有20字节,可能会在标准头的后面附加一些元数据,该部分用于确认附加元数据的长度
  4. 标志位(一共六个),由于TCP协议的主机是一个状态机,而且TCP报文段根据用途的不同分为不同的种类,这里几个标志位用于标记TCP报文段类型
  5. 接收窗口号用于流量控制,下面详述
  6. 校验和 头部的CRC32校验码,用于校验报文段是否发生差错

建立连接

前面已经说了TCP协议是需要建立连接的,因此建立连接需要发特殊的报文,前前后后一共需要发送三个报文(主动方2个+被动方一个),一般称之为三次握手,其中只有第三个数据包才能携带实际的数据,下图展示了连接建立过程:

sequenceDiagram
    A->>B:SEQ=X, SYN
    B->>A:SEQ=Y, ACK=X+1, SYN
    A->>B:SEQ=x+1, ACK=Y+1 [data]

其中SYN表示双方的建立连接的应答包(这两个包不包含实际的数据),X,Y是两边数据流的起始序号(不用0是防止包残留问题),两个ACK是初始化期望序号(A假装之前收到序号为X的报文段,因此下一个期望接收的包就是X+1, B和Y同理)

断开连接

断开连接也是类似的过程,首先由一方提出断开连接,然后另一方答应即可,由于是双工的,因此两边分别要请求断开和回应,一共四个数据包,相关序列图如下所示:

sequenceDiagram
    A ->> B: FIN
    B ->> A: ACK
    B ->> A: FIN
    B -->> A: wait
    A ->> B: ACK
    A -->> B: wait

第二第三个数据包之间要间隔一段时间是因为可能由A到B的数据包在ACK之后发送到。同样的,A再发送ACK后要等一段时间才能完全关闭连接

此时主动方可以由一个状态机来描述:

<pre class="mermaid">graph TD A(closed) -- 请求建立连接 SYN --> B(SYN_SEND) B --接受对方回应的SYN,并发送携带数据的ACK --> C(Established) C --数据传输--> C C --发送FIN--> D(FIN Wait 1) D--接受对方的ACK--> E(FIN Wait 2) E --接受对方的FIN,发送ACK--> F(Time Wait) F --等待---> A

同理被动方也可以用一个类似的状态机来描述(这里不再画出,和上面那个很像)

复杂的可靠传输

由于TCP下层的IP协议仅提供基本的数据传输服务(不保障一定送达),因此TCP服务需要自己通过协议来实现可靠传输

无丢包,无延迟的理想情况

在理想情况下,A和B建立连接后都是按照A向B发送数据包(SEQ),同时应答(ACK);同时B向A应答(ACK),然后发送新的数据(SEQ)。

sequenceDiagram
    A ->> B: SEQ = X1, ACK = Y1
    B ->> A: SEQ = Y1, ACK = X2
    A ->> B: SEQ = X2, ACK = Y2
    B ->> A: SEQ = Y2, ACK = X3

重传

当A向B发送数据包后一段时间TI未收到回应,则会认为B发送的数据包太慢。A会间隔2TI重新发送相同的数据包,然后4TI,以此类推。T的值通过网络情况估算得到,见延迟估计一节,初始值一般是0.75s.

如果A收到连续3个相同序号的ACK,那么A认为自己有数据包丢失了(即出现跳包,比如B收到序号为1,2,3,4,5的数据包,但是2丢失了,因此一直ACK序号2),这时候A会无视数据包2的超时,直接重传数据包2,称之为快速重传。

差错恢复:GBN or SR

对于部分数据包丢失,如1,2,3,4,5只发送了1,4,5而2,3丢失。那么这时候有两种策略:

  1. GBN:只记录累计确认号1,即仅仅认为现在该发送2了,不知道3,4,5是否送到,因此发送方会重传2,3,4,5
  2. SR:使用复杂的缓存机制缓存发送的1,4,5,只要求重传2,3

这两种方法各有优缺点,TCP是两者的混合体,它只会重传2,并等待2的恢复,2到了后就发送3,以此类推,如果发送2,3之前提前收到5的确认,那么5也不会重传(这里比较模糊,因为不桶的TCP协议栈的实现也不太相同。)

流量控制机制

由于流量控制是双向的,而TCP双方又是相对等价的,这边以A作为发送方,B作为接收方为例描述流量控制机制(B作为发送方反过来即可)。

A、B两个主机都有各自的接受缓存区,由于这里只说明一方,因此仅考虑B的缓存区域RB

B向A发送的ACK数据包中的窗口字段会写上其缓存区RB的剩余空间大小RWND ,即缓存群RB的总大小减去缓存区中已有的数据,A在收到RWND这个数据后会控制下一次发送的总数据量不超过RWND的大小。

延迟估计

由于网络环境是动态的,且恢复重传等机制需要对RTT(报文从发出到收到确认数据报所需的总时间)进行估计,因此对TCP协议来说对数据包传输时间T进行采样是必要的。TCP会定期对网络环境的RTT进行测量,如1s一次。设一次连接中第i次采样的RTT为R(i)。RFC中建议使用如下的公式来更新下一个数据包的预期RTT:
$$
EstimatedRTT_{i+1} = (1- \alpha) * EstimatedRTT_{i} + \alpha R(i)
$$
即采用加权平均的方法,a的建议值为0.875

此外,TCP协议还会测量RTT的变化率DevRTT用以测量网络的波动情况:
$$
DevRTT_{i+1} = (1 - \beta) DevRTT_{i} + \beta |R(i) - EstimatedRTT_{i} |
$$
在此基础上,超时重传的初始时间限制TI可由
$$
TI = EstimatedRTT + 4 * DevRTT
$$

计算得到,加上devRTT就类似数学中的一阶导数,4是为例留一些余量。