1. 传输层职责与端口
1.1 传输层解决的问题
传输层在网络层提供的主机到主机通信之上,实现**端到端(进程到进程)**的可靠或尽力交付,核心抽象是端口号 + 传输协议。TCP 提供可靠、有序、面向字节流的服务;UDP 提供不可靠、无序、面向数据报的服务。
应用层 HTTP(80) DNS(53) SSH(22) ← 进程(端口)
传输层 TCP(可靠/有序) UDP(尽力/无连接)
网络层 IP(主机到主机, 尽力而为)
链路层 以太网/无线(帧)
| 传输服务 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(重传/确认) | 尽力交付 |
| 有序性 | 字节流有序 | 数据报无序 |
| 流量/拥塞控制 | 有 | 无 |
| 首部开销 | 20 字节 | 8 字节 |
| 典型应用 | HTTP/HTTPS、FTP、SSH | DNS、音视频、游戏、QUIC 底层 |
1.2 端口与 Socket
端口号范围 0-65535:0-1023 为知名端口(Well-Known,HTTP 80、HTTPS 443、DNS 53、SSH 22、MySQL 3306),1024-49151 为注册端口,49152-65535 为动态/临时端口。一个连接由四元组
(src_ip, src_port, dst_ip, dst_port)唯一标识。
# Python 建立 TCP 连接的 Socket 示例
import socket
def fetch_http(host, port=80):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect((host, port)) # 触发三次握手
sock.sendall(b"GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n" % host.encode())
data = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
data += chunk
sock.close() # 触发四次挥手
return data
2. TCP 报文段格式
2.1 首部字段
TCP 首部最小 20 字节,最大 60 字节(含选项)。各字段均为真实 RFC 793/7323 定义。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口 (16) | 目的端口 (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序号 Sequence Number (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 确认号 Acknowledgment Number (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据偏移(4)|保留(3)|N|C|E|U|A|P|R|S|F| 窗口大小 (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 (16) | 紧急指针 (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项 (可选) |
/* Linux 内核 uapi 中的 TCP 首部结构(字段与 RFC 一致) */
struct tcphdr {
__be16 source; /* 源端口 */
__be16 dest; /* 目的端口 */
__be32 seq; /* 序列号 */
__be32 ack_seq; /* 确认号 */
__u16 res1:4, /* 保留 */
doff:4, /* 数据偏移(首部长度 / 4) */
fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, ece:1, cwr:1;
__be16 window; /* 窗口大小(配合窗口缩放选项可达 1GB) */
__be16 check; /* 校验和 */
__be16 urg_ptr; /* 紧急指针 */
};
2.2 标志位速查
| 标志位 | 名称 | 含义 |
|---|---|---|
| SYN | Synchronize | 建立连接,同步初始序列号 |
| ACK | Acknowledgment | 确认号有效 |
| FIN | Finish | 主动关闭连接 |
| RST | Reset | 异常复位连接 |
| PSH | Push | 立即交付应用层,不等缓冲填满 |
| URG | Urgent | 紧急指针有效(极少用) |
| ECE/CWR | ECN 相关 | 显式拥塞通知(RFC 3168) |
| 选项 | 用途 |
|---|---|
| MSS | 通告最大报文段长度(默认 536/1460 字节) |
| Window Scale | 窗口缩放因子,支持 >64KB 窗口 |
| SACK | 选择性确认,允许只重传丢失段 |
| Timestamp | RTT 测量与 PAWS 防回绕 |
3. 三次握手与四次挥手
3.1 三次握手(建立连接)
为什么是三次?第一次确认客户端的发送能力,第二次确认服务端收到并能回复,第三次确认客户端知道服务端准备好了——同时保证双方都确认了彼此的收发能力,且能同步初始序列号,防止历史连接报文干扰。
客户端 服务端
│ 1. SYN(seq=x) │
│────────────────────────────────────►│ LISTEN → SYN-RECEIVED
│ 2. SYN(seq=y) + ACK(ack=x+1) │
│◄────────────────────────────────────│
│ 3. ACK(ack=y+1) │
│────────────────────────────────────►│ ESTABLISHED
ESTABLISHED
SYN Flood:攻击者只发 SYN 不完成握手,耗尽服务端半连接队列(backlog)。防御:SYN Cookies——把连接信息编码进 ISN,不用占用队列。
3.2 四次挥手(关闭连接)
为什么是四次?TCP 是全双工的,双方各自独立关闭自己的发送方向,因此需要两对 FIN/ACK。收到对方 FIN 后进入 CLOSE_WAIT,此时本端仍可继续发送未完成的数据。
客户端 服务端
│ 1. FIN(seq=u) │
│──────────────────────────────────►│ 客户端 FIN-WAIT-1
│ 2. ACK(ack=u+1) │ 服务端 CLOSE-WAIT
│◄──────────────────────────────────│ 客户端 FIN-WAIT-2
│ 3. FIN(seq=v) │
│◄──────────────────────────────────│ 服务端 LAST-ACK
│ 4. ACK(ack=v+1) │
│──────────────────────────────────►│
TIME-WAIT (2×MSL) CLOSED
4. TCP 状态机
4.1 完整状态迁移
TCP 连接的整个生命周期由 RFC 793 状态机管理。理解状态迁移是排查网络故障的基础(
netstat/ss输出正是这些状态)。
+---------+ +--------+
主动打开 SYN V V
CLOSED ─────────► SYN-SENT ──► SYN-RECEIVED ──► ESTABLISHED
▲ │ SYN+ACK │
│ │ │ FIN 发送
+────────── CLOSING ◄── FIN-WAIT-1 ◄─────┼──────────┐
│ RST/超时 │ │ ACK │ FIN │
│ V V V V
│ CLOSE-WAIT FIN-WAIT-2 CLOSING (数据继续)
│ │ ACK │ 收到 FIN │
│ V V │
│ LAST-ACK TIME-WAIT ────────────────┘
│ │ ACK │ 2×MSL 超时
│ V V
└────────────── CLOSED ◄─────┘
| 状态 | 含义 | 排查要点 |
|---|---|---|
| LISTEN | 服务端监听 | ss -lntp 查看端口 |
| SYN-SENT | 客户端已发 SYN | 若长期停留,多为对端无响应/被丢包 |
| ESTABLISHED | 连接建立 | 正常数据传输态 |
| FIN-WAIT-2 | 己方已 FIN,等对方 FIN | 大量堆积通常应用未关闭连接 |
| CLOSE-WAIT | 己方已收到对端 FIN,待本端关闭 | 大量 CLOSE-WAIT = 应用层泄漏 |
| TIME-WAIT | 等待 2×MSL 后彻底关闭 | 高并发短连接常见,见第 9 节 |
# 排查命令
ss -tan 'sport = :80' # 查看 80 端口 TCP 状态分布
ss -tan state time-wait # 只看 TIME-WAIT 连接
netstat -an | awk '/ESTABLISHED/{print $6}' | sort | uniq -c
5. 可靠传输:序列号/ACK/重传/滑动窗口
5.1 序列号与累计确认
TCP 通过序列号为字节流编号:每段报文携带首字节序号
seq,接收方回复累计确认ack = 期望收到的下一个字节序号(即已正确收到 ack-1 之前的所有字节)。发送方据此重传丢失数据。
| 机制 | 作用 | 说明 |
|---|---|---|
| 序列号 | 保证有序与去重 | 初始序列号 ISN 随机化防伪造 |
| ACK | 累计确认 | ack 表示"ack 之前全收到了" |
| 超时重传 RTO | 丢失恢复 | RTO 由 RTT 估算自适应(Karn 算法) |
| 快速重传 | 丢包早期恢复 | 收到 3 个重复 ACK 即重传,不等超时 |
| SACK | 选择性确认 | 精确告知哪些区间已到,避免全部重传 |
5.2 滑动窗口
发送方维护发送窗口(由接收方通告的 rwnd 与拥塞窗口 cwnd 决定),窗口内的数据可连续发送无需等待 ACK:
发送窗口(rwnd 决定)
│◄───────── 已发送并已确认 ─────────│── 已发送未确认 ──│── 可发送但未发送 ──│── 不可发送 ──│
▲ ▲
SND.UNA(最老未确认) SND.NXT(下一个要发)
# 滑动窗口发送逻辑(示意:GBN 与选择性重传的窗口管理)
def sliding_window_send(segments, window_size):
sent = {}
next_to_send = 0
base = 0
while base < len(segments):
# 填充窗口:最多发送到 base + window_size
while next_to_send < min(base + window_size, len(segments)):
send(segments[next_to_send]) # 发送段
sent[next_to_send] = None # 记录未确认
next_to_send += 1
ack = wait_ack() # 收到累计确认
base = ack # 窗口滑动
接收窗口
rwnd通过 TCP 首部 window 字段通告(配合 Window Scale 选项),上限可达约 1GB。窗口太小则吞吐受限:吞吐上限 ≈rwnd / RTT,即带宽时延积 BDP。
6. 流量控制
6.1 接收方主导的流控
流量控制防止发送方过快淹没接收方:接收方在 ACK 中通告剩余接收缓冲区
rwnd,发送方的发送窗口min(rwnd, cwnd)。rwnd=0时发送方停止,并周期性发 窗口探测(Zero Window Probe) 确认接收方恢复。
| 机制 | 主动方 | 目的 |
|---|---|---|
| 滑动窗口 | 接收方 | 匹配接收缓冲能力 |
| 拥塞控制 | 发送方 | 匹配网络承载能力 |
| 延迟确认 Delayed ACK | 接收方 | 合并 ACK 减少开销(常与 Nagle 联动) |
| Nagle 算法 | 发送方 | 小包聚合,避免大量微小报文 |
经典互锁问题:Nagle(发送方等 ACK 聚合小包)与延迟确认(接收方等数据再 ACK)相互作用会造成 40ms 级延迟。对交互式应用(如 SSH、低延迟 RPC)应关闭 Nagle(
TCP_NODELAY)。
# 设置 TCP_NODELAY 关闭 Nagle
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 低延迟
7. 拥塞控制:慢启动/拥塞避免/快速重传恢复
7.1 四阶段核心机制
拥塞控制由发送方通过
cwnd(拥塞窗口)实现,与接收方无关,目标是感知并适配网络承载能力。经典 Tahoe/Reno 框架(Linux 默认 CUBIC 是其演进):
| 阶段 | 行为 | 目的 |
|---|---|---|
| 慢启动 | 每 RTT 翻倍:cwnd = cwnd×2 | 指数探测可用带宽 |
| 拥塞避免 | 每 RTT 加 1:cwnd = cwnd + 1 | 接近容量时线性增长 |
| 快速重传 | 收到 3 个重复 ACK 即重传丢失段 | 快速恢复(不等超时) |
| 快速恢复 | 拥塞避免进入 cwnd 减半(乘法减) | AIMD 收敛公平 |
cwnd
│ ssthresh
│ /| |
│ / | /| |
│ / | / | | 拥塞避免(+1/RTT)
│ / | / | _|__
│/ |/ | / (丢包: cwnd = ssthresh, 重新慢启动)
└──────────────────→ RTT
# Reno 拥塞控制状态机(示意伪代码)
def reno_cwnd_update(event, cwnd, ssthresh):
if event == "ACK" and cwnd < ssthresh:
return cwnd * 2 # 慢启动:指数增长
if event == "ACK" and cwnd >= ssthresh:
return cwnd + 1 # 拥塞避免:加性增长
if event == "3_dup_ack": # 快速恢复:乘法减
return cwnd // 2
if event == "timeout": # 超时:回到慢启动
return 1
| 判据 | 处理 |
|---|---|
| 收到 3 个重复 ACK | 网络轻度拥塞,cwnd 减半,快速恢复 |
| RTO 超时 | 网络严重拥塞,ssthresh=cwnd/2,cwnd 回到 1 |
现代算法演进:CUBIC(Linux 默认,丢包后凹函数增长)、BBR(Google,基于带宽与延迟测量而非丢包)、Vegas/NewReno/SACK 等。BBR 尤其适合高带宽高延迟链路。
7.2 公平性:AIMD
所有 TCP 流都遵循 AIMD(加性增、乘性减),多个连接会收敛到公平分享带宽的平衡点。这正是拥塞控制"非线性博弈"的核心性质。
8. UDP 与 QUIC
8.1 UDP 特点
UDP 首部仅 8 字节:源端口、目的端口、长度、校验和。无连接、无重传、无拥塞控制,由应用自己控制时机与可靠性,因而延迟低、实现简单。
UDP 首部
| 源端口(16) | 目的端口(16) |
| 长度(16) | 校验和(16) |
| 应用 | 为什么用 UDP |
|---|---|
| DNS | 单次查询请求/响应,无连接成本 |
| 实时音视频 | 容忍丢包,拒绝重传造成的延迟抖动 |
| 游戏 | 低延迟优先,状态同步可预测 |
| QUIC | 在 UDP 之上自建可靠传输(见下) |
8.2 QUIC(HTTP/3 底层)
QUIC(RFC 9000)基于 UDP 实现类 TCP 的可靠传输,解决 TCP 的两大痛点:队头阻塞(HOL,TCP 单流丢包阻塞所有数据)与握手延迟(TLS 需要额外往返)。
| 特性 | TCP+TLS | QUIC |
|---|---|---|
| 握手 | 1-2 个 RTT(TLS1.3 前更多) | 1 个 RTT(0-RTT 恢复) |
| 队头阻塞 | 有(字节流单队列) | 无(多流独立) |
| 连接迁移 | 四元组绑定,换 IP 即断 | 连接 ID 不依赖 IP |
| 实现 | 内核 | 用户态(库实现,易迭代) |
生产上
HTTP/3即基于 QUIC;Linux 内核尚未原生实现 QUIC,实际由 ngtcp2/quic-go/lsquic 等库在用户态完成。
9. TIME_WAIT 与性能调优
9.1 TIME_WAIT 存在的意义
主动关闭方进入 TIME_WAIT,持续 2×MSL(默认 60s)。两个目的:1)保证最后的 ACK 丢失时能重发;2)让旧连接的报文在网络中彻底消失,避免其污染使用相同四元组的新连接。
| 现象 | 原因 | 处理 |
|---|---|---|
| 大量 TIME_WAIT | 高并发短连接(如 nginx 反向代理) | 开启端口复用,调大 net.ipv4.tcp_max_tw_buckets |
| 大量 CLOSE-WAIT | 应用未调用 close()(连接泄漏) | 查应用层代码,非内核问题 |
| 大量 SYN_RECV | 半连接队列满 / SYN Flood | 调大 tcp_max_syn_backlog,开启 SYN Cookies |
| RST 复位 | 对端不可达或应用主动拒绝 | 检查防火墙与监听状态 |
9.2 Linux 内核调优参数
# 常用 TCP 内核参数
sysctl -w net.ipv4.tcp_tw_reuse=1 # 允许复用 TIME_WAIT 中的连接(新连接)
sysctl -w net.ipv4.tcp_max_syn_backlog=1024 # 半连接队列上限
sysctl -w net.core.somaxconn=1024 # 全连接队列(listen backlog 上限)
sysctl -w net.ipv4.tcp_fin_timeout=30 # FIN-WAIT-2 超时(秒)
sysctl -w net.ipv4.tcp_sack=1 # 选择性确认
sysctl -w net.ipv4.tcp_rmem="4096 87380 6291456" # 接收缓冲区自动调优
| 调优参数 | 场景 | 说明 |
|---|---|---|
| tcp_tw_reuse | 高并发短连接 | 新连接可复用 TIME_WAIT 四元组(需时间戳开启) |
| tcp_fin_timeout | 大量 FIN-WAIT-2 | 缩短异常连接回收时间 |
| tcp_rmem/wmem | 高 BDP 链路 | 窗口大小影响吞吐上限 |
| somaxconn + backlog | 连接排队 | 应用层 listen(backlog) 须同步调大 |
9.3 性能模型与工具
关键公式:吞吐上限 = min(发送窗口, 拥塞窗口) / RTT。高延迟高带宽场景(如跨洲链路)必须用 BDP 计算窗口:
窗口 ≥ 带宽 × RTT,否则链路利用率不足。
# 性能测量工具
ss -tin 'sport = :443' # 查看 rtt、cwnd、mss、sack 等指标
iperf3 -c <host> -P 4 -w 1m # 测吞吐,指定窗口
netstat -s # 协议栈统计(重传率、丢包、RST)
排障思路:先看协议栈统计定位丢包/重传;再看状态分布定位连接堆积;最后用抓包(tcpdump/Wireshark)确认握手与重传细节。
参考文章
- RFC 9293 — Transmission Control Protocol (TCP)
- RFC 793 — TCP(原版)
- RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport
- Wikipedia — Transmission Control Protocol
- Wikipedia — TCP congestion control
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。