LV001-网络分层模型与TCP-IP协议栈
一、 网络为什么要分层
1. 一次性解决所有问题是不现实的
两台设备要通信,要解决的问题有一大堆:
- 物理层:用几伏电压表示 0 和 1,用几根线,双绞还是同轴;
- 链路层:怎么把一串比特划成一段一段的帧,怎么知道帧的开头和结尾,出错了要不要重传;
- 网络层:怎么在成百上千台设备里找到目标,怎么跨过一段又一段的链路;
- 传输层:数据丢了好不好重传,谁先发谁后发,对方来不及收怎么办;
- 应用层:对方拿到的字节流到底该怎么理解,是文件、是网页还是一句命令。
把这五类问题一次性全解决是不现实的:它们彼此的关注点完全不同,混在一起写,任何一处改动都会牵动全部。
工程上的做法是分层:每一层只解决一类问题,只跟自己的上下层打交道。
2. 分层带来的三个好处
(1)换硬件不影响上层。这块板子用的是 DM9000 网卡,换一颗别的网卡芯片,TCP/IP 以上的部分一行都不用改。
(2)换应用不影响下层。今天跑 TFTP 拉固件,明天跑 HTTP 传网页,底下的 TCP/IP 与网卡驱动都不动。
(3)每层的责任清晰,出问题能按层排查。这也是后面查网络故障时"从物理往上逐层砍"这个思路的由来:先看网口灯,再看网卡 ID,再看地址,再看 ARP,最后才怀疑协议栈。
3. 分层的代价
分层不是免费的,代价主要有两条:
- 首部开销:每往下走一层就加一个头部。一个 ping 包真正想传的数据可能只有 8 字节,实际在网线上跑的却有 42 字节以上。
- 层间复制与查表:每层的收发都要有自己的缓冲、查表与状态机,对内存只有 64 KB 的 STM32F103 来说,这些都是要精打细算的开支。
本工程的做法是"要分层,但每一层都裁到刚好够用"——这正是后面 LV015-lwIP配置与初始化 里那张 lwipopts.h 裁剪表的由来。
二、 两种分层模型
1. OSI 七层与 TCP/IP 四层的对应
教材里最常出现的是 OSI 七层模型,而实际互联网跑的是 TCP/IP 四层模型。两者的对应关系如下:
| OSI 七层 | TCP/IP 四层 | 这一层负责什么 | 典型协议 / 设备 |
|---|---|---|---|
| 应用层、表示层、会话层 | 应用层 | 面向具体业务 | HTTP、DNS、TFTP、DHCP |
| 传输层 | 传输层 | 进程到进程(靠端口区分) | TCP、UDP |
| 网络层 | 网络层(网际层) | 主机到主机(跨网段寻址) | IP、ICMP |
| 数据链路层、物理层 | 网络接口层 | 一跳之内(同一条链路) | 以太网、MAC、PHY |
如果按 OSI 的七层展开,从上到下依次是:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
2. 为什么实际只谈四层
OSI 的"表示层、会话层"在 TCP/IP 里被并进应用层了。原因是这两层在实际实现中很难划出清晰的边界:数据怎么编码(表示层)通常由具体应用自己决定,而"会话"的概念更多落在应用自己的状态机里。
所以实际写代码时基本只谈四层,本系列文档也统一按四层展开。
3. 一处归属争议:ARP 属于哪一层
教材里对 ARP 的说法不统一:
- 它报文的承载方式属于数据链路层——ARP 报文直接封在以太网帧里跑,帧类型字段是
0x0806; - 它干的事情却是为网络层服务——把 IP 地址解析成 MAC 地址。
所以更准确的理解是夹在网络层与网络接口层之间。本系列把它放在 LV030-ARP与MAC地址 单独讲,讲完再看归属问题会清楚得多。
三、 数据是怎么一层层封装起来的
1. 发送加头、接收脱头
发数据是从上往下逐层加头,收数据是从下往上逐层脱头:
发送方向 ↓ 接收方向 ↑
应用层 应用数据 应用数据
传输层 [TCP头][应用数据] 脱 TCP 头
网络层 [IP头][TCP头][应用数据] 脱 IP 头
链路层 [以太网头][IP头][TCP头][数据][校验] 脱以太网头每一次"加头"就是一次封装,每一次"脱头"就是一次解封装。
帧里每一个字段具体占几个字节、校验覆盖哪一段,LV005-数据封装与以太网帧 会逐个字段拆开讲,这里先看整体形态。
2. 一次 ping 的报文拆解
以一次 ping 为例,板子收到的报文会长这样:
| 部分 | 长度 | 内容 |
|---|---|---|
| 以太网头 | 14 字节 | 目的 MAC、源 MAC、类型(0x0800 表示里面装的是 IPv4) |
| IP 头 | 20 字节 | 版本、总长、源 IP、目的 IP、协议号(1 表示 ICMP)…… |
| ICMP 报文 | 8 字节起 | 类型(8 = 请求,0 = 应答)、校验和、标识、序号、数据 |
3. 三种地址各自管多远
链路层最大的一个特点:以太网头里的 MAC 地址只在一跳之内有效,每经过一台路由器就会被重写一次;而 IP 头里的地址是端到端的,一路不变。
把三种地址摆在一起看最清楚:
| 地址 | 工作在哪一层 | 作用范围 |
|---|---|---|
| MAC 地址 | 数据链路层 | 一跳之内,换一个路由器就变了 |
| IP 地址 | 网络层 | 端到端,全程不变 |
| 端口号 | 传输层 | 同一台主机上区分是哪个程序 |
一句话概括:MAC 找到"下一跳的那台设备",IP 找到"最终的那台设备",端口找到"那台设备上的哪个程序"。
跨网段时这两种地址的表现差异会成为一个关键结论,见 LV015-网关与跨网段通信 第三节。
四、 把分层映射到 lwIP 的源码目录
1. 分层与文件的对照表
本项目用的 lwIP 2.1.3(atk-f103/Components/lwip-2.1.3/)目录结构与四层模型几乎是直译的:
| 分层 | 本工程中的文件 | 说明 |
|---|---|---|
| 应用层 | port/iap_tcp.c、Drivers/BSP/Ota/ota_tftp.c | 自己写的 TCP 服务器与 TFTP 客户端 |
| 传输层 | src/core/tcp.c、tcp_in.c、tcp_out.c、udp.c | TCP/UDP 的实现 |
| 网络层 | src/core/ipv4/ip4.c、ip4_addr.c、icmp.c、etharp.c | IP 收发、地址处理、ICMP、ARP |
| 网络接口层 | port/ethernetif.c + Drivers/BSP/Ethernet/dm9000.c | lwIP 与网卡的接口 + 网卡驱动本身 |
| 平台适配 | port/lwipopts.h、port/sys_arch.c、port/arch/cc.h | 不属于某一层,服务于整个协议栈 |
2. 后面几篇各讲哪个文件
记住上面这张表,"读哪一篇对应哪个文件"就清楚了:
(1)地址与掩码(LV010-IP地址与子网掩码)→ ip4_addr.c。
(2)交换机、路由器与网关(LV020、LV015)→ 主要是硬件侧的事,协议栈里没有直接对应文件。
(3)ARP(LV030)→ etharp.c。
(4)传输层:端口、UDP 与 TCP(LV035)→ tcp.c、tcp_in.c、tcp_out.c、udp.c。
(5)DHCP(LV040)→ src/core/ipv4/dhcp.c,本工程把它裁掉了,只做源码对照。
(6)网络调试工具(LV045)→ 工具侧的事,与决策树搭配用。
(7)一次 ping 的全过程(LV050)→ 串起 icmp.c、ip4.c、etharp.c、ethernetif.c。
3. 两个术语的用法
"TCP/IP"这个词有两种用法,读到时要看语境:
(1)狭义:指 TCP 和 IP 这两个协议本身。
(2)广义:指整套协议族(TCP/IP 协议栈),包含 IP、TCP、UDP、ICMP、ARP 等等。
lwIP 这个名字是 "lightweight IP" 的缩写,但它是一整套协议栈,不只是 IP 协议。
五、 小结与参考资料
1. 本篇小结
- 网络分层是为了"每层只解决一类问题",也让排错可以按层逐段缩小范围;代价是首部开销与层间缓冲。
- OSI 七层与实际用的 TCP/IP 四层可以对照,OSI 的上三层对应 TCP/IP 的应用层。
- 发数据是逐层加头(封装),收数据是逐层脱头(解封装)。
- 四层里每一层用不同的地址:MAC 管一跳、IP 管端到端、端口管进程。
- 以太网帧类型字段是分流的依据:
0x0806→ ARP,0x0800→ IPv4。 - lwIP 的目录结构就是这套分层的直接映射,后面的文档都围绕这张表展开。
2. 参考资料
- lwIP 源码目录:
atk-f103/Components/lwip-2.1.3/ - 网卡接口层:
ethernetif.c - 手册:
STM32F1 LWIP开发手册(DM9000版)_V1.1.pdf第 1.4 节
本文档由 markdowncli 技能辅助生成