TCP 与 UDP

TCP 特点:

  • 面向连接的:发送数据前需要建立连接
  • 点对点
  • 可靠的
  • 全双工的
  • 面向字节流

    UDP 特点:

  • 无连接的

  • 不可靠
  • 面向报文的
  • 没有拥塞控制
  • 支持一对一、一对多、多对一、多对多
  • 首部开销小:只有 8 个字节,TCP的首部 20 个字节

报文格式:

image.png

  • 源端口与目的端口(各16位)
  • 序号(32位):seq number,范围是 [0, 2^32 - 1],循环使用
    • 发送端的序号,seq2 = seq1 + (seq1 的数据长度)。
    • 如果 SYN = 0 且 seq1 的数据长度为 0,则 seq2 = seq1。SYN = 1 时 seq2 = seq1 + 1。
  • 确认号(32位):ack number
    • 期望接收端下一个发送的报文的序号
  • 首部长度(4位):指明数据从那里开始,因为报文头的长度是可变的。单位是字节
    • 报文头的最大长度:2^4 * 4 字节(32位) = 60 字节
    • 最小长度:5 * 4 字节 = 20 字节
  • 保留(6位):未使用,必须全部置 0
  • 控制位(6位)
    • URG:为 1 时紧急指针字段才有效。表明此报文包含紧急数据,应尽快传送。
    • ACK:为 1 时确认号字段才有效。为 0 时表示忽略确认号字段。
      • TCP 规定,在连接建立后,所有传送的报文段都必须把 ACK 置 1。
    • PSH:推送。为 1 时表示接收方应该尽快地向上交付报文,而不是等缓存满了再交付。
    • RST:为 1 时表示连接错误,必须释放连接。还可以用于拒绝非法地报文段和拒绝连接请求。
    • SYN:在建立连接时用来同步序号。
      • 当 SYN = 1 而 ACK = 0 时,表明这是一个连接请求报文段
      • 当 SYN = 1 而 ACK = 1 时,表明这是一个连接接受报文段
    • FIN:用于释放一个连接。为 1 时表示发送方已经没有数据发送了,并要求释放运输连接。
  • 窗口大小(16位):发送方接收窗口值。表示从确认号开始,本报文的源方可以接受的字节数。
    • 因为接收方的缓存空间时有限的,所以需要窗口
  • 检验和(16位):检验的范围包括首部和数据,计算时需要加上 12 字节的伪首部
  • 紧急指针(16位):URG = 1 时才生效,它指出紧急数据的字节数(紧急数据之后是普通数据)
    • 即使窗口为零时,也可发送紧急数据
  • 选项(32位):最长 40 字节。
    • 最常见的可选字段是最长报文大小(MSS,Maximum Segment Size)
    • 指明本端所能接收的最大长度的报文段。
    • 每个连接方通常都在通信的第一个报文段(SYN = 1 的两个报文)中指明这个选项
  • 数据(32位):可选的。
    • 在一个连接建立和终止时,双方交换的报文段仅有 TCP 首部。
    • 在处理超时的许多情况中,也会发送不带任何数据的报文段。

握手与挥手

三次握手:

image.png

B 的 TCP 服务器进程处于 LISTEN 监听状态,等待客户端的连接请求。
客户端 A 主动打开连接,而服务端 B 被动打开连接

第一次握手:

  • 客户端 A 发送 SYN = 1,初始序号 seq mumber = x 的连接请求报文
  • TCP 规定,SYN 报文段不能携带数据,但是要消耗一个序号
  • 发送完后,客户端 A 进入 SYN-SENT(同步已发送)状态。

第二次握手:

  • 服务端 B 收到请求报文后,如同意建立连接,则向 A 发送 SYN = 1,ACK = 1,初始序号 seq number = y,确认号 ack number = x + 1 的连接确认报文
  • 同样的,这个 SYN 报文段不能携带数据,但要消耗一个序号。
  • 发送完后,服务端 B 进入 SYN-RCVD(同步收到)状态。

第三次握手:

  • 客户端 A 收到请求确认报文后,还需要向 B 给出确认。
  • 发送 SYN = 0,ACK = 1,seq number = x + 1,ack number = y + 1
  • TCP 规定,ACK 报文段可以携带数据。但如果不携带数据则不消耗序号,这种情况下,下一个报文的序号仍是 seq number = x + 1
  • 发送完后,客户端 A 进入 ESTABLISHED(已建立连接)状态
  • 服务端 B 收到此报文后,也进入 ESTABLISHED(已建立连接)状态

当前报文的序号 = 上一个发送的报文的序号 + 上一个发送的报文的数据长度(SYN 报文没有数据,但序号+1)

Q:为什么需要第三次握手?
A:为了防止已失效的连接请求报文突然又传送到了 B。
当 A 发送的第一个连接请求报文由于网络延迟超时,导致 A 没收到 B 的请求确认报文而重发,在和 B 建立了连接后。数据传输完毕,就释放连接。此时第一次发的连接请求报文又传到了 B ,然后 B 会再发送一个连接确认报文给 A,如果没有第三次握手,那么 A 并不会理睬 B 的确认,此时 B 就建立第二条连接了,并等待 A 发来数据,从而浪费 B 的资源。


四次挥手:

image.png

由于 TCP 连接时全双工的,所以每个方向的数据传输通道都需要关闭。TCP 的半关闭。

一开始,两边都处于 ESTABLISHED(已建立连接)状态
第一次挥手:

  • 客户端 A 发送 FIN = 1,seq number = u 的连接释放报文
  • 序号 u 等于 A 发送的上一个报文的序号 + 报文的数据长度
  • TCP 规定,FIN 报文即使不携带数据,也要消耗一个序号
  • 发送完后,客户端 A 进入 FIN-WAIT-1(终止等待1)状态。

第二次挥手:

  • 服务端 B 收到连接释放报文后,发回确认报文,ACK = 1,序号 seq number = v,确认号 ack number = u + 1。
  • 序号 v 等于 B 发送的上一个报文的序号 + 报文的数据长度
  • 发送完后,B 进入 CLOSE-WAIT(关闭等待)状态。
  • 此时应该通知高层应用程序进程,A 到 B 方向的连接就释放了,TCP 处于半关闭状态。
  • A 收到 B 的 ACK 报文后,进入 FIN-WAIT-2(终止等待2)状态。
  • 半连接状态下,A 不可以发送数据给 B,而 B 可以发送数据给 A。

第三次挥手:

  • 若服务端 B 已经没有数据要发送给 A 了,其应用进程就通知 TCP 释放连接。
  • 服务端 B 发送 FIN = 1,ACK = 1,seq number = w,ack number = u + 1 的连接释放报文。
  • 序号 w 等于 v 加上中间发送的报文的数据长度,如果中间没有再发送报文,则 w = v。
  • 发送完后,服务端 B 进入 LAST-ACK(最后确认)状态

第四次挥手:

  • A 收到 B 的连接释放报文后,也需要发回确认报文,ACK = 1,seq number = u + 1,ack number = w + 1。
  • seq number = u + 1 是因为第一次挥手的 FIN 报文占用一个序号。ack number 同理。
  • 服务端 B 收到报文后,进入 CLOSED 状态。
  • 发送完后,客户端 A 进入 TIME-WAIT(时间等待)状态,此时 TCP 连接还未释放,需要等待时间等待计时器设置的 2MSL(Maximum Segment Lifetime,最长报文段寿命),A 才进入 CLOSED 状态。

问题:
Q:为什么需要四次挥手?
A:因为 TCP 连接是全双工的,每个方向的连接的断开都需要一个连接释放报文和对应的响应报文。

Q:为什么客户端 A 需要 TIME-WAIT 等待 2MSL?
A:一、保证 A 发送的最后一个 ACK 报文能够到达 B。如果这个 ACK 报文丢失,处于 LAST-ACK 状态的 B 收不到确认,就会超时重传 FIN + ACK 报文,此时处于 TIME-WAIT 的 A 就能收到这个 FIN + ACK 报文,A 也重传对应的 ACK 报文,并重置时间等待计时器,使得最后 A 和 B 都能处于 CLOSED 状态。
二、防止前面三次握手中已失效的连接请求报文段出现在本连接中。A 在发送完最后一个 ACK 报文后,再经过时间 2MSL,就可以使本连接持续的时间内产生的所有报文段从网络中消失。


流量控制:

问题:发送者发送数据过快,接收者来不及接收,就会有分组丢失。

滑动窗口协议(连续 ARQ 协议)实现:接收方返回的 ACK 中会包含自己的接收窗口(剩余空间)的大小,来控制发送方的速率。

死锁问题:发送者先收到一个窗口为 0 的 ACK,便停止发送。当接收者处理完数据,发送窗口不为 0 的 ACK,但是这个 ACK 丢失了,那么两边都在等待。
解决:持续计时器。每当发送者收到一个零窗口应答,就启动改计时器,时间一到便主动发送报文询问接收者的窗口大小。如果接收者仍然返回零窗口,则重置计时器。如果返回非零窗口,则继续发送数据,并关闭计时器。


拥塞控制:

拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。
https://zhuanlan.zhihu.com/p/59656144
https://zhuanlan.zhihu.com/p/37379780

常用方法:

  • 慢开始 Slow Start
  • 拥塞避免 Congestion Avoidance
  • 拥塞发生
  • 快速恢复 Fast Recovery

发送方维持一个变量叫做拥塞窗口 cwnd(congestion window)

RTT(Round Trip Time):一个连接的往返时间,即数据发送时刻到接收到确认的时刻的差值;
RTO(Retransmission Time Out):重传超时时间,即从数据发送时刻算起,超过这个时间便执行重传, RTO协议实现值最小 1s

慢开始:
cwnd 的大小是以字节为单位

  1. 连接建好的开始先初始化拥塞窗口 cwnd 大小为 1,表明可以传一个 MSS 大小的数据。
  2. 每当收到一个 ACK,cwnd 大小加一,呈线性上升。
  3. 每当过了一个往返延迟时间 RTT(Round-Trip Time),cwnd 大小直接翻倍,乘以2,呈指数让升。
  4. 还有一个 ssthresh(slow start threshold),是一个上限,当 cwnd >= ssthresh时,就会进入“拥塞避免算法”(后面会说这个算法)**


拥塞避免算法:**

  1. 收到一个 ACK,则 cwnd = cwnd + 1 / cwnd
  2. 每当过了一个往返延迟时间 RTT,cwnd 大小加一

拥塞状态:
一般来说,TCP 拥塞控制默认认为网络丢包是由于网络拥塞导致的,所以一般的 TCP 拥塞控制算法丢包为网络进入拥塞状态的信号。
对于丢包有两种判定方式:

  • 一种是超时重传 RTO [Retransmission Timeout] 超时
  • 另一个是收到三个重复确认ACK
    • 快速重传:接收方收到三个以上重复的 ACK,则立即重传,而不需要等重传定时器超时。

TCP Tahoe 算法:
超时重传 RTO[Retransmission Timeout] 超时,TCP会重传数据包:

  • 由于发生丢包,将慢启动阈值ssthresh设置为当前cwnd的一半,即ssthresh = cwnd / 2.
  • cwnd重置为1
  • 进入慢启动过程

TCP Reno 算法:
当收到三个重复确认 ACK 时,TCP 开启快速重传 Fast Retransmit 算法,而不用等到 RTO 超时再进行重传:

  • cwnd 大小缩小为当前的一半
  • ssthresh 设置为缩小后的 cwnd 大小
  • 然后进入快速恢复算法 Fast Recovery

快速恢复算法 – Fast Recovery:
快速恢复算法的逻辑如下:

  • cwnd = cwnd + 3 MSS,加 3 MSS 的原因是因为收到 3 个重复的ACK。
  • 重传 DACKs 指定的数据包。
  • 如果再收到 DACKs,那么 cwnd 大小增加一。
  • 如果收到新的 ACK,表明重传的包成功了,那么退出快速恢复算法。将cwnd设置为ssthresh,然后进入拥塞避免算法。

关于 乘法减小(Multiplicative Decrease)和加法增大(Additive Increase):
“乘法减小”指的是无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞,就把慢开始门限ssthresh 设置为出现拥塞时发送窗口大小的一半,并执行慢开始算法,所以当网络频繁出现拥塞时,ssthresh 下降的很快,以大大减少注入到网络中的分组数。“加法增大”是指执行拥塞避免算法后,使拥塞窗口缓慢增大,以防止过早出现拥塞。常合起来成为 AIMD 算法。

TCP - 图5