LV035-传输层端口UDP与TCP
一、 为什么还需要传输层
1. IP 只把包送到主机,不送到程序
前面几篇解决了"怎么把包送到目标主机":IP 负责端到端寻址,MAC 负责一跳一跳搬运。但目标主机上同时跑着很多程序——浏览器、聊天软件、终端——包到了主机,应该交给谁?
IP 层回答不了这个问题:它的头里只有主机地址,没有"程序"的概念。区分程序是传输层的工作。
2. 端口:同一台主机上的分机号
传输层给每个程序分配一个端口号(16 位,0 ~ 65535),整个寻址链条到这才完整:
MAC 找到下一跳的设备 → IP 找到最终的主机 → 端口找到主机上的哪个程序端口号的约定俗成分三段:
| 范围 | 名称 | 典型用途 |
|---|---|---|
| 0 ~ 1023 | 知名端口 | 80 = HTTP、53 = DNS、69 = TFTP、67/68 = DHCP |
| 1024 ~ 49151 | 注册端口 | 各种约定好的服务 |
| 49152 ~ 65535 | 动态端口 | 客户端临时使用,用完即弃 |
注意知名端口绑定的是服务端:客户端发请求时,目的端口是服务器的知名端口,源端口则是一个临时选的动态端口。所以抓包看方向,看端口就能分清。
3. 本工程用到的端口
| 端口 | 谁在用 | 协议 | 出处 |
|---|---|---|---|
| 8888 | 板子的 TCP 升级服务器 | TCP | LV020-网络通信实验 第三节 |
| 69 | PC 侧的 TFTP 服务器 | UDP | LV025-基于TFTP的网络升级实验 |
| 67 / 68 | DHCP 服务器 / 客户端 | UDP | LV040-DHCP与IP地址自动获取 第二节 |
二、 UDP:无连接的信封
1. UDP 头只有 8 字节
UDP(用户数据报协议)是传输层里最薄的协议,头部固定 8 字节:
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口 | 2 字节 | 发送方端口 |
| 目的端口 | 2 字节 | 接收方端口 |
| 长度 | 2 字节 | 头 + 数据的总长度 |
| 校验和 | 2 字节 | 覆盖头与数据 |
没有序号、没有连接状态、没有窗口。发个 UDP 包就像寄一封信:写好地址扔进邮筒,之后就不管了。
2. 三个"不保证"
| 特性 | 含义 | 后果 |
|---|---|---|
| 不保证送达 | 丢了就丢了,没人重传 | 可靠性要靠应用自己解决 |
| 不保证顺序 | 后发的可能先到 | 应用要自己编号或容忍乱序 |
| 按报文交付 | 收发的单位是"一个包",边界保留 | 发两次 512 字节,收到的一定是两包 |
第三条是 UDP 与 TCP 最本质的区别:UDP 保留报文边界。发一个 512 字节的 UDP 报文,对方一次性收到的就是这个 512 字节,不会和下一个包粘在一起。
3. 什么场景适合 UDP
- 一问一答的小交互:DNS 查询、DHCP 地址申请。一个包问、一个包答,不值得先建连接。
- 允许丢包的实时流:音视频、在线游戏。等重传还不如要新数据。
- 链路足够可靠的局域网:丢包率本来就近乎零,可靠性的成本付得不值。
TFTP 选 UDP 就属于第一种和第三种的组合,见第五节。
三、 TCP:有连接的字节流
1. 字节流与报文的区别
TCP(传输控制协议)与 UDP 的世界观完全不同:它传输的是一条没有边界的字节流。
应用程序调三次 send(512),TCP 可能把它合成一个 1536 字节的段发出去,也可能切成六个 256 字节的小段;接收方读出来的只是"一串顺序正确的字节",至于这串字节是几次 send 凑出来的,接收方根本不知道。
这意味着应用层的"消息边界"必须自己定。本工程的做法就是 IAPBIN01 帧:用魔数定位起点、用长度字段声明边界——这正是 TCP 字节流上"自己定义协议"的典型做法(帧格式见 LV030-网络升级帧协议与三条通道 第二节)。
2. 三次握手与四次挥手
TCP 通信前先建立连接,握手只需要三个包:
客户端 服务端
│──── SYN ────────────▶│ "我要连你,我的起始序号是 X"
│◀─── SYN + ACK ───────│ "好,我的起始序号是 Y,你的 X 收到"
│──── ACK ────────────▶│ "你的 Y 收到,连接建立"结束时因为双方要各自宣布"我不再发了",需要四个包(FIN → ACK → FIN → ACK)。
三次握手的目的不复杂:双方确认对方的存在,并交换各自的初始序号。它要证明"我能发给你、你也能发给我",两个包证明不了双向都通,所以是三个。
3. 序号、ACK 与重传
连接建立后,可靠传输靠三个机制:
(1)序号:字节流里每个字节都有编号。报文段头里的序号就是"我这段数据的第一个字节是第几号"。
(2)ACK:接收方收到数据后回 ACK,告诉对方"第 N 号之前的字节我都收到了"。
(3)超时重传:发送方发出数据后启动定时器,超时没收到 ACK 就重发。
乱序到达的段先缓存起来,按序号排好再交给上层。对应用来说,TCP 呈现的就是一条不丢、不乱、不重复的字节流——代价是头更大(20 字节起)、状态机更复杂、内存占用更高。
4. 滑动窗口与 MSS
两个决定传输效率的参数:
| 参数 | 含义 | 本工程的取值 |
|---|---|---|
| 接收窗口(RWND) | 接收方允许对方"最多未确认多少字节" | TCP_WND = 4 × MSS = 2144 字节 |
| 最大报文段(MSS) | 一个段最多装多少数据,握手时各自宣布 | TCP_MSS = 536(lwIP 默认值) |
滑动窗口的作用类比"发票报销":发出去的段相当于垫出去的钱,窗口就是"最多能垫多少"——垫满之前不必等报销回来,所以链路能跑满。
MSS 的细节(为什么是 536 而不是 1460、对分段数量的影响)见 LV015-lwIP配置与初始化 第二节。
四、 UDP 与 TCP 的对照
| 对比项 | UDP | TCP |
|---|---|---|
| 连接 | 无连接 | 先握手建连接 |
| 传输单位 | 报文(保留边界) | 字节流(无边界) |
| 可靠性 | 不保证送达、不保证顺序 | 不丢、不乱、不重复 |
| 头部长度 | 固定 8 字节 | 20 字节起 |
| 状态开销 | 无 | 每个连接一个控制块(tcp_pcb) |
| 本工程用法 | TFTP(69)、DHCP(67/68) | 升级通道(8888) |
选择标准就一句话:宁可自己处理丢包也要省掉连接开销,选 UDP;字节序不能错、丢了必须补上,选 TCP。
五、 在本工程里的落地
1. TFTP 为什么用 UDP
TFTP 是"一问一答的小交互"的极端形态:每块 512 字节,收到一块回一个 ACK,服务器没收到 ACK 就重发。可靠性由协议自己在应用层实现了,所以不需要 TCP 再保证一遍——再用 TCP 就是两套重传互相打架。
而且 UDP 无连接的特性让"服务器收到 RRQ 后换一个端口(TID)回数据"这种玩法成为可能,这在 TCP 的固定四元组连接里是做不了的(细节见 LV025 第五节)。
2. 升级通道为什么用 TCP
反过来,升级通道传的是"不允许错一个字节"的固件,而且 PC 侧要写脚本——TCP 的字节流语义让脚本只需要 sendall() 了事,重传、排序、流量控制全部由协议栈兜底。
板侧为此付出的代价是一个 tcp_pcb 控制块(MEMP_NUM_TCP_PCB = 5)以及回调式编程的复杂度,这在 LV010-lwIP协议栈移植 里已经讲过。
3. ping 为什么用不上传输层
ping 用的是 ICMP,它直接坐在 IP 层上(协议号 1),根本没有端口。所以:
- "板子的 8888 端口被防火墙拦了"这类说法对 ping 不成立——防火墙拦不拦 ICMP 是另一组规则;
- ping 通不代表 TCP 能连上,两者验证的是不同的层。
这就是为什么排错要"先 ping、再 nc":ping 通了说明网络层以下都好,再往上才是端口的事(工具用法见 LV045-网络调试工具与抓包)。
4. lwIP 里的对应文件
| 协议 | 内核文件 | 本工程的应用 |
|---|---|---|
| TCP | src/core/tcp.c、tcp_in.c、tcp_out.c | port/iap_tcp.c(8888 端口服务器) |
| UDP | src/core/udp.c | Drivers/BSP/Ota/ota_tftp.c(TFTP 客户端) |
| ICMP | src/core/ipv4/icmp.c | 无应用代码,协议栈自动应答 |
lwIP 提供的是 RAW API:没有 socket、没有 read/write,所有收发都是回调(tcp_accept、tcp_recv、udp_recv)。回调运行在主循环上下文,所以移植那篇的硬约束"回调里只做 O(1) 搬运"在 UDP 与 TCP 上同样成立。
六、 小结与参考资料
1. 本篇小结
- IP 把包送到主机,端口把包送到程序;寻址链是 MAC → IP → 端口。
- 知名端口绑服务端(80、53、69、67/68),客户端用动态端口;看端口就能分清抓包方向。
- UDP 是无连接的"信封":8 字节头、不保证送达与顺序、保留报文边界,适合小交互与可靠链路。
- TCP 是有连接的"字节流":三次握手交换初始序号,靠序号 + ACK + 超时重传保证不丢不乱,靠滑动窗口跑满链路;应用层消息边界要自己定。
- 本工程的选型:TFTP 用 UDP(应用层自己做停等重传),升级通道用 TCP(字节流 + 协议栈兜底可靠性),ping 用 ICMP(根本不到传输层)。
- lwIP 只有 RAW API,收发全部走回调;
tcp_pcb数量受MEMP_NUM_TCP_PCB = 5限制。
2. 参考资料
- TCP 实现:
tcp.c、tcp_in.c、tcp_out.c - UDP 实现:
udp.c - 本工程应用:
iap_tcp.c、ota_tftp.c - 手册:
STM32F1 LWIP开发手册(DM9000版)_V1.1.pdf第三章 UDP、第五章 TCP
本文档由 markdowncli 技能辅助生成