运输层
基本概念
运输层(Transport Layer)为运行在不同主机上的应用进程提供逻辑通信能力:
- 发送端:将应用层报文分割为段(segment)并传递给网络层
- 接收端:重组分段为完整报文并递交给应用层
- 典型协议:TCP 和 UDP (User Datagram Protocol, 用户数据报协议)
协议对比
特性 | TCP | UDP |
---|---|---|
可靠性 | 可靠传输 | 尽最大努力交付 |
连接方式 | 面向连接 | 无连接 |
流量控制 | 有 | 无 |
传输单位 | 字节流 | 数据报文 |
多路复用与分解
- 多路复用(Multiplexing, Mux):将多个应用进程的数据合并为一个传输层报文
- 分解(Demultiplexing, Demux):将传输层报文分发给对应的应用进程
graph LR
subgraph "多路复用"
A["数据流 A"] --> Mux["多路复用器"]
B["数据流 B"] --> Mux
C["数据流 C"] --> Mux
Mux --> Combined["组合数据流"]
end
subgraph "解复用"
Combined --> Demux["解复用器"]
Demux --> A1["数据流 A'"]
Demux --> B1["数据流 B'"]
Demux --> C1["数据流 C'"]
end
linkStyle 0,1,2 stroke:#00AA00,stroke-width:2px;
linkStyle 4,5,6 stroke:#AA0000,stroke-width:2px;
style Mux fill:#ccf,stroke:#88f,stroke-width:2px
style Demux fill:#fcc,stroke:#f88,stroke-width:2px
UDP 是最小复用的传输层协议,仅提供多路复用与分解功能,不保证可靠传输。在如视频会议等实时应用场景中,UDP 通常优于 TCP。
而 TCP 提供了可靠传输、流量控制、拥塞控制等功能,适用于如文件传输等对可靠性要求较高的场景。
端口与套接字
- 端口(Port):16 位标识符(0-1023 为知名端口)
- 套接字(Socket):应用程序进程与操作系统(中的传输层)交换网络消息的软件抽象。
- UDP:
<本地 IP, 本地端口>
- TCP:
<本地 IP, 本地端口, 远端 IP, 远端端口>
- UDP:
graph LR
subgraph "应用程序"
A["应用 A<br>端口 1234"] --> Mux["多路复用器"]
B["应用 B<br>端口 5678"] --> Mux
C["应用 C<br>端口 9012"] --> Mux
end
Mux --> Channel["共享通道<br>(例如:网络连接)"]
style Mux fill:#ccf,stroke:#888,stroke-width:2px
linkStyle 0,1,2 stroke:#0aa,stroke-width:2px,stroke-dasharray: 5 5
style Channel fill:#ffc,stroke:#888,stroke-width:2px,text-align:center
linkStyle 3 stroke:#aa0,stroke-width:3px
S["多路复用策略<br>(例如:端口号、协议)"] --> Mux
style S fill:#fcc,stroke:#888
linkStyle 4 stroke-dasharray: 2 2,stroke:#a0a
classDef App fill:#eee,stroke:#888
class A,B,C App
解复用过程
- 主机根据数据包头部的目标端口号定位对应套接字
- UDP 使用二元组(目标 IP + 端口)
- TCP 使用四元组(源/目标 IP + 源/目标端口)
graph LR
Channel["共享通道<br>(例如:网络连接)"] --> Demux["解复用器"]
subgraph "应用程序"
Demux --> A["应用 A<br>端口 1234"]
Demux --> B["应用 B<br>端口 5678"]
Demux --> C["应用 C<br>端口 9012"]
end
style Demux fill:#ccf,stroke:#888,stroke-width:2px
linkStyle 0 stroke:#aa0,stroke-width:3px
style Channel fill:#ffc,stroke:#888,stroke-width:2px,text-align:center
linkStyle 1,2,3 stroke:#0aa,stroke-width:2px,stroke-dasharray: 5 5
S["解复用策略<br>(例如:端口号、协议)"] --> Demux
style S fill:#fcc,stroke:#888
linkStyle 4 stroke-dasharray: 2 2,stroke:#a0a
classDef App fill:#eee,stroke:#888
class A,B,C App
可靠传输设计
核心挑战:
- 数据包可能:损坏、丢失、延迟、乱序、重复
- 解决方法矩阵:
- 校验和(Checksums) 检测损坏
- 序列号(Sequence Numbers) 识别数据包
- 确认机制(ACK/NACK[1]) 反馈接收状态
- 超时重传(Retransmission) 处理丢失
停等协议
停等协议(Stop-and-Wait Protocol):发送方发送一个数据包后等待接收方的确认,再发送下一个数据包。
sequenceDiagram
Sender->>Receiver: 发送数据帧 0
activate Sender
activate Receiver
Receiver-->>Sender: 确认 ACK0
deactivate Receiver
deactivate Sender
Sender->>Receiver: 发送数据帧 1
activate Sender
activate Receiver
Receiver-->>Sender: 确认 ACK1
deactivate Receiver
deactivate Sender
Note right of Receiver: 每次发送一帧,等待确认后再发送下一帧。
超时与重传:
sequenceDiagram
Sender->>Receiver: 发送数据帧 0
activate Sender
Note right of Sender: 启动计时器
activate Receiver
Receiver--xSender: 确认 ACK0 丢失
deactivate Receiver
Sender->>Receiver: 超时,重传数据帧 0
activate Receiver
Receiver-->>Sender: 确认 ACK0
deactivate Receiver
deactivate Sender
Note right of Receiver: 处理超时和重传机制。
- 优点:实现简单
- 缺点:信道利用率低()
流水线协议
流水线协议(Pipelining Protocol):发送方连续发送多个数据包,接收方连续确认。
sequenceDiagram
Sender->>Receiver: 发送数据帧 0
Sender->>Receiver: 发送数据帧 1
Sender->>Receiver: 发送数据帧 2
activate Sender
activate Receiver
Receiver-->>Sender: 确认 ACK0
Receiver-->>Sender: 确认 ACK1
Receiver-->>Sender: 确认 ACK2
deactivate Receiver
deactivate Sender
Note right of Receiver: 连续发送多帧,无需等待确认。
滑动窗口机制
graph LR
subgraph 发送方
A[发送窗口] --> B(已发送未确认)
B --> C(可发送)
end
subgraph 接收方
D[接收窗口] --> E(已接收已确认)
E --> F(可接收)
end
A -- 数据传输 --> D
D -- 确认ACK --> A
style A fill:#ccf,stroke:#888,stroke-width:2px
style D fill:#ccf,stroke:#888,stroke-width:2px
- 发送窗口:允许连续发送 n 个未确认数据包
- 接收窗口:缓存乱序到达的数据包
确认的方式:
- 累计确认(Cumulative Acknowledgement):接收方通过发送最高连续接收到的字节序列号来确认数据。
- 例如,若接收方已收到字节 1-1000 和 2001-3000,但 1001-2000 丢失,则它仍会发送 ACK=1001(即「期望下一个收到 1001」)。
- 优点:
- 实现简单,默认在 TCP 中使用。
- 减少 ACK 报文数量(一个 ACK 可确认多个数据包)。
- 缺点:
- 重传效率低:若中间有数据包丢失,发送方会重传所有未被确认的包(即使后续包已到达)。
- 例如:发送方发送包 1, 2, 3, 4, 5,若包 3 丢失,接收方 ACK=3;即使包 4, 5 已到达,发送方仍需重传包 3, 4, 5。
- 应用场景:传统 TCP,低丢包率网络
- 选择确认(Selective Acknowledgement):接收方通过 SACK 选项明确告知发送方哪些非连续的数据块已成功接收。
- 例如,若接收方收到字节 1-1000 和 2001-3000,但 1001-2000 丢失,它会发送:ACK=1001(累计确认)+ SACK 块=2001-3000(选择确认)。
- 优点:
- 重传效率高:发送方仅重传丢失的包(如 1001-2000),无需重传已收到的数据(2001-3000)。
- 显著减少冗余重传,提升高丢包率网络下的性能。
- 缺点:
- 实现复杂,需双方协商启用(TCP 选项)。
- ACK 报文略大(需携带 SACK 块信息)。
- 应用场景:高丢包率网络(如无线网络)、需要高效重传的现代 TCP 实现(如 Linux 默认启用 SACK)。
相应有重传策略:
- GBN (Go-Back-N):重传窗口内所有未确认数据包
- SR (Selective Repeat):仅重传丢失数据包
机制 | Go-Back-N (GBN) | Selective Repeat (SR) |
---|---|---|
重传策略 | 重传所有未确认包 | 仅重传丢失包 |
接收方处理 | 丢弃乱序包 | 缓存乱序包 |
确认方式 | 累计确认 | 选择确认 |
适用场景 | 低误码率环境 | 高误码率环境 |
GBN 示例:
sequenceDiagram
Sender->>Receiver: 发送帧 0, 1, 2
activate Sender
activate Receiver
Receiver-->>Sender: ACK1 丢失
Receiver->>Sender: ACK2
deactivate Receiver
Note right of Sender: 收到乱序 ACK, 触发重传从帧 1 开始的所有帧
Sender->>Receiver: 重传帧 1, 2
activate Receiver
Receiver-->>Sender: ACK1, ACK2
deactivate Receiver
deactivate Sender
SR 示例:
sequenceDiagram
Sender->>Receiver: 发送帧 0, 1, 2
activate Sender
activate Receiver
Receiver-->>Sender: ACK0 丢失
Receiver->>Sender: ACK1, ACK2
deactivate Receiver
Note right of Sender: 只重传丢失的帧 0
Sender->>Receiver: 重传帧 0
activate Receiver
Receiver-->>Sender: ACK0
deactivate Receiver
deactivate Sender
性能优化
- 理想吞吐量:
- 窗口大小与 RTT 关系:
定时器管理
- 每个未确认包维护独立定时器(SR)
- 仅首个未确认包维护定时器(GBN)
超时重传示例
当发送方在 内未收到 ACK,触发重传机制:
流量控制
- 接收方通过通告窗口(Advertised Window)控制发送速率
- 窗口大小动态调整:
UDP
UDP (User Datagram Protocol, 用户数据报协议) 是一种轻量级的传输层协议,提供了一种不可靠、无连接的数据传输服务。
特点
- 轻量级通信:避免了为保证顺序和可靠性带来的开销和延迟。
- 无连接:
- UDP 发送方和接收方之间没有握手过程。
- 每个 UDP 段的处理都独立于其他段。
- 尽最大努力服务:UDP 报文段可能丢失或乱序到达。
- RFC 768:UDP 协议在 1980 年发布的 RFC 768 中描述。
UDP 使用场景
- 流媒体应用(容忍丢失,对速率敏感)
- DNS(域名系统)
- SNMP(简单网络管理协议)
为什么需要 UDP?
- 无需建立连接:减少了连接建立的延迟。
- 简单:发送方和接收方无需维护连接状态。
- 头部开销小:UDP 头部较小,减少了数据传输的额外开销。
- 无拥塞控制:UDP 可以尽可能快地发送数据。
UDP 报文段格式
1 | 0 31 |
- Source Port:源端口号(16 位)
- Destination Port:目标端口号(16 位)
- Length:长度,包括头部和数据的总长度,以字节为单位(16 位)
- Checksum:校验和(16 位)
- 用于检测传输过程中产生的错误(例如,位翻转)
- 计算方法:将报文段内容(包括头部字段)视为 16 位整数序列,进行加法运算,并将结果的补码作为校验和。
- 发送方将校验和值放入 UDP 校验和字段。
- 接收方计算接收到的报文段的校验和,并与校验和字段的值进行比较。如果相等,则认为没有检测到错误;如果不相等,则认为检测到错误。
- 校验和也可以设置为 0,表示不进行校验。
UDP 校验和示例
假设有两个 16 位整数:
1 | 1110011001100110 |
进行加法运算:
1 | 1110011001100110 |
由于最高位有进位,需要将进位加到结果的最低位:
1 | 1011101110111011 |
取反得到校验和:
0100010001000011 |
在进行加法运算时,如果最高位产生进位,需要将进位加到结果的最低位。
TCP
TCP (Transmission Control Protocol, 传输控制协议) 是一种面向连接、可靠的传输层协议,提供了一种可靠的、有序的字节流传输服务。
TCP 抽象
TCP 提供了一种可靠的、有序的字节流(reliable, in-order, byte stream)服务:
- 可靠性:TCP 通过重传丢失的数据包来保证可靠性。
- 直到超时并关闭连接
- 有序性:TCP 仅将连续的数据块传递给应用程序。
- 字节流:TCP 将数据视为字节流,并尝试将其传递给应用程序。
TCP 头部
1 | 0 31 |
- Source Port:源端口号(16 位)
- Destination Port:目标端口号(16 位)
- Sequence Number:序列号(32 位)
- 表示该报文段中数据的第一个字节在字节流中的偏移量。
- 由于 TCP 是字节流协议,因此序列号是字节偏移量,而不是报文段 ID。
- Acknowledgment Number:确认号(32 位)
- 表示期望接收的下一个字节的序列号。
- 接收方发送的确认号是其已按序接收到的最后一个字节的序列号加 1。
- HdrLen:头部长度(4 位)
- 表示 TCP 头部的长度,以 4 字节为单位。
- 如果头部没有选项字段,则头部长度为 5(即 20 字节)。
- Flags:标志位(6 位)
- SYN:同步标志,用于建立连接。
- ACK:确认标志,用于确认接收到的数据。
- FIN:结束标志,用于关闭连接。
- RST:复位标志,用于重置连接。
- PSH:推送标志,用于指示接收方应立即将数据推送给应用程序。
- URG:紧急标志,用于指示报文段中包含紧急数据。
- Advertised Window:通告窗口(16 位)
- 用于流量控制,表示接收方当前能够接收的字节数。
- Checksum:校验和(16 位)
- 计算范围包括 TCP 头部、数据以及伪头部。
- Urgent Pointer:紧急指针(16 位)
- 当 URG 标志位置 1 时有效,指示紧急数据在报文段中的偏移量。
- Options:选项(可变长度)
- 用于协商 MSS、窗口缩放等。
Source Port 与 Destination Port 用来多路复用(Mux)和分解数据流(Demux),Sequence Number 和 Acknowledgment Number 用来实现可靠传输。
TCP 报文段、数据包和帧
- IP 数据包(IP packet):
- 最大长度不能超过最大传输单元(Maximum Transmission Unit, MTU)。
- 例如,以太网的 MTU 通常为 1500 字节。
- TCP 数据包(TCP packet):
- 包含 TCP 头部和数据的 IP 数据包。
- TCP 头部长度至少为 20 字节。
- TCP 报文段(TCP segment):
- 最大长度不能超过最大报文段长度(Maximum Segment Size, MSS)。
- 例如,以太网环境下,MSS 通常为 1460 字节(1500 - 20 - 20)。
- MSS = MTU - IP 头部长度 - TCP 头部长度
序列号(Sequence Numbers)
ISN(Initial Sequence Number):初始序列号,在建立连接时随机生成。
- 序列号表示报文段中第一个字节在字节流中的位置。
graph LR
subgraph Host A
A[字节流] --> B(ISN)
B --> C[k bytes]
C --> D[报文段]
D --> E["Sequence number = ISN + k"]
end
style A fill:#ccf,stroke:#888,stroke-width:2px
style D fill:#ccf,stroke:#888,stroke-width:2px
为什么要有 ISN?为什么不直接从 0 开始编号?这是为了安全和避免混淆。
- 防止旧连接的干扰:如果每次都从 0 开始,那么如果网络中残留了之前连接的延迟报文段,这些旧报文段的序列号可能会和新连接的序列号冲突,导致数据错乱。
- 提高安全性:随机的 ISN 使得攻击者更难猜测序列号,从而增加伪造 TCP 报文段的难度。
操作 | 发送字节 | 当前序列号计算 | 报文段序列号 |
---|---|---|---|
发送 SYN | - | ISN = 1000 | 1000 |
发送 "Hello" | 5 | 1000 + 1 + 5 = 1006 | 1001 |
发送 "World" | 5 | 1006 + 5 = 1011 | 1006 |
发送 FIN | - | 1011 → 结束于1012 | 1011 |
SYN 和 FIN 各占 1 个序列号位置,即使不携带数据。
确认号和序列号
- 发送方发送数据包:
- 数据从序列号 X 开始。
- 数据包包含 B 字节的数据:[X, X+1, X+2, …, X+B-1]。
- 接收方接收到数据包后,发送 ACK:
- 如果 X 之前的所有数据都已按序接收:
- ACK 确认 X+B(因为这是下一个期望的字节)。
- 如果接收到的最高有序字节是 Y,且 (Y+1) < X:
- ACK 确认 Y+1。
- 即使之前已经确认过 Y+1,也会再次确认。
- 如果 X 之前的所有数据都已按序接收:
graph LR
subgraph Host A
A[字节流] --> B(ISN)
B --> C[k bytes]
C --> D[TCP Data]
D --> E[TCP HDR]
E --> F["Sequence number = ISN + k"]
end
subgraph Host B
G[TCP Data] --> H[TCP HDR]
H --> I["ACK sequence number = next expected byte = seqno + length(data)"]
end
D -- 数据传输 --> G
H -- ACK --> E
style A fill:#ccf,stroke:#888,stroke-width:2px
style D fill:#ccf,stroke:#888,stroke-width:2px
style G fill:#ccf,stroke:#888,stroke-width:2px
典型操作
- Sender: seqno=X, length=B
- Receiver: ACK=X+B
- Sender: seqno=X+B, length=B
- Receiver: ACK=X+2B
- Sender: seqno=X+2B, length=B
下一个数据包的序列号与上一个 ACK 字段相同。
TCP 采用的可靠数据传输机制
- 校验和(Checksum):用于检测数据是否损坏。
- 序列号(Sequence numbers):字节偏移量,用于识别数据包。
- 累计确认(Cumulative acknowledgements):类似 GBN,接收方发送累计确认。
- 选择重传:接收方可以缓存乱序的数据包(类似 SR)。
- 单一重传计时器:发送方维护一个单一的重传计时器(类似 GBN)。
- 快速重传(Fast retransmit):通过重复 ACK 触发提前重传。
- 重复 ACK 是孤立丢包的信号。
- ACK 没有进展,意味着 500 还没有被传递。
- ACK 流意味着一些数据包正在被传递。
- 收到 k 个重复 ACK 后触发重传。
- TCP 使用 k=3。
- 比等待超时更快。
- 重复 ACK 是孤立丢包的信号。
- 超时重传:
- 如果发送方在超时时间内没有收到 ACK,则重传窗口中的第一个数据包。
- 如何选择超时值?
- 太长:连接吞吐量低。
- 太短:重传的数据包可能只是延迟了。
- 解决方案:使超时时间与 RTT 成比例。
- 如何测量 RTT?
RTT 估计:迭代式估计
TCP 连接管理
TCP 连接建立
TCP 使用三次握手(three-way handshake)建立连接:
- 客户端向服务器发送一个 SYN 报文段(SYN=1,seq=x)。
- SYN:同步标志,用于发起连接。
- seq:客户端的初始序列号。
- 服务器收到 SYN 后,回复一个 SYN-ACK 报文段(SYN=1,ACK=1,seq=y,ack=x+1)。
- SYN:同步标志。
- ACK:确认标志。
- seq:服务器的初始序列号。
- ack:确认客户端的 SYN,值为客户端的初始序列号 + 1。
- 客户端收到 SYN-ACK 后,回复一个 ACK 报文段(ACK=1,seq=x+1,ack=y+1)。
- ACK:确认标志。
- seq:客户端的序列号 + 1。
- ack:确认服务器的 SYN,值为服务器的初始序列号 + 1。
sequenceDiagram
participant Client
participant Server
Note over Client,Server: 正常情况
Client->>Server: SYN (seq=x)
activate Server
Server->>Client: SYN-ACK (seq=y, ack=x+1)
activate Client
Client->>Server: ACK (seq=x+1, ack=y+1)
deactivate Client
deactivate Server
Note over Client,Server: 异常情况 1:SYN 丢失
Client->>Server: SYN (seq=x)
Client->>Client: 超时重传 SYN
Client->>Server: SYN (seq=x)
activate Server
Server->>Client: SYN-ACK (seq=y, ack=x+1)
activate Client
Client->>Server: ACK (seq=x+1, ack=y+1)
deactivate Client
deactivate Server
Note over Client,Server: 异常情况 2:SYN-ACK 丢失
Client->>Server: SYN (seq=x)
activate Server
Server->>Client: SYN-ACK (seq=y, ack=x+1)
deactivate Server
Server->>Server: 超时重传 SYN-ACK
Server->>Client: SYN-ACK (seq=y, ack=x+1)
activate Client
Client->>Server: ACK (seq=x+1, ack=y+1)
deactivate Client
Note over Client,Server: 异常情况 3:ACK 丢失
Client->>Server: SYN (seq=x)
activate Server
Server->>Client: SYN-ACK (seq=y, ack=x+1)
activate Client
Client->>Server: ACK (seq=x+1, ack=y+1)
deactivate Client
deactivate Server
Client->>Client: 数据传输
Server->>Server: 可能超时重传数据, 认为连接未建立
- 三次握手的作用:确认双方的 SYN 和序号。
如果 SYN 数据包丢失怎么办?
- 假设 SYN 数据包丢失:数据包被网络丢弃或服务器繁忙。
- 最终,没有 SYN-ACK 到达。
- 发送方在超时时重传 SYN。
- TCP 发送方应该如何设置计时器?
- 发送方不知道接收方有多远。
- 很难猜测一个合理的等待时间。
- RFC 1122 和 2988 规定默认值为 3 秒。
- 一些实现使用 6 秒。
TCP 连接关闭
TCP 使用四次挥手关闭连接:
- 客户端向服务器发送一个 FIN 报文段(FIN=1)。
- FIN:结束标志,用于关闭连接。
- 服务器收到 FIN 后,回复一个 ACK 报文段。
- 此时,连接处于半关闭状态,服务器仍然可以向客户端发送数据。
- 服务器准备好关闭连接后,向客户端发送一个 FIN 报文段(FIN=1)。
- 客户端收到 FIN 后,回复一个 ACK 报文段。
- 客户端进入
TIME_WAIT
状态,等待一段时间以确保服务器收到 ACK。 - 如果 ACK 丢失,服务器将重传 FIN。
- 客户端进入
sequenceDiagram
participant A
participant B
A->>B: FIN
activate B
B-->>A: ACK
Note over A,B: 连接半关闭
B->>A: FIN
activate A
A-->>B: ACK
deactivate A
deactivate B
Note over A: TIME_WAIT
Note over A,B: 连接关闭
- 正常终止,一次关闭一端:
- 使用 Finish (FIN) 关闭并接收剩余字节。
- FIN 在序列空间中占用一个字节。
- 另一方确认该字节以确认。
- 关闭连接的 A 端,但不关闭 B 端。
- 直到 B 同样发送 FIN。
- 然后 A 确认。
- 使用 Finish (FIN) 关闭并接收剩余字节。
- 正常终止,两端同时关闭:与之前相同,但 B 在确认 A 的 FIN 的同时设置 FIN。
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Client 主动关闭连接
Client->>Server: FIN (seq=x)
activate Server
Server->>Client: ACK (seq=y, ack=x+1)
Server->>Server: 准备关闭
Server->>Client: FIN (seq=z)
activate Client
Client->>Server: ACK (seq=x+1, ack=z+1)
deactivate Client
deactivate Server
Note over Client,Server: Server 主动关闭连接
Server->>Client: FIN (seq=x)
activate Client
Client->>Server: ACK (seq=y, ack=x+1)
Client->>Client: 准备关闭
Client->>Server: FIN (seq=z)
activate Server
Server->>Client: ACK (seq=x+1, ack=z+1)
deactivate Client
deactivate Server
Note over Client,Server: 同时关闭
Client->>Server: FIN (seq=x)
activate Server
Server->>Client: FIN (seq=z)
activate Client
Client->>Server: ACK (seq=y, ack=z+1)
Server->>Client: ACK (seq=x+1, ack=z+1)
deactivate Client
deactivate Server
- 突然终止:
- A 向 B 发送 RESET (RST)。
- 例如,因为 A 上的应用程序崩溃。
- B 不确认 RST。
- 因此,RST 不会可靠地传递,并且任何正在传输的数据都会丢失。
- 但是:如果 B 发送更多内容,将引发另一个 RST。
- A 向 B 发送 RESET (RST)。
sequenceDiagram
participant Client
participant Server
Note over Client,Server: 情况 1:连接不存在,收到数据
Client->>Server: 数据包 (seq=x)
activate Server
Server->>Client: RST
deactivate Server
Note over Client,Server: 情况 2:收到期望之外的包
Client->>Server: SYN (seq=x)
activate Server
Server->>Client: SYN-ACK (seq=y, ack=x+1)
activate Client
Client->>Client: 客户端异常
Client->>Server: RST
deactivate Client
deactivate Server
Note over Client,Server: 情况 3:超时或其他错误导致连接异常
Client->>Server: 数据包 (seq=x)
activate Server
Server->>Server: 超时/其他错误
Server->>Client: RST
deactivate Server
Client->>Client: 连接中断
Note over Client,Server: 情况 4:端口不存在/未监听
Client->>Server: SYN (seq=x)
activate Server
Server->>Server: 端口未监听
Server->>Client: RST
deactivate Server
Client->>Client: 连接被拒绝
RST 表示重置连接,通常表示有严重错误发生,连接需要立即中断。
-
连接不存在,收到数据
- Server 收到一个数据包,但是找不到对应的连接,说明可能连接已经关闭或者从来没有建立。
- Server 回复 RST 包,通知 Client 连接不存在。
-
收到期望之外的包
- Client 与 Server 建立连接的过程中,Client 发生异常 (例如应用程序崩溃)。
- Client 发送 RST 包,通知 Server 重置连接。
-
超时或其他错误导致连接异常
- Client 发送数据包后,Server 因为某些原因 (超时、资源不足等) 无法处理。
- Server 发送 RST 包,中断连接。
-
端口不存在/未监听
- Client 尝试连接 Server 的某个端口,但该端口没有程序监听。
- Server (或者网络设备) 发送 RST 包,拒绝连接。
TCP 状态转换图
客户端状态转换图
stateDiagram
[*] --> CLOSED
CLOSED --> SYN_SENT: Send SYN
SYN_SENT --> ESTABLISHED: Receive SYN-ACK, Send ACK
ESTABLISHED --> FIN_WAIT_1: Send FIN
FIN_WAIT_1 --> FIN_WAIT_2: Receive ACK, Send Nothing
FIN_WAIT_2 --> TIME_WAIT: Receive FIN, Send ACK
TIME_WAIT --> CLOSED: Wait 30 sec
服务器端状态转换图
stateDiagram
[*] --> CLOSED
CLOSED --> LISTEN: Create a listen socket
LISTEN --> SYN_RCVD: Receive SYN, Send SYN-ACK
SYN_RCVD --> FIN_WAIT_1: Receive ACK, Send Nothing
FIN_WAIT_1 --> CLOSE_WAIT: Receive FIN, Send ACK
CLOSE_WAIT --> LAST_ACK: Send FIN
LAST_ACK --> CLOSED: Receive ACK, Send Nothing
TCP 流量控制
流量控制(Flow Control)是 TCP 的一项关键功能,旨在防止发送方发送速率过快,导致接收方缓冲区溢出。
滑动窗口回顾
TCP 使用滑动窗口(Sliding Window)机制进行流量控制。发送方和接收方各自维护一个窗口:
- 发送窗口:
- 左边界:第一个未确认字节的序列号。
- 右边界:左边界 + 窗口大小。
- 窗口大小:限制了发送方在收到确认前可以发送的未确认数据量。
- 接收窗口:
- 左边界:期望接收的下一个字节的序列号。
- 右边界:左边界 + 窗口大小。
- 窗口大小:接收方缓冲区可用空间的大小。
graph LR
subgraph 发送方
A[发送窗口] --> B(已发送未确认)
B --> C(可发送)
end
subgraph 接收方
D[接收窗口] --> E(已接收已确认)
E --> F(可接收)
end
A -- 数据传输 --> D
D -- 确认ACK --> A
style A fill:#ccf,stroke:#888,stroke-width:2px
style D fill:#ccf,stroke:#888,stroke-width:2px
通告窗口
- 通告窗口(Advertised Window, RWND[2]):接收方在 ACK 报文中告知发送方其当前可接收的字节数。
- RWND = 接收方缓冲区大小 - (最后接收字节 - 最后读取字节)
- 接收方通过在 ACK 中包含 RWND 值来动态调整发送方的发送速率。
- 发送方根据 RWND 调整发送窗口大小,确保未确认字节数不超过 RWND。
接收方的滑动窗口:
发送方的滑动窗口:
graph TB
%% 发送方子图
subgraph "发送方(Sender)"
SendingProcess["发送进程<br>(Sending Process)"] --> TCP["TCP 缓冲区<br>(TCP Buffer)"]
TCP -- "滑动窗口限制<br>(≤ RWND)" --> LastByteWritten["最后写入字节<br>(Last Byte Written)"]
FirstUnACKedByte["第一个未确认字节<br>(First UnACKed Byte)"] -->|数据等待确认| LastByteWritten
LastByteSent["最后发送字节<br>(Last Byte Sent)"] -->|可发送范围| LastByteCanSend["可发送的最后字节<br>(Last Byte Can Send)"]
end
%% 接收方子图
subgraph "接收方(Receiver)"
LastByteRead["最后读取字节<br>(Last Byte Read)"] -->|可用缓冲区| BufferSize["缓冲区大小(B)<br>(Buffer Size)"]
NextByteNeeded["下一个需要的字节<br>(Next Byte Needed)"] -->|接收窗口| LastByteReceived["最后接收字节<br>(Last Byte Received)"]
RWND["接收窗口大小<br>RWND = B - (LastByteReceived - LastByteRead)"] -->|动态计算| NextByteNeeded
end
%% 链接发送方和接收方
LastByteCanSend -->|基于 RWND 限制| NextByteNeeded
%% 样式定义
classDef sender fill:#e6f3ff,stroke:#333,stroke-width:2px
classDef receiver fill:#ffe6e6,stroke:#333,stroke-width:2px
classDef key fill:#f9f9f9,stroke:#333,stroke-width:4px,font-weight:bold
%% 应用样式
class SendingProcess,TCP,LastByteWritten,FirstUnACKedByte,LastByteSent,LastByteCanSend sender
class LastByteRead,BufferSize,NextByteNeeded,LastByteReceived,RWND receiver
class RWND key
- 发送方窗口随着新数据被确认而前进。
- 接收方窗口随着接收进程消耗数据而前进。
- 接收方通告发送方接收窗口的当前右边界。
- 发送方保证发送的数据量不超过此限制。
由于 UDP 没有流量控制,故可能因缓冲区溢出而丢包。
TCP 拥塞控制
拥塞控制(Congestion Control)是 TCP 的另一项核心功能,旨在避免网络拥塞,提高网络利用率和公平性。
拥塞控制概述
- 目标:
- 发现可用带宽
- 适应带宽变化
- 公平共享带宽
- 挑战:
- 网络状况复杂多变
- 缺乏全局信息
- 需要分布式、自适应的解决方案
拥塞控制设计考虑
- 如何感知拥塞?
- 隐式信号:
- 数据包延迟:网络拥塞时,数据包在路由器缓冲区中的排队时间增加,导致往返时间(RTT)增大。
- 数据包丢失:当路由器缓冲区溢出时,会丢弃数据包。
- 显式信号:
- ECN (Explicit Congestion Notification):路由器在数据包头部设置拥塞指示位,通知发送方网络拥塞。
- 隐式信号:
- 谁负责拥塞控制?
- 主要由端主机(发送方和接收方)负责。
- 路由器可以提供辅助信息(如 ECN)。
- 如何处理拥塞?
- 持续自适应:根据网络状况动态调整发送速率。
拥塞控制抽象模型
graph LR
A["发送主机 A"] --> Buffer["路由器缓冲区"]
Buffer --> B["接收主机 B"]
style A fill:#ccf,stroke:#888,stroke-width:2px
style Buffer fill:#fcc,stroke:#888,stroke-width:2px
style B fill:#ccf,stroke:#888,stroke-width:2px
- 忽略路由器内部结构,将其建模为特定输入输出对的单个队列。
- 拥塞控制的目标是使发送速率与瓶颈带宽相匹配。
- 发送速率过低:带宽未充分利用。
- 发送速率过高:导致拥塞,数据包丢失。
多流共享与带宽分配
graph LR
A1["A1"] --> Buffer["缓冲区"]
A2["A2"] --> Buffer
A3["A3"] --> Buffer
Buffer -->|"BW(t)"| Node[" "]
Node --> B1["B1"]
Node --> B2["B2"]
Node --> B3["B3"]
style A1 fill:#ccf,stroke:#888,stroke-width:2px
style A2 fill:#ccf,stroke:#888,stroke-width:2px
style A3 fill:#ccf,stroke:#888,stroke-width:2px
style Buffer fill:#fcc,stroke:#888,stroke-width:2px
style B1 fill:#ccf,stroke:#888,stroke-width:2px
style B2 fill:#ccf,stroke:#888,stroke-width:2px
style B3 fill:#ccf,stroke:#888,stroke-width:2px
- 多个流共享同一瓶颈链路时,需要考虑:
- 总发送速率应与可用带宽匹配。
- 如何在多个流之间分配带宽。
拥塞控制方法
- 不关心:
- 可能导致大量丢包。
- 预留:
- 预先协商带宽分配。
- 需要事先协商。
- 可能导致低利用率。
- 定价:
- 根据出价决定是否丢包。
- 需要支付模型。
- 动态调整:
- 主机推断拥塞程度并调整。
- 网络向主机报告拥塞程度,主机调整。
- 结合以上方法。
- 实现简单但可能次优,动态复杂。
动态调整的通用性已被证明非常强大:
- 不预设商业模式、流量特征、应用需求
- 但假设良好的合作精神
TCP 拥塞控制基本问题
- 发送方如何检测拥塞?
- 发送方如何调整发送速率?
仍然是为实现上面提及的三个目的:
- 发现可用带宽
- 适应带宽变化
- 公平共享带宽
检测拥塞
方式:
- 数据包延迟:
- 棘手:嘈杂信号(延迟通常变化很大)。
- 路由器告知端主机:当它们拥塞时。
- 数据包丢失:
- TCP 已经必须检测的故障安全信号。
- 复杂性:非拥塞性丢失(例如,校验和错误)。
丢失的类型也不尽相同:
- 重复 ACK:孤立丢失
- 仍在接收 ACK
- 超时:更严重
- 没有足够的重复 ACK
- 必须遭受多次丢失
需要针对每种情况以不同方式调整速率。
速率调整
基本结构
- 收到 ACK(新数据):增加速率
- 检测到丢失:降低速率
如何增加/减少速率取决于我们所处的拥塞控制阶段:
- 发现可用瓶颈带宽(慢启动)
- 适应带宽变化(拥塞避免:AIMD)
慢启动(Slow Start)
慢启动(Slow Start)是 TCP 拥塞控制的初始阶段,旨在快速探测可用带宽。
- 目标:估计可用带宽
- 缓慢启动(为了安全)
- 快速提升(为了效率)
- 原理:
- 初始拥塞窗口(Congestion Window, CWND)设置为 1 个 MSS(Maximum Segment Size),即「最大数据包大小」。
- 每收到一个 ACK,CWND 增加 1 个 MSS。
- 每个 RTT,CWND 翻倍(指数增长)。
- 何时停止:
- 发生丢包(超时或 3 个重复 ACK)。
- CWND 达到慢启动阈值(ssthresh)。
sequenceDiagram
participant Sender
participant Receiver
Note over Sender: CWND = 1 MSS, ssthresh = 16 MSS
Sender->>Receiver: 1 MSS
activate Sender
activate Receiver
Receiver-->>Sender: 1 ACK
deactivate Receiver
Note over Sender: CWND = 2 MSS
Sender->>Receiver: 2 MSS
activate Receiver
Receiver-->>Sender: 2 ACK
deactivate Receiver
Note over Sender: CWND = 4 MSS
Sender->>Receiver: 4 MSS
activate Receiver
Receiver-->>Sender: 4 ACK
deactivate Receiver
Note over Sender: CWND = 8 MSS
Sender->>Receiver: 8 MSS
activate Receiver
Receiver-->>Sender: 8 ACK
deactivate Receiver
Note over Sender: CWND = 16 MSS
deactivate Sender
AIMD(Additive Increase Multiplicative Decrease)
当 CWND 达到 ssthresh 时,TCP 进入拥塞避免(Congestion Avoidance)阶段,采用 AIMD 算法调整 CWND:
- 加性增(Additive Increase):
- 每收到一个 ACK,CWND 增加 1/CWND 个 MSS。
- 每个 RTT,CWND 约增加 1 个 MSS(线性增长)。
- 乘性减(Multiplicative Decrease):
- 检测到丢包(3 个重复 ACK):
- ssthresh = CWND / 2
- CWND = ssthresh
- 进入拥塞避免状态
- 检测到超时:
- ssthresh = CWND / 2
- CWND = 1
- 重新进入慢启动状态
- 检测到丢包(3 个重复 ACK):
sequenceDiagram
participant Sender
participant Receiver
Note over Sender, Receiver: 拥塞避免阶段
Sender->>Receiver: CWND 个 MSS
activate Sender
activate Receiver
Receiver-->>Sender: ACK
deactivate Receiver
Note over Sender: CWND += 1/CWND
Note over Sender, Receiver: 收到 3 个重复 ACK
Sender->>Receiver: CWND 个 MSS
activate Receiver
Receiver-->>Sender: 重复 ACK
Receiver-->>Sender: 重复 ACK
Receiver-->>Sender: 重复 ACK
deactivate Receiver
Note over Sender: ssthresh = CWND / 2, CWND = ssthresh
Note over Sender, Receiver: 超时
Sender->>Receiver: CWND 个 MSS
activate Receiver
Note right of Sender: 超时
deactivate Receiver
Note over Sender: ssthresh = CWND / 2, CWND = 1, 慢启动
deactivate Sender
TCP 锯齿行为
AIMD 导致 TCP 拥塞窗口呈现锯齿状:
- 慢启动阶段快速探测带宽。
- 拥塞避免阶段在平衡点附近震荡。
- 丢包导致窗口减半。
为什么选择 AIMD?
- 效率(Efficiency):充分利用可用带宽。
- 公平性(Fairness):多个 TCP 流共享瓶颈链路时,趋向于平分带宽。
AIMD 的公平性模型:
两个用户,速率分别为 和 :
- 当 时发生拥塞。
- 当 时未充分利用。
- 当 时实现公平。
AIMD 的收敛性:
- 增加:
- 减少:
- 收敛到公平
快速恢复(Fast Recovery)
快速恢复是对 AIMD 的优化,旨在更快地从丢包中恢复:
- 触发条件:收到 3 个重复 ACK。
- 操作:
- ssthresh = CWND / 2
- CWND = ssthresh + 3 (考虑到 3 个数据包已离开网络)
- 每收到一个重复 ACK,CWND 增加 1 个 MSS。
- 收到新 ACK 后,CWND = ssthresh,进入拥塞避免状态。
sequenceDiagram
participant Sender
participant Receiver
Note over Sender, Receiver: 收到 3 个重复 ACK
Sender->>Receiver: CWND 个 MSS
activate Sender
activate Receiver
Receiver-->>Sender: 重复 ACK
Receiver-->>Sender: 重复 ACK
Receiver-->>Sender: 重复 ACK
deactivate Receiver
Note over Sender: ssthresh = CWND / 2, CWND = ssthresh + 3
Note over Sender, Receiver: 收到更多重复 ACK
Sender->>Receiver: (CWND + 1) 个 MSS
activate Receiver
Receiver-->>Sender: 重复 ACK
deactivate Receiver
Note over Sender: CWND += 1
Note over Sender, Receiver: 收到新 ACK
Sender->>Receiver: (CWND + 1) 个 MSS
activate Receiver
Receiver-->>Sender: 新 ACK
deactivate Receiver
Note over Sender: CWND = ssthresh, 进入拥塞避免
deactivate Sender
TCP 状态机
- 状态:
- 慢启动(Slow Start)
- 拥塞避免(Congestion Avoidance)
- 快速恢复(Fast Recovery)
- 转换条件:
- 超时(timeout)
- 3 个重复 ACK(dupACK=3)
- 新 ACK(new ACK)
- CWND > ssthresh
TCP 不同版本
- TCP Tahoe:
- 3 个重复 ACK 或超时:CWND = 1
- TCP Reno:
- 超时:CWND = 1
- 3 个重复 ACK:CWND = CWND / 2
- TCP NewReno:
- TCP Reno + 改进的快速恢复
- TCP SACK:
- 支持选择确认
路由器辅助的拥塞控制
TCP 的问题
- 误判非拥塞性丢失
- 填满队列导致高延迟
- 短流在发现可用容量之前完成
- 高速链路下 AIMD 不实用
- 锯齿发现对某些应用来说过于不稳定
- 不同 RTT 下不公平
- 与可靠性机制紧密耦合
- 终端主机可以作弊
1~2 可以靠路由器通知终端解决;3~6 可以靠路由器通知终端发送速率解决;7~8 可以靠路由器强制公平共享解决。
路由器辅助
ECN (Explicit Congestion Notification, 显式拥塞通知):
- 路由器在数据包头部设置拥塞指示位。
- 如果数据包设置了该位,则 ACK 也设置 ECN 位。
- 路由器何时设置该位的多种选择:
- 链路利用率和数据包延迟之间的权衡。
- 拥塞语义可以与丢包完全相同:
- 即,终端主机反应就像看到丢包一样。