大家好,我是程序员大海
系列: 从 0 开始学习计算机网络
适合读者: 计算机专业学生、网络基础薄弱的开发者、Java 后端学习者
本文关键词: 数据封装、解封装、TCP 段、IP 包、以太网帧、Java 后端
一、引言:程序发出的一段数据,最后变成了什么?
在 Java 后端开发中,我们可能只写了几行代码,就向服务器发送了一次 HTTP 请求。但这段请求真正离开电脑时,并不是简单地原样塞进网线。
网络协议栈会按照各层协议的规则处理数据:传输层可能添加 TCP 首部,网络层添加 IP 首部,链路层再把 IP 包放进当前链路使用的帧中。接收端收到数据后,会由相应协议逐层处理,最后把应用需要的数据交给应用程序。
这个过程叫作封装(Encapsulation)与解封装(Decapsulation)。
本文以 HTTP/1.1 over TCP、IPv4、以太网 作为教学例子。实际网络可能使用 IPv6、Wi-Fi 或其他链路技术;HTTP/3 则使用基于 UDP 的 QUIC,不能把本文的 TCP 示例直接套用到所有 HTTP 版本上。
二、学习目标
读完本文,你应该能够:
- 解释封装和解封装的基本含义;
- 区分应用数据、TCP 段、IP 包和链路层帧;
- 理解 TCP 首部、IP 首部和以太网首部分别解决什么问题;
- 解释为什么数据经过路由器后,链路层地址通常会变化;
- 使用 Java 请求示例和 Wireshark,初步观察网络通信。
三、先认识几个基本术语
在学习封装之前,先记住下面几个名称。
| 名称 | 所属层次(常见教学划分) | 可以先怎么理解 |
|---|---|---|
| 应用数据 / HTTP 消息 | 应用层 | 应用想传递的请求或响应内容 |
| TCP 段(TCP segment) | 传输层 | TCP 首部加上本次承载的数据 |
| IP 包 / IP 数据报(IP packet / datagram) | 网络层 | IP 首部加上其承载的上层数据 |
| 帧(frame) | 数据链路层 | 当前链路协议组织和传输的数据单位 |
不同教材对"包""分组""数据报"等中文术语的使用可能略有区别。本文会尽量使用"TCP 段、IP 包、以太网帧"来区分它们。
四、什么是数据封装?
可以把封装理解为:每一层根据自己协议的要求,为上层交付的数据添加控制信息,或者按照本层规则组织数据。
例如,在一个简化的 HTTP/1.1 over TCP、IPv4、以太网场景中,发送端可以按下面的逻辑理解:

4.1 第一步:应用层准备 HTTP 消息
假设客户端想请求一个接口:
http
GET /api/hello HTTP/1.1
Host: 192.168.1.20:8080
Connection: close
这是一个简化的 HTTP/1.1 请求示例。真实请求通常还会包含其他请求头;具体内容取决于客户端、请求方法和目标服务。
HTTP 负责定义应用层请求和响应的语义及消息规则。它本身不负责把比特信号送到网线或无线介质上。
4.2 第二步:传输层处理数据
在本文的例子中,HTTP/1.1 使用 TCP 传输。TCP 会在数据前添加 TCP 首部,其中包含源端口、目的端口、序列号、确认号、标志位等字段。
这些字段服务于 TCP 的通信机制,例如识别通信端点、组织字节流、确认数据和进行重传等。
关键点:TCP 提供的是有序字节流服务。 应用程序一次写入的数据,并不保证对应一个独立的 TCP 段。TCP 可以根据 MSS、拥塞控制、发送时机等因素分段,也可能把多次应用写入的数据合并发送。
4.3 第三步:网络层添加 IP 首部
IP 层会在上层数据前添加 IP 首部。以 IPv4 为例,首部包含源 IP 地址、目的 IP 地址、TTL、上层协议标识等信息。
IP 的核心任务是让数据报能够在互联的网络中被寻址和转发。IP 本身不提供 TCP 那样的端到端可靠、有序字节流服务。
4.4 第四步:数据链路层组织成帧
在以太网场景中,链路层会把 IP 包放进以太网帧的载荷部分,并添加以太网帧首部以及帧校验序列(FCS)等字段。
以太网首部包含源 MAC 地址、目的 MAC 地址和类型等信息。MAC 地址用于当前链路上的帧交付;它与 IP 地址承担的职责不同。
需要注意:图中以太网帧只是一个具体例子。如果当前链路使用 Wi-Fi 或其他技术,链路层帧格式和字段就会不同。
五、解封装:接收端如何把数据交给应用?
接收端收到链路层帧后,会由网络协议栈进行相应处理。对于本文的简化例子,可以按以下逻辑理解:

- 链路层处理帧: 检查并处理当前链路上的帧,取出其中承载的 IP 包。
- 网络层处理 IP 包: 检查 IP 首部,判断数据报是否应交给本机或继续转发。
- 传输层处理 TCP 数据: TCP 根据连接信息和序列信息处理收到的数据,并向应用提供有序字节流。
- 应用层解析消息: HTTP 组件按照对应 HTTP 版本的消息规则解析请求或响应。
这里说的"逐层解封装"是一个帮助理解的逻辑模型,不意味着每一步都由独立硬件完成,也不意味着应用最终看到的边界与底层报文边界完全相同。
六、数据经过路由器时,哪些信息会变化?
这是理解封装时非常重要的一点。
链路层帧通常只在当前链路上有效。 当 IP 包需要经过路由器转发到另一个网络时,路由器通常会处理收到的链路层帧,取出 IP 包;再根据路由选择出口,并使用出口链路对应的链路层格式重新封装。

假设客户端要访问另一个网段的服务器:
- 客户端发出的第一帧,目的 MAC 地址通常是本地默认网关的 MAC 地址,而不是远端服务器的 MAC 地址。
- 路由器处理 IP 包后,会在出口链路上重新封装帧,使用该链路所需的源 MAC 和目的 MAC 地址。
- 在没有 NAT 等地址转换的简化场景中,源 IP 和目的 IP 通常保持不变;但 IPv4 的 TTL 会在路由转发时递减,IPv6 则使用 Hop Limit。
所以,MAC 地址主要解决当前链路上的交付,IP 地址用于跨网络寻址和转发。 不要把"目的 IP 地址"和"当前帧的目的 MAC 地址"混为一谈。
七、一个容易忽略的问题:数据边界并不一一对应
初学者很容易想当然地认为:
一次 HTTP 请求 = 一个 TCP 段 = 一个 IP 包 = 一个以太网帧。
这并不正确。
原因包括:
- HTTP 消息可能比较大,需要通过多个 TCP 段承载;
- TCP 面向字节流,不保留应用程序每次
write()的消息边界; - IP 包的大小受到链路 MTU 等条件影响,具体传输过程可能涉及分片或其他处理;
- 一个 IP 包通常会被放进某个链路层帧中,但在经过路由器时会重新封装成下一条链路的帧;
- 网卡卸载功能、抓包位置和抓包工具的展示方式,也可能影响你在抓包软件中看到的分段现象。
这里要分清两个层面:
- 逻辑上的封装关系: TCP 段作为 IP 包的载荷,IP 包又作为链路层帧的载荷。
- 实际发送时的数量关系: 一条应用消息最终对应多少 TCP 段、IP 包或链路层帧,取决于消息大小、协议行为和网络条件。
八、结合 Java 后端发送一次 HTTP 请求
下面用 Java 11 及以上版本的 HttpClient 展示应用程序如何发起 HTTP/1.1 请求。
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpRequestDemo {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"http://192.168.1.20:8080/api/hello"))
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString());
System.out.println("状态码:" + response.statusCode());
System.out.println("响应体:" + response.body());
}
}
运行前提
- JDK 11 或更高版本;
- 将
192.168.1.20:8080/api/hello替换为真实可访问的 HTTP/1.1 服务地址; - 服务端需要已经启动,并且对应接口确实存在;
- 客户端所在网络需要能够访问服务端。
本文没有假设该代码已经运行成功,也不预设固定的响应内容。
这段代码只负责在应用层构造和发送 HTTP 请求。TCP 首部、IP 首部以及链路层帧并不是由业务代码手动拼出来的,而是由 HTTP 客户端实现、操作系统协议栈和网络接口等共同处理。
九、动手观察:用 Wireshark 看一条请求
如果想把图中的概念和实际通信联系起来,可以使用 Wireshark 抓包工具观察自己有权访问的测试服务。
操作步骤
- 在客户端打开 Wireshark,选择实际承载目标流量的网络接口开始抓包。
- 访问自己控制的测试服务,或运行上面的 Java 示例。
- 使用显示过滤器缩小范围,例如:
text
ip.addr == 192.168.1.20 && tcp.port == 8080
将 192.168.1.20 替换为实际服务端 IP。
- 选择一条合适的数据包,展开协议详情,观察 Ethernet、Internet Protocol、Transmission Control Protocol 等层级。
- 如果流量是明文 HTTP/1.1,并且 Wireshark 能正确识别该协议,可以进一步查看 HTTP 请求内容。
抓包时要注意
- 如果客户端和服务端在同一台机器上,流量可能走回环接口,需要选择适当的抓包接口。
- 如果使用 HTTPS,抓包通常仍能观察到 IP、TCP 或 QUIC 等传输相关信息,但没有相应解密条件时,不能直接把加密后的应用数据当作明文 HTTP 内容阅读。
- Wireshark 中显示的报文不一定等于应用程序一次发送的完整消息。TCP 分段、重组和网卡卸载都会影响观察结果。
这项练习的目标不是强行对上每一行数据,而是学会把协议详情中的字段和各层职责联系起来。
十、常见误区
误区 1:每一层都会把原数据完整复制一遍
不准确。封装通常是添加本层控制信息或按本层协议组织数据,并不是每一层都复制一份完整数据。
误区 2:TCP 一次发送就对应一个 TCP 段
不准确。TCP 提供字节流服务,应用写入边界与 TCP 段边界没有固定的一一对应关系。
误区 3:IP 包从源主机到目标主机时,MAC 地址始终不变
不准确。MAC 地址用于链路层交付。跨路由器转发时,链路层帧通常会在每一跳重新封装。
误区 4:TCP 和 IP 都能保证数据可靠到达
不准确。TCP 提供可靠、有序的字节流服务;IP 本身不提供端到端可靠交付保证。即使使用 TCP,应用仍需正确处理连接错误、超时和业务层重试等问题。
误区 5:抓包中看到一个 HTTP 请求,就一定只会看到一个 TCP 包
不准确。请求可能跨越多个 TCP 段;抓包软件也可能展示重组后的应用层内容。应区分原始报文、TCP 字节流和应用层消息。
十一、本篇总结
本篇最重要的内容可以归纳为:
- 封装: 发送端按照协议规则逐层添加控制信息或组织数据。
- 解封装: 接收端处理相应协议头部,并把数据交给上层。
- 数据单位: HTTP 消息、TCP 段、IP 包、链路层帧是不同层次的概念。
- 路由转发: 链路层帧通常逐跳变化;在无 NAT 的简化场景中,源 IP 和目的 IP 通常保持不变,但 TTL 或 Hop Limit 会变化。
- 边界问题: 一次应用写入不等于一个 TCP 段,也不等于一个 IP 包或链路层帧。
- 开发实践: Java 业务代码通常调用 HTTP 客户端;底层封装由协议实现和操作系统网络栈处理。
下一篇预告:《【计算机网络】第 3 篇:MAC 地址、IP 地址和端口有什么区别?一次讲清楚》。我们会结合客户端访问 Java 服务的例子,弄清楚这三种标识分别解决什么问题。
参考资料
- RFC 9110:HTTP Semantics
- RFC 9293:Transmission Control Protocol (TCP)
- RFC 791:Internet Protocol (IPv4)
- RFC 894:A Standard for the Transmission of IP Datagrams over Ethernet Networks
本文是面向初学者的概念讲解。图片为原创教学示意图,省略了部分字段和特殊情况;真实报文格式与行为应以对应协议标准和具体网络环境为准。