# TCP/UDP

# TCP

:传输控制协议(Transmission Control Protocol)

  • 属于传输层协议,规定了网络上的主机之间如何传输数据。
  • 特点:
    • 全双工通信。
    • 面向连接:通信双方在通信之前要先建立连接,作为信道。
    • 传输可靠:
      • 会确认对方是否收到消息,如果未收到则自动重发。
      • 可进行差错控制,如果对方收到的消息出错,则自动重发。
      • 发送的数据包带有序号,接收方可按顺序处理。
      • 可实现流量控制、拥塞控制。
  • 大部分应用层协议都是基于 TCP 进行通信的,比如 HTTP、FTP、SMTP 等。

# 架构

  • TCP 协议采用 C/S 架构:

    • server
      • :称为服务器、被动连接方,由一个主机担任。需要持续监听某个 TCP 端口,供 client 连接。
      • Linux 系统通常可监听的端口号范围为 0~65535 ,例如 HTTP 服务器通常监听 TCP 80 端口。
    • client
      • :称为客户端、主动连接方,由任意个主机担任,可以连接到 server 。
  • 假设 client 想与某个 server 按 TCP 协议进行通信(比如传输一张图片),主要流程如下:

    1. client 事先知道 server 的 IP 地址、监听的端口号。
    2. 建立连接:client 发送几个特殊的 TCP 数据包给 server ,请求用 client 的随机一个端口,连接到 server 的指定一个端口。
    3. 正式通信:双方端口组成一个全双工信道,可以发送任意个包含任意 Payload 的 TCP 数据包。
  • 在类 Unix 系统中进行 TCP/UDP 通信时,通信双方需要各创建一个 Socket 文件,以读写文件的方式进行通信。

    • 比如 client 向本机的 Socket 文件写入数据,会被自动传输到 server 端的 Socket 文件,被 server 读取到数据。

# 建立连接

  • 建立 TCP 连接时需要经过三个步骤,称为 "三次握手" :

    1. client 发送一个 SYN=1 的 TCP 包给 server ,表示请求连接到 server 的指定一个 TCP 端口。
    2. server 收到后,回复一个 SYN=1、ACK=1 的 TCP 包,表示允许连接。
    3. client 收到后,发送一个 ACK=1 的 TCP 包,表示正式建立连接。
      • client 收到第 2 步发送的包时,证明了自己发送的包能被对方接收,而对方发送的包也能被自己接收,因此判断双方能够相互通信。
      • server 收到第 3 步发送的包时,才判断双方能够相互通信。
  • 建立 TCP 连接时的 Socket 状态变化:

    • LISTEN :server 正在监听该 Socket ,允许接收 TCP 包。
    • SYN_SENT :client 已发出 SYN=1 的 TCP 包,还没有收到 SYN+ACK 包。
    • SYN_RECV :server 已收到 SYN 包,还没有收到 ACK 包。
      • 如果 server 一直未收到 ACK 包,则会在超时之后重新发送 SYN+ACK 包,再次等待。多次超时之后,server 会关闭该连接。
    • ESTABLISHED :已建立连接。

# 关闭连接

  • 关闭 TCP 连接时需要经过四个步骤,称为 "四次分手" :

    1. 主动关闭方发送 FIN ,表示请求关闭连接。
    2. 被动关闭方收到后,先回复 ACK ,表示同意关闭连接。准备好之后也发送 FIN ,请求关闭连接。
    3. 主动关闭方收到后,发送一个 ACK ,表示自己已经关闭连接。
    4. 被动关闭方收到后,正式关闭连接。
  • 只有 client 能主动建立连接,但 client、server 都可以主动关闭连接。

    • 建立连接时,通信双方都要发送一个 SYN、一个 ACK 。
    • 关闭连接时,通信双方都要发送一个 FIN、一个 ACK 。
  • 关闭 TCP 连接时的 Socket 状态变化

    • FIN-WAIT-1
    • FIN-WAIT-2
    • TIME-WAIT
      • 主动关闭方在第 3 步之后,已关闭 TCP 连接,但 Socket 尚未关闭,还要等待 2*MSL 时间才能变成 CLOSED 状态,从而避免对方来不及关闭连接。
      • MSL(Maximum Segment Lifetime):TCP 段在网络传输中的最大生存时间,超过该时长就会被丢弃。
        • RFC 793 定义的 MSL 为 2 分钟,而 Linux 中 MSL 通常为 30 秒。
      • 考虑到一个 server 通常会被大量 client 请求连接,建议 server 不要主动关闭 TCP 连接,避免产生大量 TIME-WAIT 状态的 Socket ,占用一定内存。这样有两种结果:
        • client 主动关闭连接,则 server 可立即关闭 Socket ,而 client 要保留 TIME-WAIT 状态的 Socket 一段时间。
        • client 不主动关闭连接,则 server 可等 TCP 连接长时间未用于传输数据时将它主动关闭。
    • CLOSE-WAIT
      • 在第 2 步,如果被动关闭方没有立即调用 close() 关闭端口,就会长时间处于 CLOSE-WAIT 状态。
    • LAST-ACK
    • CLOSED :TCP、Socket 都已关闭。

# 数据包结构

TCP 数据包的结构如下:

  • Source Port :源端口,长度为 16 bit 。
  • Dest Port :目标端口,16 bit 。
  • Seq number :序列号,32 bit ,用于保证消息顺序。
  • Ack number :确认号,32 bit ,表示期望收到的下一个序列号,用于标识符 ACK=1 的情况。
  • Data offset :偏移量,4 bit 。表示 Payload 的起始坐标,即 TCP Header 的总长度。
  • Reserved :保留给未来使用,3 bit ,默认值为 0 。
  • Flag :标志符,9 bit 。每个 bit 代表一个标志位,默认值为 0 。
    • NS
    • CWR
    • ECE
    • URG=1 :表示该数据是紧急数据,应该被优先处理。
    • ACK=1 :表示确认。
    • PSH=1 :表示发送方应该立即将该数据打包成一个 TCP 数据包发送,接收方也应该立即将该数据上报给上层程序。
      • TCP 模块在发送数据时,一般会等发送缓冲区满了,才打包成一个 TCP 数据包发送。同理,接收数据时也一般会等接收缓冲区满了才上报。
      • 一般一个应用层的报文会被切分成多个 TCP 数据包发送,最后一个 TCP 数据包会设置成 PSH=1 。
    • RST=1 :用于重新建立 TCP 连接,也可用于拒绝连接。
    • SYN=1 :用于建立 TCP 连接,开始同步。
    • FIN=1 :用于关闭 TCP 连接。
  • Window size
  • Checksum :校验和,16 bit 。
  • Urgent pointer
  • Options
  • Payload :有效载体,即该数据包要传递的实际数据。
    • Payload 之前的其它数据都只是用于描述 TCP 数据包的元数据,称为 TCP Header 。
    • 以太网中网卡的 MTU 通常设置为 1500 ,因此如果 TCP 报文超过该长度,转换成 IP 数据包时就需要拆分成多份。

# 传输数据包

# 重传

  • 建立 TCP 连接之后,通信双方可以开始传输数据包。
    • 发送方每发送一个数据包之后,都需要接收方回复一个包含相应序列号的 ACK 包,才能确认发送成功。
  • 超时重传
    • :如果发送方发送一个数据包之后,超过 RTO(Retransmission Timeout) 时间未收到 ACK 包,则重新发送原数据包。
    • RTO 应该略大于 RTT 。例如 Linux 会根据网络实时的平均 RTT ,动态设置 ERO 。
      • 往返时间(Round Trip Time ,RTT),表示从发出数据包到收到回复的耗时。是端到端延迟的两倍。
    • 超时的常见原因:
      • 网络延迟突然变大
      • 发送的数据包丢失
      • 回复的 ACK 包丢失
  • 快速重传
    • :如果发送方发送一个数据包之后,没有受到 ACK 包,但之后发送的 3 个数据包都收到 ACK 包,则认为前一个数据包发送失败,立即重传。

# 流量控制

  • 滑动窗口
    • :接收方将接收窗口的大小告诉发送方,发送方由此确定发送窗口,可以每次发送多个数据包,再等待确认。
    • 这可以实现流量控制,控制传输速度。
  • 发送窗口
    • :指发送方能同时发送的数据包数,即最多允许多少个数据包等待确认,此时不允许发送新的数据包。
    • 它不能超过网络允许带宽、接收方的接收缓冲区,否则会丢包。
    • 发送窗口取决于接收窗口,近似相等

# 拥塞控制

  • 网络拥塞:当网络延迟变大、发生丢包时,会引发超时重传,但重传的数据包又会增加网络负载,导致延迟更大、丢包率更高。

  • 为了减轻网络拥塞,发送方会动态计算一个拥塞窗口,并限制发送窗口不能大于接收窗口或拥塞窗口。

  • BBR

    • :Google 于 2016 年发布的一种 TCP 拥塞控制算法。在延迟超过 100ms 、丢包率超过 1% 的场景下,能在发送 TCP 包时明显提高吞吐量、降低延迟。
    • 当网络链路拥塞时,传统的拥塞控制算法 Reno、Cubic 会将数据包放到缓冲区再发送,因此增加了延迟。而 BBR 算法会避免使用缓冲区。
    • Linux 内核 4.9 加入了 BBR 算法,可通过 sysctl 启用。
    • 拥塞控制算法只作用于 TCP 通信的一方,因此发送方、接收方可以采用不同的算法。

# UDP

:用户数据报协议(User Datagram Protocol)

  • 特点:
    • 全双工通信。
    • 面向无连接。
    • 传输不可靠。
  • UDP 与 TCP 对比:
    • UDP 没有 TCP 的诸多功能,更像是 IP 协议,只是简单地发送数据包。
    • UDP 的开销更低。比如 TCP 需要一直运行一个服务器,而 UDP 适合用作即时通信、广播消息。
    • 使用 UDP 时,如果需要差错控制等可靠性,可由应用层实现。
  • 少部分应用层协议是基于 UDP 进行通信的,比如 DHCP、DNS、SNMP、RIP 等。