OSI 七层模型的每一层作用、在代码编程中的具体体现
✔ 应用层是你平时写得最多的(HTTP、MQTT、DDS)✔ 表示层是序列化/反序列化(Protobuf、JSON)✔ 会话层是保持连接(WebSocket、MQTT、DDS)✔ 传输层是端口、TCP/UDP、粘包拆包✔ 网络层是 IP 地址、ICMP、路由✔ 链路层是 MAC、ARP、以太帧✔ 物理层是信号、比特流MTU(Maximum Transmission Unit)= 最大传输单元👉它
文章目录
- 🏛️ OSI 七层模型:作用 & 代码开发中的体现
- 第 7 层:应用层(Application Layer)
- 第 6 层:表示层(Presentation Layer)
- 第 5 层:会话层(Session Layer)
- 第 4 层:传输层(Transport Layer)
- 第 3 层:网络层(Network Layer)
- 第 2 层:数据链路层(Data Link Layer)
- 第 1 层:物理层(Physical Layer)
- 🎯 “拆包 / 组包”属于哪一层?
- 🔥 在你日常 C++/Qt/FastDDS 开发中,各层体现在哪里?
- 🧠 总结(非常关键)
- 🟦 1. 什么是 MTU?
- 🟦 2. UDP 发送“一包有效数据”需要被包装多少层?(非常关键)
- ✔ **最终从应用发出去的完整链路层帧结构如下:**
- 🧱 **UDP 包数据的最小开销是多少?**
- 🧮 **以太网 + IPv4 + UDP 的实际包大小计算**
- 🟦 3. MTU 限制能放多少 **有效数据(Payload)**?
- 🟦 4. IPv6 情况
- 🟦 5. 总结:一包 UDP 数据经过几层封装?
- 🟩 最终发送出去的包 **必须包含:**
- 🟩 为什么 **UDP 不可能粘包?**
- 🟥 为什么 **TCP 一定会粘包?(也会拆包)**
- 🟦 一、UDP 为什么不会粘包?
- ✅ **结论:UDP 不会粘包,也不会拆包。
- 🟥 二、那为什么 TCP 会粘包?
- 🟦 TCP 粘包 + 拆包 的三个主要原因
- 🟦 关键结论:
- 🟩 那么 TCP 如何处理粘包?(非常重要)
- 🟦 总结对比表
下面我会完整解释 OSI 七层模型的每一层作用、在代码编程中的具体体现、拆包/组包属于哪一层。
我会结合你日常 C++ / Qt / 网络开发的视角讲解,让你能“看到”每一层在代码里出现在哪里。
🏛️ OSI 七层模型:作用 & 代码开发中的体现
我用一句话+工程场景快速说明每一层。
第 7 层:应用层(Application Layer)
📌 作用
提供应用程序直接使用的高级协议和接口。
📌 代码中体现
你写的绝大多数逻辑都在这一层!
- HTTP 请求/响应
- WebSocket 逻辑
- MQTT publish/subscribe
- FastDDS/Protobuf 数据结构
- Qt 网络高层模块(QNetworkAccessManager)
📌 示例
QNetworkAccessManager mgr;
QNetworkRequest req(QUrl("http://example.com"));
mgr.get(req);
你调用的是HTTP客户端,属于 应用层协议的调用。
第 6 层:表示层(Presentation Layer)
📌 作用
- 数据格式化
- 序列化/反序列化
- 加密/解密
- 压缩/解压
📌 代码中体现
应用最常出现的就是 序列化 / 数据格式转换:
- JSON / XML 解析
- Protobuf 序列化(FastDDS IDL)
- Base64 编码/解码
- TLS/SSL 加密传输
📌 示例(Protobuf)
message Person {
string name = 1;
int32 id = 2;
}
这是典型的表示层工作:结构编码、格式定义、二进制序列化。
第 5 层:会话层(Session Layer)
📌 作用
管理会话、连接维持、断线重连、状态保持。
📌 代码中体现
你使用的:
- WebSocket 长连接
- MQTT 会话(Session)
- token 登录后保持状态
- FastDDS Participant 的 Session 管理
这些都是会话层工作。
📌 示例
websocket->open(QUrl("ws://server"));
保持连接、重连逻辑,就属于会话层。
第 4 层:传输层(Transport Layer)
📌 作用
提供端到端通信,端口号就在这一层:
- TCP(可靠流)
- UDP(不可靠包)
📌 代码中体现
如果你用以下 API,就是在“自己实现传输层”:
QTcpSocket
QUdpSocket
select/epoll
recv/send
你在处理:
- 分片重组(TCP 自动)
- 重传(TCP 自动)
- 超时(TCP/UDP 都需要处理)
第 3 层:网络层(Network Layer)
📌 作用
管理网络地址与路由
- IPv4 / IPv6
- ICMP
- 路由转发
- ARP/NDP 逻辑与邻居发现(辅助)
📌 代码中体现
一般开发者不会直接写网络层,但常用到:
- 设置 IP、子网掩码
- 构造原始 IP 报文(Raw Socket)
- Ping (ICMP)
- 路由表操作
📌 示例(构造 ICMP Ping)
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
这是手写网络层协议。
第 2 层:数据链路层(Data Link Layer)
📌 作用
在局域网内进行帧的传输
- MAC 地址
- ARP 协议
- 交换机学习 MAC
- VLAN / PPPoE
📌 代码中体现
一般程序员不写二层,但在下面情况会接触到:
- 抓包(pcap/WinPcap/NPcap)
- 构造以太网帧
- 解析 MAC / VLAN 标签
- ARP 处理或分析
- 二层过滤器(BPF)
📌 示例(抓包)
pcap_loop(handle, 0, packet_handler, NULL);
在回调里你能看到完整的二层帧结构:
| Ethernet Header | IP Header | TCP Header | Data |
第 1 层:物理层(Physical Layer)
📌 作用
负责比特流传输
- 电信号
- 光信号
- 网线/光纤
- 调制与解调
📌 代码中体现
几乎不会写,但会遇到:
- 串口波特率
- 网络带宽与 MTU 限制
- 信号质量
- 硬件丢包统计
🎯 “拆包 / 组包”属于哪一层?
取决于你拆/组的是什么协议:
| 你处理的协议 | 属于哪一层 | 拆包/组包属于 | 示例 |
|---|---|---|---|
| Qt / HTTP / MQTT / WebSocket | 应用层 | 7 层 | JSON、MQTT Publish、HTTP Header |
| 自己封装协议(自定义命令帧) | 表示层/应用层 | 6/7 层 | “头+长度+CRC+数据” |
| UDP/TCP 粘包、拆包 | 传输层 | 4 层 | Stream → Message |
| 实现 ICMP/IP 头部 | 网络层 | 3 层 | Raw Socket |
| 以太网帧封装、抓包解析 | 数据链路层 | 2 层 | Ethernet + ARP + IP |
🔥 在你日常 C++/Qt/FastDDS 开发中,各层体现在哪里?
| 层级 | 你经常做的事情 |
|---|---|
| 应用层 | Qt UI、业务协议、JSON、MQTT、HTTP |
| 表示层 | Protobuf、结构体序列化、协议格式定义 |
| 会话层 | WebSocket、MQTT 会话保持、DDS Participant |
| 传输层 | UDP/TCP 收发、粘包拆包 |
| 网络层 | IP 地址、子网设置、Raw Socket、ICMP |
| 数据链路层 | 抓包、ARP 分析、MAC 地址处理 |
| 物理层 | 几乎不写,但受 MTU 影响 |
🧠 总结(非常关键)
✔ 应用层是你平时写得最多的(HTTP、MQTT、DDS)
✔ 表示层是序列化/反序列化(Protobuf、JSON)
✔ 会话层是保持连接(WebSocket、MQTT、DDS)
✔ 传输层是端口、TCP/UDP、粘包拆包
✔ 网络层是 IP 地址、ICMP、路由
✔ 链路层是 MAC、ARP、以太帧
✔ 物理层是信号、比特流
🟦 1. 什么是 MTU?
MTU(Maximum Transmission Unit)= 最大传输单元
👉 它指的是“二层以太网帧中 IP 包的最大大小”
常见值:
| 网络类型 | MTU |
|---|---|
| 有线以太网(Ethernet) | 1500 字节(最常见) |
| PPPoE (宽带拨号) | 1492 |
| VPN(IPsec) | 1400~1472 |
| IPv6 自动配置 | 1280(最低要求) |
⚠️ MTU 是链路层(第 2 层)的限制
它限定的是 IP 包的最大大小(不含以太网头)。
🟦 2. UDP 发送“一包有效数据”需要被包装多少层?(非常关键)
你的业务数据会被逐层加头:
✔ 最终从应用发出去的完整链路层帧结构如下:
┌──────────────────────────────┐
│ 以太网帧(Ethernet II) │ ← 第 2 层
│ ┌──────────────────────────┐│
│ │ IP Header (IPv4/IPv6) │ ← 第 3 层
│ │ ┌────────────────────────┤│
│ │ │ UDP Header │ ← 第 4 层
│ │ │ ┌──────────────────────┤│
│ │ │ │ UDP Payload(你的数据) ← 第 7/6 层
│ │ │ └──────────────────────┘│
│ │ └──────────────────────────┘│
│ └──────────────────────────────┘
└──────────────────────────────────┘
🧱 UDP 包数据的最小开销是多少?
| 层级 | Header 大小 |
|---|---|
| UDP header | 8 字节 |
| IPv4 header | 20 字节(无选项) |
| IPv6 header | 40 字节 |
| Ethernet II header | 14 字节 |
| Ethernet FCS(尾部 CRC) | 4 字节 |
🧮 以太网 + IPv4 + UDP 的实际包大小计算
➤ 要发送 N 字节业务数据
完整帧大小:
以太网头(14) +
IP头(20) +
UDP头(8) +
数据(N) +
FCS(4)
➤ 加起来:
| 项目 | 大小 |
|---|---|
| Ethernet Header | 14B |
| IP Header | 20B |
| UDP Header | 8B |
| FCS | 4B |
| 合计包头开销 | 46 字节 |
因此一个 UDP 包有效负载 N 字节
最终在网线上发送的大小约为:
N + 46 字节
🟦 3. MTU 限制能放多少 有效数据(Payload)?
MTU = 1500,指的是 IP 层的最大包:
IP Header + UDP Header + Payload <= 1500
🧮 所以最大 Payload:
1500 - 20(IP) - 8(UDP) = 1472 字节
UDP 单包最大有效数据 = 1472 字节(IPv4)
如果超过这个数 → IP 分片 → 丢包概率暴增。
🟦 4. IPv6 情况
IPv6 Header = 40 字节:
1500 - 40(IP6) - 8(UDP) = 1452 字节
🟦 5. 总结:一包 UDP 数据经过几层封装?
⭐ 最完整答案:4 层封装 + 2 层头尾
| 层 | 是否加头 | 描述 |
|---|---|---|
| 应用层 | 你自己的协议格式 | 自定义数据 |
| 传输层 | UDP header (8B) | 端口号 + 长度 |
| 网络层 | IP header (20B/40B) | 源 IP、目标 IP |
| 数据链路层 | Ethernet header (14B) | MAC 地址 |
| 数据链路尾部 | FCS 校验 (4B) | CRC32 |
🟩 最终发送出去的包 必须包含:
- 应用层数据 N 字节
- UDP 头 8 字节
- IP 头 20(IPv4) 或 40(IPv6)
- 以太网头 14
- 以太网尾 FCS 4
🟩 为什么 UDP 不可能粘包?
🟥 为什么 TCP 一定会粘包?(也会拆包)
读完你就不会再被网上那些模糊说法误导。
🟦 一、UDP 为什么不会粘包?
因为 UDP 是“消息(报文)边界协议”——Message-Oriented Protocol。
一次 sendto() 对应一次 recvfrom()。
一发一收,永远是一个完整的包。
🧩 原因 1:UDP 包在链路层就是独立的“帧”
每一个 UDP 数据包在网络中都是一个独立的 IP 包:
[IP Header][UDP Header][Payload]
每个包都有:
- 包长度字段
- 校验字段
- 独立的发送、转发、接收过程
❗路由器永远不会把两个 UDP 包合并,也不会拆开(除非分片)
🧩 原因 2:recvfrom() 会一次性取回完整的一个 UDP 包
recvfrom(sock, buf, sizeof(buf), 0, ...);
- 如果接收缓冲区够大 ⇒ 你永远得到完整的一个包
- 如果太小 ⇒ 包 被丢弃(系统直接丢,应用拿不到任何数据)
‼️ 不会出现半包情况(不会收到前半部分)
‼️ 不会粘在一起(不会收到两个包合并)
🧩 原因 3:内核缓冲区以“包”为单位排队
UDP 接收队列是:
包1
包2
包3
……
不是像 TCP 那样的连续字节流。
✅ **结论:UDP 不会粘包,也不会拆包。
只可能“丢包”。**
🟥 二、那为什么 TCP 会粘包?
因为 TCP 是字节流协议(Stream-Oriented Protocol)。
TCP:
- 没有边界
- 没有包的概念
- 只有字节序列
现实中你收到的数据像这样连续:
ABCD|EFGHIJKL|MNOPQRST
但你的应用程序可能这样读取:
recv 读到: ABCDEFG
下一次读: HIJKLMNOPQRST
你不知道这些字节原来是哪几次 send() 发出来的。
🟦 TCP 粘包 + 拆包 的三个主要原因
🔶 1. Nagle 算法(合并小包,提高效率)
TCP 认为频繁发小包浪费性能,于是自动合并。
例:
send("A");
send("B");
send("C");
TCP 可能合成:
ABC
🔶 2. TCP 是流,不会保留“消息边界”
发送三次:
send(100字节)
send(50字节)
send(80字节)
接收可能是:
recv(120字节)
recv(110字节)
你根本不知道哪里切。
🔶 3. MTU 决定了数据是否会被拆包
例如你 send() 了 5000 字节的数据,
以太网 MTU 是 1500,所以:
5000 → 1500 + 1500 + 1500 + 500(IP 分片)
接收端会逐段收到。
🟦 关键结论:
⭐ UDP:一发一收,永远完整,没有边界问题
⭐ TCP:流式,需要你自己定义消息边界
🟩 那么 TCP 如何处理粘包?(非常重要)
你必须在应用层定义协议格式,例如:
📌 方法 1:固定包长(简单但死板)
每个包固定 64 字节
📌 方法 2:包头 + 长度字段(最常用)
包格式:
[4 字节长度][数据...]
接收端:
- 先读 4 字节
- 得到 payload 长度
- 按长度读取剩余数据
- 完成一帧解析
这是 所有框架正在用的方法(QT、FastDDS、protobuf RPC 等)
📌 方法 3:特殊分隔符(如 \n)
HTTP 就使用 CRLF 分隔行。
🟦 总结对比表
| 项目 | UDP | TCP |
|---|---|---|
| 是否会粘包 | ❌ 不会 | ✔ 会 |
| 是否会拆包 | ❌ 不会(小 buffer 会丢) | ✔ 会 |
| 是否保持消息边界 | ✔ 是 | ❌ 否 |
| 是否丢包 | ✔ 可能 | ❌ 不会 |
| 顺序是否保证 | ❌ 不保证 | ✔ 保证 |
| 是否可靠 | ❌ 不可靠 | ✔ 可靠 |
| 典型应用 | 视频、语音、传感器 | Web、文件传输、数据库 |
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)