Skip to content

第一百零九章:信鸽

问道期

涉及内核源码:

林小源离开 TCP 商船,沿着码头往回走。

一阵扑棱声从头顶传来。他抬头一看,一大群鸽子从一座矮矮的石屋方向飞起,每只鸽子腿上都绑着一个小竹筒。石屋门口站着一个瘦高的年轻人,穿着一身灰扑扑的短衣,正往鸽子腿上绑竹筒,动作飞快。

"你是 UDP?" 林小源问。

年轻人头也不抬:"嗯。你有话就说,我没空闲聊——手上的信还得发。"

"这些信……你不管它们到不到?"

"不管。" UDP 把最后一只鸽子往天上一抛,拍拍手上的灰,"扔出去就完事了。到不到是它的事,不是我的事。我只管发。"

林小源看着鸽群四散飞去,有的往东、有的往西,毫无队形。"那……丢了呢?"

"丢了就丢了。" UDP 终于抬起头来,露出一张精瘦的脸,眼神锐利得像鹰。"你去 DNS 服务器查个域名,用得着三次握手?你开视频会议,丢一帧画面天塌了?你打游戏,旧的坐标还有用?"

他从口袋里掏出一张纸片,上面只有四个格子:源端口、目的端口、长度、校验和。"看看我的报文头——8 个字节。TCP 那家伙的报文头至少 20 字节,加上选项能到 60。我快,就快在这里。没有握手、没有确认、没有重传、没有滑动窗口。发了就走。"

这不就是"大道至简"嘛——没有握手、没有确认、没有重传,报文头才 8 个字节,不快才怪。

林小源跟着 UDP 走进了石屋。

屋子里很简陋——墙上挂着一张地图,桌上放着几叠竹筒。没有航海日志、没有绞盘、没有信号旗。

"你坐。" UDP 倒了碗水推过来,"我知道你在想什么——这么不靠谱,谁用?"

他指着墙上的地图:"DNS 查询。客户端发一个包问'这个域名的 IP 是什么',服务器回一个包。就这两个包,用得着建立连接?我一个包搞定,快得很。就算丢了,应用层重试一次就行了。"

他又指了指地图上标着"视频流"的区域:"视频通话。每秒三十帧,丢一帧你眼睛都来不及眨。你要是用 TCP,丢了一个包就停下来等重传,画面直接卡住。用 UDP,丢了就丢了,播下一帧,用户根本感觉不到。"

"游戏呢?"

"游戏更简单。" UDP 敲了敲桌子,"玩家的位置信息每秒更新几十次。你收到的是五秒前的位置?那还不如不要。旧数据没有价值,新数据才有价值。TCP 那种'必须按序到达'的规矩在这里完全不适用。"

林小源端起碗喝了口水。"那如果我确实需要可靠传输呢?"

"那就自己在应用层做。在 UDP 之上自己加确认、加重传、加排序。QUIC 就是这么干的——用 UDP 当底子,自己实现可靠传输。这样你还能定制自己的拥塞控制算法,不用被 TCP 的内核实现绑死。"

林小源看着 UDP 那张精瘦的脸,忽然觉得他说得有道理——不是所有东西都需要保险箱,有时候弹弓就够了。

傍晚,林小源站在石屋门口,看着最后一波信鸽飞走。

他忽然想起一个问题:"UDP 发的消息,接收方怎么收?"

UDP 倚在门框上:"你调用 ,我就给你一个完整的消息。你发 100 字节,我就给你 100 字节。你发 200 字节,那就是另一条消息。每条消息是独立的,有明确的开头和结尾。"

"TCP 不是这样的?"

"TCP 是河流,我是信封。" UDP 说,"TCP 发出去的数据是连续的字节流,你调用 recv() 可能收到半条消息、一条消息、或者两条消息粘在一起。你得自己定协议来切分。我不一样——消息边界是天然的, 发多少, 就收多少,不会混。"

"但代价是不可靠。"

"对。" UDP 没有否认,"我的鸽子可能迷路,可能迟到,可能重复。你选我就得接受这些。但你换来的是速度——没有握手延迟、没有确认等待、没有拥塞退让。"

他转身走进屋子里,声音从门内传来:"TCP 是保险箱,我是弹弓。你运金条用保险箱,你打鸟用弹弓。别搞反了就行。"

林小源点了点头——UDP 的消息边界是天然的,不用像 TCP 那样自己想办法切分。

c
/*
 * UDP 的特点:
 *
 * 1. 无连接
 *    不需要建立连接
 *    直接发送数据
 *
 * 2. 不可靠
 *    不保证到达
 *    不保证顺序
 *    可能重复
 *
 * 3. 消息式
 *    每次发送是独立的消息
 *    有消息边界
 *
 * 4. 快速
 *    没有握手开销
 *    没有确认开销
 *    没有重传开销
 *
 * UDP 的用途:
 *   DNS 查询 — 简单请求-响应
 *   视频流 — 可以容忍丢失
 *   游戏 — 延迟比可靠性更重要
 *   DHCP — 广播
 *
 * UDP 报文头:
 *   源端口 | 目的端口
 *   长度   | 校验和
 *   数据...
 */

printf("=== UDP — 迅捷的信鸽 ===\n\n");

printf("UDP 的特点:\n");
printf("  无连接: 不需要握手\n");
printf("  不可靠: 不保证到达\n");
printf("  消息式: 有消息边界\n");
printf("  快速: 没有额外开销\n\n");

printf("--- UDP vs TCP ---\n");
printf("%-15s %-15s %-15s\n",
       "特性", "TCP", "UDP");
printf("%-15s %-15s %-15s\n",
       "---", "---", "---");
printf("%-15s %-15s %-15s\n",
       "连接", "面向连接", "无连接");
printf("%-15s %-15s %-15s\n",
       "可靠", "是", "否");
printf("%-15s %-15s %-15s\n",
       "顺序", "保证", "不保证");
printf("%-15s %-15s %-15s\n",
       "边界", "无", "有");
printf("%-15s %-15s %-15s\n",
       "速度", "较慢", "快");
printf("%-15s %-15s %-15s\n",
       "开销", "高", "低\n");

printf("--- UDP 的用途 ---\n");
printf("DNS 查询:\n");
printf("  简单的请求-响应\n");
printf("  不需要建立连接\n\n");
printf("视频流:\n");
printf("  可以容忍少量丢失\n");
printf("  延迟比可靠性更重要\n\n");
printf("游戏:\n");
printf("  实时性要求高\n");
printf("  旧数据不如新数据重要\n\n");

printf("--- UDP 报文头 ---\n");
printf("┌────────────────────────────────┐\n");
printf("│ 源端口 (16) │ 目的端口 (16)  │\n");
printf("├────────────────────────────────┤\n");
printf("│ 长度 (16)   │ 校验和 (16)    │\n");
printf("├────────────────────────────────┤\n");
printf("│ 数据...                        │\n");
printf("└────────────────────────────────┘\n");
printf("报文头只有 8 字节\n");

道藏笔记

内核启示

UDP 那小子说得对——不是所有东西都需要保险箱,有时候弹弓就够了。

UDP 无连接、不可靠、消息式,报文头只有 8 个字节,比 TCP 的 20-60 字节小得多。没有握手、没有确认、没有重传、没有滑动窗口,发了就走。DNS 查询一个请求一个响应,用不着三次握手;视频通话丢一帧画面用户根本感觉不到;游戏里旧的坐标不如不要——这些场景下 UDP 的"不可靠"反而成了优势。

如果确实需要可靠传输,可以在应用层自己做。QUIC 就是这么干的——用 UDP 当底子,在应用层实现确认、重传、流控,还能定制拥塞控制算法,不用被 TCP 的内核实现绑死。端到端原则说的就是这个:可靠性等功能应该在端点实现,而不是在网络中间。


破关试炼

UDP 之试

本章的信鸽没有握手、确认、重传和滑动窗口,它代表哪种传输协议?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道