引言
前几篇我们学习了OSI七层模型和TCP/IP四层模型,理解了网络分层的概念。但这些都是抽象的理论。
现在我们要做的是:跟踪一个真实的数据包,看看它如何从你的浏览器,一步步到达目标服务器。
这就像跟踪一封信从投递到收信的完整过程,让我们看看网络世界中数据包的"快递之旅"。
场景设定
假设你在浏览器地址栏输入:http://www.example.com/index.html 并按下回车。
让我们跟随数据包的旅程,看看背后发生了什么!
准备工作:DNS域名解析
在真正发送HTTP请求之前,浏览器需要先知道 www.example.com 的IP地址。
DNS查询过程
1. 浏览器检查缓存
├─ 浏览器DNS缓存
├─ 操作系统DNS缓存
└─ hosts文件
2. 如果缓存没有,向DNS服务器查询
├─ 本地DNS服务器(运营商提供,如8.8.8.8)
├─ 根DNS服务器
├─ .com顶级域名服务器
└─ example.com权威DNS服务器
3. 获得IP地址:93.184.216.34
DNS查询是一个UDP请求(应用层协议),过程如下:
应用层:DNS查询请求
↓
传输层:UDP协议,端口53
↓
网络层:IP协议,目标8.8.8.8(Google DNS)
↓
链路层:以太网帧
↓
发送到DNS服务器
DNS响应:
93.184.216.34
现在浏览器知道了目标IP地址,可以发送HTTP请求了!
第1站:应用层 - 浏览器生成HTTP请求
浏览器生成一个HTTP GET请求:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
Accept: text/html,application/xhtml+xml
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive
关键信息:
- 请求方法:GET
- 请求路径:/index.html
- 协议版本:HTTP/1.1
- Host头:www.example.com(虚拟主机标识)
- User-Agent:浏览器标识
- Connection:keep-alive(保持连接)
此时的数据:只有HTTP请求本身,还没有任何网络信息。
第2站:传输层 - TCP协议
2.1 三次握手建立连接
在发送HTTP请求之前,需要先建立TCP连接:
客户端 服务器
│ │
│───── SYN (seq=x) ────────────>│ 第1次握手:请求连接
│ │
│<──── SYN+ACK (seq=y, ack=x+1) │ 第2次握手:同意连接
│ │
│───── ACK (ack=y+1) ──────────>│ 第3次握手:确认连接
│ │
│ 连接建立 │
三次握手的TCP报文段:
第1次:SYN=1, seq=1000
第2次:SYN=1, ACK=1, seq=2000, ack=1001
第3次:ACK=1, ack=2001
2.2 添加TCP头部
现在TCP层给HTTP请求添加TCP头部:
TCP头部(20字节):
┌────────────────────────────────┐
│ 源端口号:52341 │ 随机端口
├────────────────────────────────┤
│ 目标端口号:80 │ HTTP默认端口
├────────────────────────────────┤
│ 序列号(Sequence Number) │ 数据顺序
├────────────────────────────────┤
│ 确认号(Acknowledgment Number) │ 确认收到
├────────────────────────────────┤
│ 标志位:ACK=1, PSH=1 │ 推送数据
├────────────────────────────────┤
│ 窗口大小:65535 │ 流量控制
├────────────────────────────────┤
│ 校验和(Checksum) │ 错误检测
├────────────────────────────────┤
│ 紧急指针 │ (不常用)
└────────────────────────────────┘
关键字段:
- 源端口:52341(操作系统随机分配)
- 目标端口:80(HTTP标准端口)
- 序列号:用于保证数据顺序
- 确认号:用于确认收到对方数据
- 窗口大小:告诉对方自己能接收多少数据
此时的数据包:
[TCP头部 | HTTP请求]
第3站:网络层 - IP协议
3.1 添加IP头部
网络层给数据包添加IP头部:
IP头部(20字节):
┌────────────────────────────────┐
│ 版本:4(IPv4) │
├────────────────────────────────┤
│ 头部长度:20字节 │
├────────────────────────────────┤
│ 服务类型(TOS) │ 优先级
├────────────────────────────────┤
│ 总长度:HTTP请求长度+40 │
├────────────────────────────────┤
│ 标识(Identification) │ 分片标识
├────────────────────────────────┤
│ 标志位 + 片偏移 │ 分片控制
├────────────────────────────────┤
│ 生存时间(TTL):64 │ 防止循环
├────────────────────────────────┤
│ 协议:6(TCP) │ 上层协议
├────────────────────────────────┤
│ 头部校验和 │ 错误检测
├────────────────────────────────┤
│ 源IP地址:192.168.1.100 │ 你的电脑
├────────────────────────────────┤
│ 目标IP地址:93.184.216.34 │ example.com
└────────────────────────────────┘
关键字段:
- 源IP:192.168.1.100(你的内网IP)
- 目标IP:93.184.216.34(服务器公网IP)
- TTL:64(最多经过64个路由器)
- 协议:6表示TCP
此时的数据包:
[IP头部 | TCP头部 | HTTP请求]
3.2 路由选择
操作系统查看路由表,决定将数据包发送到哪里:
# 查看路由表
netstat -rn
# 输出示例
Destination Gateway Genmask
0.0.0.0 192.168.1.1 0.0.0.0 # 默认路由
192.168.1.0 0.0.0.0 255.255.255.0 # 本地网段
决策:
- 目标IP
93.184.216.34不在本地网段 - 使用默认路由,发送到网关
192.168.1.1(家用路由器)
但是,IP地址无法直接发送,需要知道网关的MAC地址!
第4站:数据链路层 - 以太网
4.1 ARP解析MAC地址
操作系统检查ARP缓存:
# 查看ARP缓存
arp -a
# 输出示例
192.168.1.1 at aa:bb:cc:dd:ee:ff on en0
如果缓存中没有,发送ARP请求:
ARP请求(广播):
"谁的IP是192.168.1.1?请告诉我你的MAC地址!"
↓
发送到局域网所有设备(广播MAC地址:FF:FF:FF:FF:FF:FF)
↓
路由器回复:
"我的IP是192.168.1.1,MAC地址是aa:bb:cc:dd:ee:ff"
4.2 添加以太网帧头和尾
现在数据链路层给数据包添加以太网帧头和尾:
以太网帧头(14字节):
┌────────────────────────────────┐
│ 目标MAC地址:aa:bb:cc:dd:ee:ff │ 路由器MAC
├────────────────────────────────┤
│ 源MAC地址:11:22:33:44:55:66 │ 你的网卡MAC
├────────────────────────────────┤
│ 类型:0x0800(IPv4) │ 上层协议
└────────────────────────────────┘
以太网帧尾(4字节):
┌────────────────────────────────┐
│ FCS(帧校验序列) │ CRC校验
└────────────────────────────────┘
此时的完整数据包(以太网帧):
[以太网头 | IP头 | TCP头 | HTTP请求 | 以太网尾]
14字节 20字节 20字节 ~200字节 4字节
总大小:约258字节
第5站:物理层 - 网线传输
5.1 转换为电信号
网卡将数字信号(0和1)转换为电信号:
0 → 低电平(0V)
1 → 高电平(+5V)
5.2 通过网线发送
数据包通过网线(双绞线)发送到路由器:
你的电脑 ──[网线]──> 路由器
8根铜线
Cat5e/Cat6
最大100米
传输速率:
- 千兆以太网:1000 Mbps
- 传输时间:约 2 微秒(258字节 ÷ 125 MB/s)
第6站:路由器转发
6.1 路由器接收数据包
路由器接收到以太网帧:
- 物理层:接收电信号,转换为比特流
- 数据链路层:检查目标MAC地址
aa:bb:cc:dd:ee:ff(是自己) - 去掉以太网头和尾
现在路由器看到的是:
[IP头 | TCP头 | HTTP请求]
6.2 查看IP头部
路由器检查IP头部:
- 目标IP:93.184.216.34
- TTL:64 → 63(减1)
6.3 查找路由表
路由器查找路由表,决定下一跳:
Destination Gateway Interface
0.0.0.0/0 10.255.255.1 WAN口
192.168.1.0/24 0.0.0.0 LAN口
决策:
- 目标IP不在本地网段
- 使用默认路由,发送到运营商网关
10.255.255.1
6.4 NAT地址转换
家用路由器执行NAT(Network Address Translation):
原始数据包:
源IP:192.168.1.100(内网IP)
源端口:52341
经过NAT后:
源IP:123.45.67.89(路由器公网IP)
源端口:12345(路由器分配的新端口)
NAT表记录:
123.45.67.89:12345 ↔ 192.168.1.100:52341
为什么需要NAT?
- 内网IP(192.168.x.x)无法在公网路由
- 节省公网IP地址
- 提供一定的安全性
6.5 重新封装并转发
路由器重新添加以太网头和尾:
[新以太网头 | IP头 | TCP头 | HTTP请求 | 新以太网尾]
↓
目标MAC:运营商网关的MAC地址
源MAC:路由器WAN口的MAC地址
发送到运营商网关!
第7站:公网传输
数据包在公网上传输,经过多个路由器:
你的路由器
↓
运营商接入网关
↓
运营商骨干路由器
↓
国际骨干网
↓
目标运营商骨干网
↓
目标数据中心路由器
↓
目标服务器
每经过一个路由器:
- 去掉以太网头和尾
- 检查IP目标地址
- TTL减1
- 查找路由表
- 重新添加以太网头和尾
- 转发到下一跳
使用traceroute查看路径:
traceroute www.example.com
# 输出示例
1 192.168.1.1 (192.168.1.1) 1.5 ms
2 10.255.255.1 (10.255.255.1) 3.2 ms
3 61.135.169.121 (61.135.169.121) 5.8 ms
4 202.97.33.54 (202.97.33.54) 8.4 ms
5 202.97.91.142 (202.97.91.142) 12.6 ms
6 93.184.216.34 (93.184.216.34) 35.2 ms
共经过6个路由器,总延迟35.2ms。
第8站:到达目标服务器
8.1 服务器接收数据包
服务器的网卡接收到以太网帧:
- 物理层:接收光信号(数据中心通常用光纤),转换为比特流
- 数据链路层:检查目标MAC地址(是自己),去掉以太网头尾
- 网络层:检查目标IP地址(93.184.216.34,是自己),去掉IP头
- 传输层:检查目标端口(80,HTTP服务),去掉TCP头
- 应用层:HTTP服务器(如Nginx)处理请求
8.2 TCP确认
服务器发送TCP ACK确认:
服务器 ────── ACK ────────> 客户端
"我收到你的HTTP请求了!"
8.3 处理HTTP请求
Nginx处理请求:
location /index.html {
root /var/www/html;
index index.html;
}
读取文件 /var/www/html/index.html。
8.4 生成HTTP响应
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Thu, 21 Nov 2025 08:00:00 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1256
Connection: keep-alive
<!DOCTYPE html>
<html>
<head>
<title>Example Domain</title>
</head>
<body>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples...</p>
</body>
</html>
8.5 响应返回
HTTP响应沿着来时的路径返回:
应用层:HTTP响应
↓
传输层:TCP头部(源端口80,目标端口52341)
↓
网络层:IP头部(源IP 93.184.216.34,目标IP 123.45.67.89)
↓
数据链路层:以太网帧
↓
物理层:光信号/电信号
↓
通过公网传输
↓
到达你的路由器
↓
NAT反向转换(123.45.67.89:12345 → 192.168.1.100:52341)
↓
到达你的电脑
↓
浏览器接收HTTP响应,渲染网页
完整旅程总结
┌──────────────────────────────────────────────────────────────┐
│ 你的电脑(客户端) │
├──────────────────────────────────────────────────────────────┤
│ 应用层:生成HTTP GET请求 │
│ 传输层:添加TCP头部(端口52341 → 80) │
│ 网络层:添加IP头部(192.168.1.100 → 93.184.216.34) │
│ 链路层:添加以太网头尾(你的MAC → 路由器MAC) │
│ 物理层:转换为电信号,通过网线发送 │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 家用路由器 │
├──────────────────────────────────────────────────────────────┤
│ 物理层:接收电信号 │
│ 链路层:检查MAC地址,去掉以太网头尾 │
│ 网络层:NAT转换(内网IP → 公网IP),查找路由表 │
│ 链路层:重新封装以太网帧 │
│ 物理层:转换为电信号/光信号,发送到运营商 │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 公网(多个路由器) │
├──────────────────────────────────────────────────────────────┤
│ 运营商接入网关 → 骨干网 → 国际网络 → 目标数据中心 │
│ 每一跳:去掉/添加以太网头尾,TTL减1,查找路由表,转发 │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 目标服务器(example.com) │
├──────────────────────────────────────────────────────────────┤
│ 物理层:接收光信号 │
│ 链路层:检查MAC地址,去掉以太网头尾 │
│ 网络层:检查IP地址(93.184.216.34),去掉IP头 │
│ 传输层:检查端口号(80),去掉TCP头 │
│ 应用层:Nginx处理HTTP请求,生成响应 │
└──────────────────────────────────────────────────────────────┘
↓
HTTP响应沿着来时的路径返回
数据包大小变化
应用层: HTTP请求(约200字节)
传输层:+ 20 TCP头部
网络层:+ 20 IP头部
链路层:+ 18 以太网头(14)+ 尾(4)
────────────────────────────────
总计: 258 字节
响应数据包:
应用层: HTTP响应(约1500字节)
传输层:+ 20 TCP头部
网络层:+ 20 IP头部
链路层:+ 18 以太网头尾
────────────────────────────────
总计: 1558 字节
时间消耗分析
假设从北京访问美国服务器:
DNS解析: ~20ms
TCP三次握手: ~150ms(3次×50ms RTT)
HTTP请求发送: ~1ms(本地→路由器)
公网传输: ~50ms(光速延迟 + 路由器处理)
服务器处理: ~10ms
HTTP响应返回: ~50ms
────────────────────────────
总计: ~281ms
优化方向:
- DNS缓存(省20ms)
- Keep-Alive长连接(省150ms握手)
- CDN加速(省100ms传输)
- 服务器优化(省5ms处理)
实战:使用tcpdump抓包查看
在Linux/macOS上使用tcpdump查看真实的数据包:
# 抓取80端口的HTTP请求
sudo tcpdump -i en0 -nn -X port 80
# 输出示例(简化)
15:30:45.123456 IP 192.168.1.100.52341 > 93.184.216.34.80: Flags [P.], seq 1:201, ack 1, win 65535, length 200
0x0000: 4500 00e8 1234 4000 4006 xxxx c0a8 0164 # IP头
0x0010: 5db8 d822 cc75 0050 xxxx xxxx xxxx xxxx # TCP头
0x0020: 4745 5420 2f69 6e64 6578 2e68 746d 6c20 # HTTP: GET /index.html
0x0030: 4854 5450 2f31 2e31 0d0a # HTTP/1.1
总结
核心要点
数据包封装过程
- 应用层生成数据
- 逐层添加头部(传输层、网络层、链路层)
- 物理层转换为信号传输
每一层的作用
- 链路层:MAC寻址,在同一网段传输
- 网络层:IP寻址,跨网段路由转发
- 传输层:端口号,可靠传输(TCP)
- 应用层:HTTP等应用协议
路由器的作用
- 去掉链路层头尾
- 检查网络层IP地址
- 查找路由表,决定下一跳
- 重新封装链路层头尾
- TTL减1
NAT的作用
- 内网IP转换为公网IP
- 节省公网IP地址
- 记录映射关系,返回时反向转换
下一篇预告
下一篇我们将深入学习 IP地址与子网划分,理解如何规划网络地址,为微服务集群分配IP段。
思考题:
- 如果中间某个路由器故障了,数据包会怎样?
- TTL为什么要减1?如果TTL减到0会发生什么?
- 为什么需要既有IP地址又有MAC地址?
欢迎在评论区分享你的思考!