第一百零九章:信鸽
问道期涉及内核源码:
一
林小源离开 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 那样自己想办法切分。
/*
* 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");#include <stdio.h>
/*
* UDP 的特点:
*
* 1. 无连接
* 不需要建立连接
* 直接发送数据
*
* 2. 不可靠
* 不保证到达
* 不保证顺序
* 可能重复
*
* 3. 消息式
* 每次发送是独立的消息
* 有消息边界
*
* 4. 快速
* 没有握手开销
* 没有确认开销
* 没有重传开销
*
* UDP 的用途:
* DNS 查询 — 简单请求-响应
* 视频流 — 可以容忍丢失
* 游戏 — 延迟比可靠性更重要
* DHCP — 广播
*
* UDP 报文头:
* 源端口 | 目的端口
* 长度 | 校验和
* 数据...
*/
int main() {
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");
return 0;
}道藏笔记
内核启示
UDP 那小子说得对——不是所有东西都需要保险箱,有时候弹弓就够了。
UDP 无连接、不可靠、消息式,报文头只有 8 个字节,比 TCP 的 20-60 字节小得多。没有握手、没有确认、没有重传、没有滑动窗口,发了就走。DNS 查询一个请求一个响应,用不着三次握手;视频通话丢一帧画面用户根本感觉不到;游戏里旧的坐标不如不要——这些场景下 UDP 的"不可靠"反而成了优势。
如果确实需要可靠传输,可以在应用层自己做。QUIC 就是这么干的——用 UDP 当底子,在应用层实现确认、重传、流控,还能定制拥塞控制算法,不用被 TCP 的内核实现绑死。端到端原则说的就是这个:可靠性等功能应该在端点实现,而不是在网络中间。
UDP 之试
本章的信鸽没有握手、确认、重传和滑动窗口,它代表哪种传输协议?