引言

前几篇我们学习了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 路由器接收数据包

路由器接收到以太网帧:

  1. 物理层:接收电信号,转换为比特流
  2. 数据链路层:检查目标MAC地址 aa:bb:cc:dd:ee:ff(是自己)
  3. 去掉以太网头和尾

现在路由器看到的是:

[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站:公网传输

数据包在公网上传输,经过多个路由器:

你的路由器
   ↓
运营商接入网关
   ↓
运营商骨干路由器
   ↓
国际骨干网
   ↓
目标运营商骨干网
   ↓
目标数据中心路由器
   ↓
目标服务器

每经过一个路由器

  1. 去掉以太网头和尾
  2. 检查IP目标地址
  3. TTL减1
  4. 查找路由表
  5. 重新添加以太网头和尾
  6. 转发到下一跳

使用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 服务器接收数据包

服务器的网卡接收到以太网帧:

  1. 物理层:接收光信号(数据中心通常用光纤),转换为比特流
  2. 数据链路层:检查目标MAC地址(是自己),去掉以太网头尾
  3. 网络层:检查目标IP地址(93.184.216.34,是自己),去掉IP头
  4. 传输层:检查目标端口(80,HTTP服务),去掉TCP头
  5. 应用层: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

总结

核心要点

  1. 数据包封装过程

    • 应用层生成数据
    • 逐层添加头部(传输层、网络层、链路层)
    • 物理层转换为信号传输
  2. 每一层的作用

    • 链路层:MAC寻址,在同一网段传输
    • 网络层:IP寻址,跨网段路由转发
    • 传输层:端口号,可靠传输(TCP)
    • 应用层:HTTP等应用协议
  3. 路由器的作用

    • 去掉链路层头尾
    • 检查网络层IP地址
    • 查找路由表,决定下一跳
    • 重新封装链路层头尾
    • TTL减1
  4. NAT的作用

    • 内网IP转换为公网IP
    • 节省公网IP地址
    • 记录映射关系,返回时反向转换

下一篇预告

下一篇我们将深入学习 IP地址与子网划分,理解如何规划网络地址,为微服务集群分配IP段。


思考题

  1. 如果中间某个路由器故障了,数据包会怎样?
  2. TTL为什么要减1?如果TTL减到0会发生什么?
  3. 为什么需要既有IP地址又有MAC地址?

欢迎在评论区分享你的思考!