TCP/UDP 网络协议教程 / 03-TCP 头部详解
03 - TCP 头部详解
3.1 TCP 头部结构
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |C|E|U|A|P|R|S|F| |
| Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window |
| (4) | (3) |R|E|G|K|H|T|N|N| (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (0-40 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
3.2 头部字段详解
| 字段 | 长度 | 说明 |
|---|---|---|
| Source Port | 16 bit | 源端口号 (0-65535) |
| Destination Port | 16 bit | 目的端口号 (0-65535) |
| Sequence Number | 32 bit | 数据的序列号 |
| Acknowledgment Number | 32 bit | 期望收到的下一个序列号 |
| Data Offset | 4 bit | 头部长度(单位:4字节) |
| Reserved | 3 bit | 保留位 |
| Flags | 9 bit | 控制标志 |
| Window | 16 bit | 接收窗口大小 |
| Checksum | 16 bit | 校验和 |
| Urgent Pointer | 16 bit | 紧急指针 |
3.3 TCP 标志位 (Flags)
| 标志 | 全称 | 说明 |
|---|---|---|
| SYN | Synchronize | 同步序列号,建立连接 |
| ACK | Acknowledgment | 确认号有效 |
| FIN | Finish | 发送方数据发送完毕 |
| RST | Reset | 重置连接 |
| PSH | Push | 推送数据,立即交付 |
| URG | Urgent | 紧急数据,Urgent Pointer 有效 |
| CWR | Congestion Window Reduced | 拥塞窗口减少 |
| ECE | ECN-Echo | ECN 回显 |
三次握手标志位:
Client → Server: SYN=1, Seq=x
Server → Client: SYN=1, ACK=1, Seq=y, Ack=x+1
Client → Server: ACK=1, Seq=x+1, Ack=y+1
四次挥手标志位:
Client → Server: FIN=1, ACK=1, Seq=u, Ack=v
Server → Client: ACK=1, Seq=v, Ack=u+1
Server → Client: FIN=1, ACK=1, Seq=w, Ack=u+1
Client → Server: ACK=1, Seq=u+1, Ack=w+1
3.4 序列号与确认号
序列号 (Sequence Number)
# 序列号的工作方式
# 初始序列号 (ISN) 是随机生成的
# 发送数据时,序列号递增
# 假设 ISN = 1000,发送 100 字节数据:
# 发送第1个段:Seq=1000, 数据=100字节
# 发送第2个段:Seq=1100, 数据=100字节
# 发送第3个段:Seq=1200, 数据=100字节
def calculate_next_seq(current_seq, data_len):
"""计算下一个序列号"""
return current_seq + data_len
# 示例
seq = 1000
for i in range(3):
next_seq = calculate_next_seq(seq, 100)
print(f"发送: Seq={seq}, 下一个Seq={next_seq}")
seq = next_seq
确认号 (Acknowledgment Number)
确认号 = 期望收到的下一个字节的序列号
示例:
Host A 发送:Seq=1000, 100字节
Host B 回复:Ack=1100(表示:我已收到 1000-1099,期望收到 1100)
Host A 发送:Seq=1100, 200字节
Host B 回复:Ack=1300(表示:我已收到 1100-1299,期望收到 1300)
3.5 窗口大小 (Window Size)
# 窗口大小用于流量控制
# 接收方通过窗口大小告诉发送方:我还能接收多少数据
def flow_control_example():
"""窗口大小变化示例"""
receiver_buffer = 10000 # 接收缓冲区大小
received_data = 0
print(f"初始窗口大小: {receiver_buffer}")
# 模拟数据接收
for i in range(5):
incoming_data = 2000
received_data += incoming_data
window = max(0, receiver_buffer - received_data)
print(f"接收 {incoming_data} 字节后,窗口大小: {window}")
# 模拟应用层读取数据
if received_data > 5000:
read_size = 3000
received_data -= read_size
print(f" 应用层读取 {read_size} 字节,窗口恢复: {receiver_buffer - received_data}")
flow_control_example()
输出:
初始窗口大小: 10000
接收 2000 字节后,窗口大小: 8000
接收 2000 字节后,窗口大小: 6000
接收 2000 字节后,窗口大小: 4000
接收 2000 字节后,窗口大小: 2000
应用层读取 3000 字节,窗口恢复: 5000
接收 2000 字节后,窗口大小: 3000
3.6 校验和 (Checksum)
def tcp_checksum(src_ip, dst_ip, tcp_segment):
"""计算 TCP 校验和(含伪头部)"""
import struct
# 伪头部
src = socket.inet_aton(src_ip)
dst = socket.inet_aton(dst_ip)
placeholder = 0
protocol = 6 # TCP
tcp_len = len(tcp_segment)
pseudo_header = struct.pack('!4s4sBBH', src, dst, placeholder, protocol, tcp_len)
# 计算校验和
data = pseudo_header + tcp_segment
if len(data) % 2:
data += b'\x00'
checksum = 0
for i in range(0, len(data), 2):
word = (data[i] << 8) + data[i+1]
checksum += word
checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum = ~checksum & 0xFFFF
return checksum
TCP 校验和覆盖范围:
┌─────────────────────────────────────┐
│ 伪头部 (12 字节) │
│ ┌─────────────────────────────┐ │
│ │ 源 IP (4) │ 目的 IP (4) │ │
│ │ 保留 (1) │ 协议 (1) │ 长度(2)│ │
│ └─────────────────────────────┘ │
├─────────────────────────────────────┤
│ TCP 头部 + 数据 │
└─────────────────────────────────────┘
3.7 TCP 选项 (Options)
| 选项 | 类型 | 长度 | 说明 |
|---|---|---|---|
| EOL | 0 | 1 | 选项结束 |
| NOP | 1 | 1 | 填充 |
| MSS | 2 | 4 | 最大段大小 |
| Window Scale | 3 | 3 | 窗口缩放因子 |
| SACK Permitted | 4 | 2 | 允许选择性确认 |
| SACK | 5 | 可变 | 选择性确认块 |
| Timestamp | 8 | 10 | 时间戳 |
MSS 协商示例:
Client → Server: SYN, MSS=1460
Server → Client: SYN+ACK, MSS=1460
实际 MSS = min(1460, 1460) = 1460 字节
3.8 头部解析代码
import struct
import socket
def parse_tcp_header(data):
"""解析 TCP 头部"""
if len(data) < 20:
return None
# 解析固定头部(20字节)
fields = struct.unpack('!HHIIBBHHH', data[:20])
src_port = fields[0]
dst_port = fields[1]
seq_num = fields[2]
ack_num = fields[3]
data_offset = (fields[4] >> 4) * 4
flags = fields[5]
window = fields[6]
checksum = fields[7]
urgent_ptr = fields[8]
# 解析标志位
flag_names = []
if flags & 0x01: flag_names.append('FIN')
if flags & 0x02: flag_names.append('SYN')
if flags & 0x04: flag_names.append('RST')
if flags & 0x08: flag_names.append('PSH')
if flags & 0x10: flag_names.append('ACK')
if flags & 0x20: flag_names.append('URG')
return {
'src_port': src_port,
'dst_port': dst_port,
'seq_num': seq_num,
'ack_num': ack_num,
'data_offset': data_offset,
'flags': flag_names,
'window': window,
'checksum': f'0x{checksum:04x}',
'urgent_pointer': urgent_ptr,
'options': data[20:data_offset]
}
# 使用示例
header = parse_tcp_header(tcp_data)
if header:
print(f"{header['src_port']} → {header['dst_port']}")
print(f"Seq={header['seq_num']}, Ack={header['ack_num']}")
print(f"Flags: {', '.join(header['flags'])}")
print(f"Window: {header['window']}")
3.9 注意事项
⚠️ 序列号溢出:32 位序列号在高速网络中可能回绕,需要使用 PAWS(Protection Against Wrapped Sequences)机制
⚠️ 窗口缩放:Window Scale 选项只在 SYN 包中协商,连接建立后不可更改
⚠️ 头部长度:Data Offset 字段最大值为 15,因此 TCP 头部最大为 60 字节
3.10 业务场景
| 场景 | 关注的头部字段 |
|---|---|
| 防火墙规则 | 源/目的端口、标志位 |
| 负载均衡 | 源/目的端口、IP 地址 |
| 抓包分析 | 序列号、确认号、窗口大小 |
| 性能优化 | 窗口大小、MSS、SACK |
| 安全审计 | 标志位组合(如 SYN Flood) |
3.11 扩展阅读
下一章:04 - TCP 连接管理 - 三次握手与四次挥手