bc's club

This is Bc's club

计算机网络:自顶向下方法 - Day 15 | 全书总结与面试题精选(收官篇)

📖 Day 15 / 15 · 《计算机网络:自顶向下方法》阅读笔记

每天 30 分钟,15 天读懂计算机网络。今天是最后一天——收官之日。我们把全书五层模型从头到尾串一遍,然后用 20 道面试题检验学习成果,最后以你的 Mac mini + nginx + frp + bc970321.cn 系统作为综合案例,把所有知识点焊死在脑子里。

读完这篇,你可以合上书,对自己说一句:「计算机网络,我入门了。」

计算机网络:自顶向下方法 - Day 14 | 网络安全

📖 Day 14 / 15 · 《计算机网络:自顶向下方法》阅读笔记

每天 30 分钟,15 天读懂计算机网络。今天我们进入全书最「刺激」的一章——网络安全。前面 13 天我们学了网络怎么工作,今天来看看网络怎么被攻击、又怎么防御。加密、数字签名、SSL/TLS、防火墙、IDS/IPS,一次性安排上。你每天 SSH 登录服务器、frp 穿透内网、给网站配 HTTPS 证书,背后全是这些原理。

计算机网络:自顶向下方法 - Day 13 | 多媒体网络、流媒体、CDN 与 QoS

Day 13 — 多媒体网络、流媒体、CDN 与 QoS#

前情回顾:Day 11 我们聊了链路层和以太网,理解了 MAC 地址、CSMA/CD、VLAN 这些底层基石。至此,五层模型的每一层我们都已经翻了个遍。今天,我们换个视角——不再逐层拆解,而是聚焦一个改变互联网面貌的应用领域:多媒体网络。你每天刷的 B 站、追的 Netflix、开的腾讯会议,背后全是我们今天要聊的技术。


一、多媒体网络:互联网的”流量巨兽”#

1.1 多媒体流量到底有多恐怖?#

先来感受一组数据:

  • YouTube 每分钟上传超过 500 小时的视频
  • Netflix 占北美互联网流量的 30%+
  • 2025 年全球 IP 流量中,视频流量占比超过 80%

可以说,今天的互联网基本就是一个巨大的视频管道。文本?那只是管壁上的一层薄漆。

生活类比:想象互联网是一条高速公路。早期只有几辆自行车(文本邮件),后来有了小汽车(网页图片),现在呢?全是重型卡车(视频流),一辆接一辆,日夜不停。

多媒体应用对网络的需求和传统应用截然不同:

特性 传统应用(HTTP、邮件) 多媒体应用(视频、语音)
延迟敏感度 不太敏感 极其敏感(>300ms 就难受)
丢包容忍度 零容忍(文件不能缺字节) 有一定容忍(丢几帧无所谓)
带宽需求 低~中等 高~极高
持续时间 短连接为主 长时间持续流

这张表揭示了一个核心矛盾:多媒体要低延迟,但又需要大带宽;能容忍丢包,但 TCP 偏偏不丢包。这个矛盾贯穿了整个多媒体网络的设计。


二、流式存储视频:DASH 横空出世#

2.1 从”下载完再看”到”边下边看”#

远古时代(2000 年代初),在网上看视频的体验是这样的:点开链接 → 等待 → 等待 → 进度条慢慢爬 → 下完了 → 终于能看了。

这叫 下载-播放模型,显然不符合人类的即时满足需求。

于是有了 流式传输(Streaming):视频数据像流水一样源源不断地从服务器涌向你的设备,你一边接水一边喝,不需要等整个水池灌满。

生活类比:下载-播放就像你得把整个 bathtub 的水放满才能泡澡;流式传输就像淋浴——水一边来你一边洗,随到随用。

2.2 UDP 流 vs HTTP 流#

早期流媒体用 UDP(比如 RTSP 协议),因为 UDP 延迟低。但问题来了:

  1. 很多防火墙/NAT 会挡 UDP
  2. CDN 和缓存系统是围绕 HTTP 设计的
  3. UDP 没有拥塞控制,容易把网络搞崩

所以现代流媒体几乎全转向了 HTTP 流:用 TCP/HTTP 传视频,就像传普通网页一样。防火墙不挡,CDN 能缓存,皆大欢喜。

代价呢?TCP 的头部开销和重传机制会增加一点延迟。但对于非实时的流式视频(不是视频通话),这点延迟完全可以接受。

2.3 DASH:动态自适应流#

DASH(Dynamic Adaptive Streaming over HTTP) 是现代流媒体的核心技术。B 站、YouTube、Netflix 用的都是这个思路(具体实现可能是 HLS、MPEG-DASH 等变体)。

核心思想非常优雅:不要只准备一个版本的视频,准备很多个版本,让客户端自己选

工作原理#

服务器把视频切割成等时长的小块(比如每块 2~10 秒),每个块编码成多个质量等级(不同码率、分辨率):

1
2
3
4
5
6
视频源 → 切分成 N 个块

块 1: [240p 500kbps] [480p 1Mbps] [720p 2Mbps] [1080p 5Mbps] [4K 15Mbps]
块 2: [240p 500kbps] [480p 1Mbps] [720p 2Mbps] [1080p 5Mbps] [4K 15Mbps]
块 3: [240p 500kbps] [480p 1Mbps] [720p 2Mbps] [1080p 5Mbps] [4K 15Mbps]
...

同时服务器提供一个 manifest 文件(清单),列出了所有块和所有质量等级的 URL。

客户端(播放器)的工作流程:

  1. 先拉 manifest 文件,了解有哪些版本可选
  2. 根据当前带宽估计,选择一个合适的质量等级
  3. 请求第一个块 → 播放
  4. 持续监控带宽变化
  5. 如果带宽充裕 → 下一块选更高画质
  6. 如果带宽变差 → 降级到更低画质

这就是你在 B 站看视频时,画质自动在 1080p 和 360p 之间跳来跳去的原因。

生活类比:DASH 就像自助餐厅。厨师(服务器)把每道菜都做成了大份、中份、小份。你(客户端)根据自己的胃口(带宽)决定拿哪份。今天胃口好 → 大份牛排;突然胃疼 → 换小份沙拉。全程不用跟厨师说话,自己拿就行。

DASH 的聪明之处#

DASH 的精妙在于它把智能下放到了客户端

  • 服务器不需要知道客户端的网络状况
  • 服务器只需要存一堆文件,用普通 HTTP 服务器就能搞定
  • 客户端自己决定拉什么质量,完全自治
  • 不同客户端可以有完全不同的体验,互不干扰

这也是为什么 CDN 非常喜欢 DASH——它就是一堆静态文件的 HTTP 请求,和缓存网页没有任何区别。

告警:码率切换的”突变”问题#

你可能注意过,B 站视频有时会在 1080p 和 360p 之间反复横跳,画面突然模糊又突然清晰,非常难受。

这是因为带宽估计不稳定。如果客户端的算法太激进,网络一抖动就降到最低画质,网络一恢复就冲到最高画质,用户体验就很糟糕。

优秀的 ABR(Adaptive Bit Rate)算法需要在稳定性和响应速度之间找平衡。Netflix 甚至用机器学习来优化这个决策过程。


三、CDN:内容分发网络#

3.1 为什么需要 CDN?#

假设你在中国上海,想看一个托管在美国纽约服务器的视频。数据要跨越太平洋,经过十几个路由器,延迟 200ms+。如果同时有一万个人在看这个视频,纽约服务器的带宽直接爆炸。

这个问题的解决方案不是把服务器搬到每个城市(成本太高),而是 CDN(Content Distribution Network)

CDN 的核心思想:在全世界各地部署大量缓存节点(边缘服务器),把内容的副本放在离用户最近的地方。用户访问时,被引导到最近的节点,直接从那里获取数据。

生活类比:CDN 就像连锁超市。你不需要每次想吃薯片都去工厂总部(源服务器)买。工厂把薯片分销到你家门口的超市(CDN 节点),你走两分钟就能买到。库存没了?超市会让工厂补货。

3.2 CDN 的工作流程#

当你在浏览器输入 bc970321.cn 时(假设接了 CDN),发生的事情:

1
2
3
4
5
6
7
8
1. 用户请求 https://bc970321.cn/some-video.mp4
2. DNS 查询:bc970321.cn 的 DNS 记录不是 A 记录,而是 CNAME 指向 CDN 提供商的域名
3. CDN 的 DNS 服务器根据用户 IP 地址,返回最近的边缘节点 IP
4. 用户连接到该边缘节点
5. 边缘节点检查:有没有这个视频的缓存?
- 有 → 直接返回(缓存命中,Cache Hit)
- 没有 → 从源服务器拉取,缓存一份,再返回用户(缓存未命中,Cache Miss)
6. 后续其他用户请求同一视频 → 直接从边缘节点返回

3.3 CDN 的关键设计挑战#

选哪个节点?#

CDN 的 DNS 需要决定把用户导向哪个节点。最简单的策略是基于地理距离,但距离不等于网络质量。一个北京用户可能到天津节点的延迟比到北京本地某节点还低(取决于运营商路由)。

高级 CDN(Cloudflare、Akamai、AWS CloudFront)使用:

  • BGP Anycast:同一个 IP 地址在全球广播,用户的数据包自然路由到最近的节点
  • 实时网络探测:持续测量各节点到各地区的延迟和丢包率
  • 机器学习预测:根据历史数据预测最优节点

缓存什么?缓存多久?#

CDN 的缓存策略决定了命中率:

  • Pull 策略:用户第一次请求时,节点从源服务器拉取并缓存。最常见。
  • Push 策略:源服务器主动把热门内容推到所有节点。适合大文件首发的场景。
  • TTL(Time To Live):缓存的有效期。过期后下次请求会重新从源服务器拉取。

对于你的博客 bc970321.cn

  • HTML 页面:TTL 短(几分钟),确保内容更新能及时生效
  • 图片/CSS/JS 静态资源:TTL 长(几天到几个月),反正不怎么变
  • 如果上了 Hexo 博客,可以用 hexo_asset_img 等插件给静态资源加 hash 后缀,实现长缓存 + 自动更新

缓存替换策略#

边缘节点的存储空间有限,新内容来了,旧内容就得淘汰。常见策略:

  • LRU(Least Recently Used):淘汰最久没用的
  • LFU(Least Frequently Used):淘汰使用频率最低的
  • 大多数 CDN 用 LRU 的变体

四、实战分析:bc970321.cn 博客接入 CDN#

说了这么多理论,来点实际的。假设我们要给 bc970321.cn 这个 Hexo 博客接入 CDN,怎么做?

4.1 方案一:全站 CDN(Cloudflare 免费版)#

最简单的方案——把整个域名丢给 Cloudflare:

1
2
3
DNS 记录:
bc970321.cn A → 你的源站 IP(通过 Cloudflare 橙云代理)
www CNAME → bc970321.cn

Cloudflare 会自动:

  • 缓存所有静态资源(CSS、JS、图片)
  • 提供 DDoS 防护
  • 提供 TLS 证书(免费)
  • 全球 300+ 节点加速

缓存规则建议

1
2
3
4
5
# Cloudflare Page Rules
1. *.bc970321.cn/images/* Cache Everything, TTL 1 month
2. *.bc970321.cn/css/* Cache Everything, TTL 1 month
3. *.bc970321.cn/js/* Cache Everywhere, TTL 1 month
4. *.bc970321.cn/* Cache Everything, TTL 5 min

4.2 方案二:静态资源走 CDN,动态请求走源站#

如果你想更精细地控制:

1
2
3
DNS 记录:
bc970321.cn A → 你的源站 IP(灰云,不代理)
cdn.bc970321.cn CNAME → CDN 提供商

在 Hexo 模板里,把静态资源 URL 改成 CDN 地址:

1
2
<link rel="stylesheet" href="https://cdn.bc970321.cn/css/style.min.css">
<img src="https://cdn.bc970321.cn/images/avatar.jpg">

对 Hexo 来说,可以在 _config.yml 里设置:

1
2
3
4
5
url: https://bc970321.cn
# 如果主题支持 CDN 加速
cdn:
enable: true
prefix: https://cdn.bc970321.cn

4.3 Nginx 的流媒体能力#

你的服务器上跑着 Nginx,它其实也是流媒体的一把好手:

Nginx 做 HLS 直播/点播#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 点播 HLS
location /hls/ {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/videos;
add_header Cache-Control public;
add_header Access-Control-Allow-Origin *;
}

# 直播 HLS(需要 nginx-rtmp-module)
rtmp {
server {
listen 1935;
application live {
live on;
hls on;
hls_path /var/hls;
hls_fragment 3;
}
}
}

Nginx 做 DASH#

1
2
3
4
5
6
7
8
location /dash/ {
types {
application/dash+xml mpd;
video/mp4 m4s;
}
root /var/videos/dash;
add_header Cache-Control public;
}

Nginx 的限速功能(保护源站带宽)#

1
2
3
4
5
6
# 限制每个 IP 的视频流带宽
location /video/ {
limit_rate 2m; # 每个连接限速 2MB/s
limit_rate_after 1m; # 前 1MB 不限速(快速起播)
root /var/videos;
}

这个 limit_rate_after 很聪明——先让你快速拿到前面的数据(快速起播),然后再限速(防止占太多带宽)。和 DASH 的思路异曲同工。


五、视频会议:实时多媒体的硬核战场#

5.1 和流媒体的区别#

流式视频(YouTube、B 站)是 存储视频——内容已经存在服务器上,可以提前编码、切分、缓存,对延迟的要求是”尽量低”但不是”必须低”。

视频会议(Zoom、腾讯会议、Discord)是 实时交互——画面是即时的,没有预编码和缓存的机会,延迟必须 < 300ms,否则人类就会觉得”卡”。

生活类比:流式视频像看录播的电视剧——你可以暂停、倒退、换清晰度。视频会议像打电话——你说完一句,对方 0.5 秒后才听到,这对话就没法正常进行了。

5.2 视频会议的架构#

经典架构:MCU(Multipoint Control Unit)#

早期视频会议用集中式服务器 MCU:

1
2
3
用户A ──┐
用户B ──┼──→ MCU 服务器(解码所有流 → 合成画面 → 重新编码 → 发给所有人)
用户C ──┘

问题:MCU 服务器的计算压力极大(要解码+编码 N 路视频),延迟也高。

现代架构:SFU(Selective Forwarding Unit)#

WebRTC 时代的主流方案:

1
2
3
用户A ──发送多路质量的流──→ SFU ──→ 转发给 B 和 C(按需选择质量)
用户B ──发送多路质量的流──→ SFU ──→ 转发给 A 和 C
用户C ──发送多路质量的流──→ SFU ──→ 转发给 A 和 B

SFU 不解码视频,只做选择性转发。它就像一个聪明的快递分拣员——不看包裹里是什么,只看标签决定转发给谁。

这和 DASH 的”多质量等级”思想一脉相承:发送端同时发多路不同质量的流(Simulcast),SFU 根据接收端的带宽选择转发哪一路。

5.3 WebRTC:浏览器的实时通信#

WebRTC 是浏览器内置的实时通信框架,核心技术包括:

  • 音视频编解码:VP8/VP9、H.264、Opus 音频
  • 传输协议:RTP over UDP(不走 TCP!实时性优先)
  • NAT 穿透:ICE/STUN/TURN 协议组合拳
  • 拥塞控制:自定义的 GCC(Google Congestion Control)

为什么 WebRTC 不用 TCP?因为实时通信中,迟到的帧不如丢弃的帧。如果用 TCP,一个丢包会导致后续所有数据被阻塞(队头阻塞),视频就卡住了。UDP 丢了就丢了,后面的帧照常到达,画面顶多闪一下。


六、RTP 与 RTCP:多媒体的传输协议#

6.1 RTP(Real-time Transport Protocol)#

既然视频会议用 UDP,但 UDP 啥都不保证,怎么知道包的顺序?怎么知道时间戳?这就需要 RTP。

RTP 运行在 UDP 之上,给每个包加上了:

  • 序列号:检测丢包和重排序
  • 时间戳:音视频同步
  • 负载类型:标识编码格式(H.264、Opus 等)
  • SSRC:标识发送源(多个人的流混在一起时区分谁是谁)
1
2
3
4
5
6
7
8
9
10
RTP 包头:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | 序列号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 时间戳 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 同步源标识 (SSRC) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

6.2 RTCP(RTP Control Protocol)#

RTP 只管传数据,但发送方需要知道:接收方收到了吗?网络质量怎么样?这就需要 RTCP。

RTCP 定期发送控制包,包含:

  • 接收报告(RR):告诉发送方”我收到了多少、丢了多少、延迟抖动多大”
  • 发送报告(SR):告诉接收方”我发了多少数据”

发送方根据 RTCP 的反馈来动态调整编码率和发送速率。这就是 网络自适应 的基础。

生活类比:RTP 是你不断往邮筒里塞信(数据包),RTCP 是收件人偶尔给你回一张明信片说”最近信件到达率 85%,有几封晚了 3 天”。你根据反馈调整寄信策略。


七、QoS:服务质量保障#

7.1 为什么需要 QoS?#

当网络拥塞时,所有流量一视同仁地丢包和延迟。但问题是:你的 SSH 会话丢几个包只是屏幕闪一下,而你的视频会议丢几个包就是”你说什么?我听不——“。

QoS(Quality of Service) 的目标就是:在网络资源有限时,优先保障重要流量的质量

生活类比:QoS 就像医院的分诊制度。所有人都挤在急诊室(路由器队列),但心脏病发作的(实时语音)优先看,崴脚的(文件下载)慢慢等。

7.2 QoS 的关键技术#

调度策略(Scheduling)#

路由器在出口有多个队列,如何决定先发哪个?

  • FIFO(First In First Out):先来先服务,默认策略,没有优先级
  • Priority Queuing(优先级队列):高优先级队列先清空,低优先级等着。问题是低优先级可能被饿死
  • Round Robin(轮询):轮流给每个队列发,公平但没优先级
  • WFQ(Weighted Fair Queuing,加权公平队列):给每个队列分配权重,按比例分配带宽。最常用
1
2
3
4
5
权重分配示例:
语音流 权重 50 → 分到 50% 带宽
视频流 权重 30 → 分到 30% 带宽
HTTP 权重 15 → 分到 15% 带宽
其他 权重 5 → 分到 5% 带宽

流量标记(DSCP / ToS)#

IP 包头有个字段叫 DSCP(Differentiated Services Code Point),6 个 bit,可以标记 64 个优先级。

路由器看到标记后,把包放进对应的队列。常见标记值:

  • EF(Expedited Forwarding):最高优先级,用于实时语音
  • AF(Assured Forwarding):中等优先级,分多个子级别
  • BE(Best Effort):默认,尽力而为

流量整形(Traffic Shaping)#

令牌桶(Token Bucket) 算法限制流的速率:

1
2
3
4
5
6
7
8
9
10
令牌桶原理:
- 桶里有令牌,每个令牌代表发送一定数据的权限
- 令牌以固定速率 r 加入桶中
- 桶满了(容量 b),多余的令牌丢弃
- 发送数据时,消耗对应数量的令牌
- 没有令牌 → 等待

效果:
- 平均速率 = r(令牌生成速率)
- 突发容量 = b(桶大小),允许短时间的流量突发

生活类比:令牌桶就像游乐场的过山车。每分钟放 10 个人上车(令牌生成速率 r),但排队区最多容纳 50 人(桶大小 b)。平时队不长,大家按 10 人/分钟上车。节假日突然来了 100 人?前 50 人排队,后面的人就得等——或者直接被劝退(丢包)。

主动队列管理:RED#

当队列太长时,所有包都经历高延迟。RED(Random Early Detection) 在队列快满之前就开始随机丢包,给发送方 TCP 一个”嘿,快堵了,慢点发”的信号。

这比等队列满了再全部丢包好得多——后者会导致 TCP 全局同步(所有连接同时减速,带宽利用率暴跌)。

7.3 QoS 的现实困境#

理论上 QoS 很美好,实践中却很难全面部署:

  1. 端到端问题:你的包要经过多个 ISP,每个 ISP 的 QoS 策略不同。你标记了 EF 优先级,别人的路由器可能直接忽略
  2. 资源分配之争:谁的语音通话比谁的重要?运营商之间很难达成一致
  3. 识别困难:Deep Packet Inspection(DPI)能识别流量类型,但加密(HTTPS/TLS)让 DPI 很难工作

所以现代方案更倾向于Provisioning(过度配置带宽)——带宽够用的话,QoS 的问题根本不存在。这也是为什么近年来 QoS 的讨论越来越少了——光纤入户、5G 时代,带宽不再是稀缺资源。

但对你的服务器来说,Nginx 的 limit_ratelimit_conn 就是一种轻量级 QoS,在带宽有限时仍然很有用。


八、内容分发的新趋势:P2P + CDN 混合#

8.1 P2P 辅助 CDN#

纯 CDN 的成本很高(每个节点都要服务器和带宽)。P2P-CDN 混合方案让用户之间互相分享数据,减轻服务器压力。

WebRTC 的 DataChannel 让浏览器之间可以直接传数据,不需要插件。这就催生了基于浏览器的 P2P-CDN:

1
2
3
4
5
6
7
8
9
传统 CDN:
CDN节点 → 用户A
CDN节点 → 用户B
CDN节点 → 用户C
(服务器带宽 = 3 份流量)

P2P-CDN:
CDN节点 → 用户A → 用户B → 用户C(部分数据从 A 和 B 互相获取)
(服务器带宽 = 1~1.5 份流量)

B 站和部分直播平台已经在用这种技术了。你在看直播时,你的浏览器其实在悄悄给其他观众传数据

8.2 边缘计算#

CDN 节点不再只是缓存静态文件,而是在边缘节点运行代码(Cloudflare Workers、AWS Lambda@Edge)。这意味着:

  • 视频转码可以在边缘完成
  • 个性化推荐可以在边缘处理
  • ABR 策略可以在边缘优化

九、完整链路:你发一条视频消息的全旅程#

把今天学的全部串起来。假设你在微信发一条 30 秒的视频消息给朋友:

1
2
3
4
5
6
7
8
1. 拍摄 → 手机编码(H.264/H.265 压缩,减少数据量)
2. 上传 → HTTP POST 到微信服务器
3. 服务器处理 → 转码成多个质量等级(DASH/HLS)
4. 分发 → 通过 CDN 缓存到全国各节点
5. 朋友点击 → DNS 解析到最近的 CDN 节点
6. 播放 → DASH 自适应选择合适画质
7. 传输 → HTTP over TCP(非实时,所以用 TCP 没问题)
8. 解码 → 朋友的手机解码播放

如果你俩在视频通话呢?流程完全不同:

1
2
3
4
5
6
7
1. 采集 → 摄像头/麦克风实时采集
2. 编码 → 实时编码(VP8/H.264)
3. 传输 → RTP over UDP(实时性优先!)
4. NAT 穿透 → STUN/TURN 服务器帮忙打通连接
5. 转发 → 通过 SFU 服务器转发给对方
6. 反馈 → RTCP 报告网络质量,动态调整码率
7. 解码播放 → 对方的设备实时解码

同一个”视频”,因为场景不同,背后用的技术完全不一样。这就是计算机网络的美妙之处——没有银弹,只有适合场景的设计。


关键知识点回顾#

📌 流媒体核心概念#

概念 一句话总结
流式传输 边下边看,不需要下载完整文件
DASH 服务器切多质量等级的视频块,客户端按带宽自适应选择
Manifest 文件 告诉客户端有哪些版本可选的”菜单”
ABR 算法 客户端决定下一块选哪个质量的核心算法

📌 CDN 核心概念#

概念 一句话总结
CDN 全球部署缓存节点,让用户从最近的节点获取内容
DNS 重定向 通过 DNS 把用户引导到最近的 CDN 节点
缓存命中率 命中率越高,回源流量越少,性能越好
缓存策略 Pull(被动拉取)、Push(主动推送)、TTL(有效期)

📌 实时多媒体核心概念#

概念 一句话总结
RTP UDP 之上的实时传输协议,带序列号和时间戳
RTCP RTP 的控制协议,反馈网络质量
WebRTC 浏览器内置的实时通信框架
SFU 选择性转发单元,现代视频会议的核心架构
Simulcast 发送端同时发多质量等级的流

📌 QoS 核心概念#

概念 一句话总结
QoS 在有限资源下优先保障重要流量
调度策略 FIFO / 优先级 / WFQ 决定队列发送顺序
DSCP IP 包头标记优先级的字段
令牌桶 限制流量速率的经典算法
RED 主动队列管理,避免全局同步

Day 14 预告:网络安全——加密、认证、SSL/TLS、防火墙 🔒#

今天我们聊了多媒体数据怎么高效传输,但有一个问题一直被我们忽略:这些数据在传输过程中安全吗? 你在咖啡厅连公共 WiFi 看视频,旁边的人能截获你的流量吗?你确定你在访问的是真正的 bc970321.cn 而不是钓鱼网站?

Day 14 我们进入计算机网络最关键的领域——安全。我们会聊:

  • 对称加密 vs 非对称加密:一把钥匙还是两把钥匙?
  • 数字签名与证书:怎么证明”我就是我”?
  • SSL/TLS:HTTPS 背后的守护神
  • 防火墙与 IDS/IPS:网络安全的门卫和监控摄像头
  • VPN:在公共网络上建一条加密隧道

网络安全是整个计算机网络学习的收官之战,也是现实中最重要的话题。毕竟,一个不安全的网络,速度再快也没用。

“There are two types of companies: those that have been hacked, and those who don’t know they have been hacked.” — Robert Mueller

我们 Day 14 见!🚀


本文是《计算机网络:自顶向下方法》(第 8 版)读书笔记系列的第 13 篇。系列文章持续更新中。

计算机网络:自顶向下方法 - Day 10 | IP 协议详解与路由算法

📖 Day 10 / 15 · 《计算机网络:自顶向下方法》阅读笔记

每天 30 分钟,15 天读懂计算机网络。今天我们进入网络层的核心——IP 协议。IPv4、IPv6、子网掩码、CIDR、DHCP、NAT,再加上路由算法(链路状态、距离向量、OSPF、BGP),一次讲透。这是计算机网络中最「硬核」的一天,撑过去,后面的内容就是 downhill。

计算机网络:自顶向下方法 - Day 9 | 网络层概述、数据平面与路由器工作原理

Day 9 — 网络层概述、数据平面与路由器工作原理#

前情回顾:Day 7 我们聊了 UDP 和可靠数据传输原理,Day 8 深入 TCP 的拥塞控制。传输层的故事暂时告一段落——但你会发现,不管 TCP 还是 UDP,它们都建立在一个假设之上:网络层会把数据从源送到目的。这个”网络层”到底怎么送的?今天我们终于要打开这个黑盒,看看互联网的”快递分拣中心”——路由器——是怎么工作的。


一、网络层是个什么层?#

1.1 从传输层到网络层#

回顾一下我们的五层模型:

1
2
3
4
5
应用层    ← HTTP, DNS, SMTP...
传输层 ← TCP, UDP
★网络层★ ← IP, 路由协议
链路层 ← Ethernet, WiFi
物理层 ← 光、电、无线信号

传输层的 TCP/UDP 负责端到端的通信(进程到进程),而网络层负责主机到主机的通信。你可以理解为:

  • 传输层:你给快递员打电话说”请把这个包裹送到张三家,交给张三本人”(进程到进程,端口寻址)
  • 网络层:快递分拣中心看地址,决定这个包裹要从北京发到上海(主机到主机,IP 寻址)

🎯 生活类比:传输层像是快递单上的收件人姓名和电话(具体到人),网络层像是收件地址里的城市和街道(具体到地方)。你写的快递单两层信息缺一不可,网络世界也一样。

1.2 网络层的两大功能#

网络层可以说是整个互联网最忙的一层,它有两个核心功能:

① 转发(Forwarding)——数据平面

当一个数据包到达路由器的某个输入端口,路由器要决定:这个包该从哪个输出端口送出去?这个决策是本地的、即时的,就在路由器内部完成。

② 路由(Routing)——控制平面

路由器们需要互相交流,搞清楚整个网络的拓扑结构,然后在每台路由器上建立起转发表(Forwarding Table),告诉转发逻辑”遇到去某个网络的包,应该从哪个端口送出去”。这个决策是全局的、慢速的,需要路由协议(如 OSPF、BGP)的参与。

对比项 转发(数据平面) 路由(控制平面)
时间尺度 纳秒级(每个包都要处理) 秒~分钟级(拓扑变化才更新)
作用范围 单个路由器内部 整个网络/自治系统
关键设备 路由器的硬件/软件 路由协议和算法
类比 快递员看地址分拣包裹 快递公司规划物流线路

🎯 生活类比:想象一个巨大的快递分拣中心。转发就是传送带上的包裹到了一个分叉口,工作人员看一眼邮编,推到对应的滑道里——这个动作每秒发生几百万次。路由则是公司的调度部门根据全国道路情况、新开的网点、拥堵的高速,定期更新一份”路由指南”发到每个分拣中心——这个动作可能一天才更新一次。

1.3 数据平面 vs 控制平面#

这是第 8 版书里特别强调的一个架构视角:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────┐
│ 网络层 │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ 数据平面 │ │ 控制平面 │ │
│ │ (Data Plane) │ │ (Control Plane) │ │
│ │ │ │ │ │
│ │ • 转发 │ │ • 路由算法 │ │
│ │ • 查表 │ │ • 路由协议 │ │
│ │ • 移交到输出 │ │ • 维护转发表 │ │
│ │ • 硬件实现 │ │ • 软件实现 │ │
│ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────┘

数据平面是路由器的”肌肉”——快速、机械、每包执行。通常用硬件(ASIC、FPGA)实现,追求速度。

控制平面是路由器的”大脑”——思考、协商、更新。通常用软件实现,运行路由协议进程。

传统路由器把这两个功能合在一台设备里。但 SDN(Software-Defined Networking,软件定义网络)的革命性思想就是:把控制平面从路由器里抽出来,放到一个集中的控制器上。就像连锁快餐店把菜单决策权从每家门店收回总部——门店只管按菜单做菜(转发),总部负责研发新品和调整策略(路由)。

🔧 实战关联:你家跑 OpenWrt 的路由器就是一个完整的传统路由器。OpenWrt 里 ip route 显示的 routing table 就是控制平面维护的转发表,而路由器芯片内部的硬件转发芯片(如 MediaTek/Qualcomm 的 SoC)就是数据平面。当你跑 tc qdisc 做流量整形时,你其实是在数据平面上动手脚!


二、路由器的”解剖图”#

现在让我们打开路由器的外壳,看看里面长什么样。一个通用的路由器架构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
       输入端口          交换结构          输出端口
┌────────┐ ┌─────────┐ ┌────────┐
线路 │ 物理层 │ │ │ │ 物理层 │ 线路
──────┤ 链路层 │─────▶│ 交换 │─────▶│ 链路层 ├──────
│ 网络层 │ │ 结构 │ │ 网络层 │
│(查找/ │ │ │ │(排队/ │
│ 转发) │ │ │ │ 调度) │
└────────┘ └─────────┘ └────────┘
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ 路由处理器 │
│ (控制平面:路由协议、维护转发表) │
└─────────────────────────────────────────┘

路由器由四大组件构成:

  1. 输入端口(Input Ports)
  2. 交换结构(Switching Fabric)
  3. 输出端口(Output Ports)
  4. 路由处理器(Routing Processor)

我们逐一拆解。


三、输入端口:门口的”安检+导航员”#

3.1 输入端口的三层结构#

输入端口不止是一个”网线口”那么简单。它从物理层到网络层都有处理逻辑:

1
2
3
4
5
6
7
8
9
10
┌──────────────────────────────────┐
│ 物理层:终止物理链路 │
│ (光电信号 → 比特流) │
├──────────────────────────────────┤
│ 链路层:处理链路层协议 │
│ (如 Ethernet 帧的解封装) │
├──────────────────────────────────┤
│ 网络层:查找转发表,决定输出端口 │
│ (最长前缀匹配) │
└──────────────────────────────────┘

🎯 生活类比:输入端口就像公司大楼的前台。物理层是保安检查你的车有没有停对位置,链路层是前台确认你是不是从合作公司来的(链路层帧),网络层是前台查访客系统,决定你应该去几楼哪个会议室。

3.2 最长前缀匹配(Longest Prefix Matching)#

这是输入端口网络层的核心工作。转发表大概长这样:

目的网络前缀 输出接口 下一跳
11001000.10101010.00000000.00 接口0
11001000.10101010.10100000.00 接口1
11001000.10101010. 接口2
0.0.0.0/0(默认路由) 接口3 网关

当一个目的 IP 为 192.168.160.111001000.10101010.10100000.00000001)的包到达时,路由器需要在表里找最匹配的那一条。

  • 第一条 ...00000000.00/26 不匹配
  • 第二条 ...10100000.00/26 匹配!26 位前缀
  • 第三条 192.168.x.x/16 也匹配,但只有 16 位前缀

最长前缀匹配的规则是:选择匹配位数最多的那条——这里是第二条(26 位 > 16 位),从接口 1 输出。

🎯 生活类比:你寄快递写的地址是”北京市海淀区中关村大街1号”。快递分拣中心有一张表:

  • “北京市” → 发到北京转运中心
  • “北京市海淀区” → 发到海淀分部
  • “北京市海淀区中关村” → 发到中关村网点

三个都匹配,但当然选最精确的那个——最长的前缀匹配!

3.3 为什么要在输入端口做查找?#

你可能会问:为什么不把包直接送给路由处理器(CPU)去查表?因为太慢了

现代骨干路由器每个端口要处理 100Gbps 甚至 400Gbps 的流量。每个包的转发决策必须在纳秒级完成。如果每个包都走 CPU 查表,CPU 早就累趴了。所以输入端口上有专用的硬件查找引擎(TCAM,三态内容寻址存储器),一个时钟周期就能完成查找。

🔧 实战关联:在 OpenWrt 上跑 ip route get 8.8.8.8,你看到的就是路由查找的结果。你的家用路由器虽然速度不比骨干路由器,但原理一样——每个到达的包都要走一遍这个查找过程。当你用 frp 做内网穿透时,frp 服务器收到的数据包要转发到你内网的服务,这个过程中经过的每一个路由器都在做最长前缀匹配。


四、交换结构:路由器的”十字路口”#

查完表,知道了包该从哪个输出端口出去,接下来就是把包从输入端口送到输出端口。这个”搬运”动作就是交换结构要干的事。

有三种经典的交换方式:

4.1 经内存交换(Switching via Memory)#

最早的路由器其实就是一台普通计算机,用 CPU 来”交换”:输入端口把包写入内存,CPU 再从内存读出来写到输出端口。

1
2
输入端口 ──▶ [ 内存 ] ──▶ 输出端口
(CPU 介入)

优点:简单,用现成的硬件就行。

缺点:慢!每个包要两次内存访问(读+写),而且 CPU 要介入。如果内存带宽是 B,那最大吞吐量是 B/2。而且 CPU 处理速度有限。

🎯 生活类比:就像你一个人在家里搬东西。左手从门口拿进来放桌上(写内存),右手再从桌上拿到后门口递出去(读内存)。一个人两手交替,速度可想而知。

4.2 总线交换(Switching via Bus)#

输入端口把包放到一条共享总线上,输出端口从总线上取走。

1
2
3
输入端口0 ──┐
输入端口1 ──┼──[ 共享总线 ]──┬── 输出端口0
输入端口2 ──┘ └── 输出端口1

优点:比内存交换快,不需要 CPU 介入。

缺点:总线是共享的!同一时刻只能有一个端口往总线上放数据。如果 N 个输入端口同时有包要送,只有一个能成功,其他要等。总线带宽成了瓶颈。

🎯 生活类比:公司只有一部电梯。每层楼的人都要用它下楼。同一时间只能载一批人,大家只能排队等。楼层多了就堵成一锅粥。

大多数家用路由器(包括 OpenWrt 设备)用的就是总线交换架构。因为家用场景流量不大,总线够用了。但在骨干网,总线交换就力不从心了。

4.3 互联网络交换(Switching via Interconnection Network)#

这是高性能路由器用的方案。用一个多级的交叉开关矩阵(Crossbar Switch),让多个输入-输出对同时交换数据。

1
2
3
4
5
6
7
          输出0   输出1   输出2   输出3
输入0 ──[×]──[ ]──[×]──[ ]──
输入1 ──[ ]──[×]──[ ]──[ ]──
输入2 ──[ ]──[ ]──[ ]──[×]──
输入3 ──[×]──[ ]──[ ]──[ ]──

× = 交叉点闭合(连通)

如果输入端口0要送到输出端口0,同时输入端口1要送到输出端口1,这两个交换可以同时进行,互不干扰。只有当两个输入端口要送到同一个输出端口时才会冲突。

优点:可以并行交换,吞吐量高,是现代骨干路由器的标配。

缺点:复杂、贵、功耗大。N×N 的 crossbar 需要 N² 个交叉点。

🎯 生活类比:从”一部电梯”升级到了”立交桥”。每条路都有自己的匝道,想去哪直接走对应的匝道,互不干扰。只有当两辆车想去同一个出口时,才需要排队。

4.4 交换结构的性能瓶颈#

无论哪种交换方式,当多个输入端口的包都想去同一个输出端口时,就会出现拥塞(HOL Blocking,Head-of-Line Blocking)。排在队头的包如果因为输出端口冲突而被阻塞,后面即使目的地是空闲端口的包也出不去——这就是队头阻塞问题。

想象你在超市收银台排队。你前面的大哥在找零钱(输出端口忙),你后面的小姐姐其实只是想退个货(另一个收银台空闲),但她排在你后面,只能等你——这就是队头阻塞。


五、输出端口:出门前的”排队等候区”#

5.1 输出端口的结构#

输出端口是包离开路由器前的最后一站:

1
2
3
4
5
6
7
8
9
10
┌──────────────────────────────────┐
│ 网络层:排队管理 + 调度 │
│ (哪个包先发?要不要丢包?) │
├──────────────────────────────────┤
│ 链路层:封装链路层帧 │
│ (加 Ethernet 头) │
├──────────────────────────────────┤
│ 物理层:发送到链路 │
│ (比特流 → 信号) │
└──────────────────────────────────┘

5.2 为什么要排队?#

排队的根本原因:到达速率 > 输出链路的发送速率

想象一个水槽。进水管的水流(到达的包)大于出水管的水流(输出链路带宽),水就会在槽里积攒。这就是排队。

1
2
3
4
5
6
       到达的包                    输出链路
┌──┐ ┌──┐ ┌──┐ ↓
──────▶│ │ │ │ │ │──────────▶ (100 Mbps)
│ │ │ │ │ │
└──┘ └──┘ └──┘
输出队列(缓存)

当队列满了,新来的包就只能被丢弃(Drop)或者挤掉已有的包(如果用了 AQM 主动队列管理)。

5.3 排队场景#

考虑两种排队场景:

场景一:输入排队

当交换结构来不及处理所有输入端口的包时,包在输入端口排队。前面提到的 HOL 队头阻塞就是输入排队的主要问题。

场景二:输出排队

当多个输入端口的包同时到达同一个输出端口时(交换结构足够快,但输出链路只有一条),包在输出端口排队。

实际路由器中,输出排队更常见,因为现代 crossbar 交换结构速度已经足够快,瓶颈通常在输出链路带宽上。

5.4 队列管理策略#

队列满了怎么办?有几种策略:

① 尾丢弃(Tail Drop,FIFO/DropTail)

最简单的策略:队列满了,新来的包直接丢弃。

1
2
队列状态:[包A][包B][包C][包D][包E] ← 满了!
新包F到达:💀 丢弃

缺点:会导致”全局同步”问题——TCP 流同时感知到丢包,同时降低发送速率,然后同时增大,导致网络流量呈锯齿状波动。

② 随机早期检测(RED, Random Early Detection)

在队列还没满的时候就开始以一定概率随机丢包,给 TCP 发送方”温柔”的信号:”快了快了,悠着点”。

1
2
3
队列长度 < min_threshold → 不丢包
min_threshold < 队列长度 < max_threshold → 以概率 p 丢包
队列长度 > max_threshold → 丢包

RED 的聪明之处在于:在崩溃之前就发预警,而不是等到队列满了才暴力丢包。

🎯 生活类比:尾丢弃就像高速收费站在车已经堵了一公里后才关闭入口。RED 则是在车排队到 200 米时就放一块”前方拥堵,建议绕行”的牌子,让一部分车主动分流。

③ 公平排队(Fair Queuing)

不同流轮流发送,避免一个贪婪的流量霸占整个队列。

🎯 生活类比:银行窗口排队,每个人限办 3 分钟,时间到了下一位。不会因为前面有个大哥办 50 张银行卡的复杂业务就让你等一下午。

5.5 调度策略(Scheduling)#

当输出端口有多个包排队时,谁先走?这就是调度器要回答的问题。

调度策略 规则 特点
FIFO(先来先服务) 按到达顺序发送 最简单,但无法区分优先级
优先级排队 高优先级先发 可能导致低优先级饿死
加权公平排队(WFQ) 按权重分配带宽 各流公平,可保证最低带宽
缺口轮转(Round Robin) 轮流给每个流发送机会 简单的公平

🔧 实战关联:OpenWrt 的 SQM(Smart Queue Management)就是一个典型的输出端口队列管理 + 调度系统。当你在家开 Zoom 会议的同时下载大文件,SQM 通过 CAKE 或 fq_codel 算法,保证 Zoom 的包(低延迟需求)优先发送,而下载流量(吞吐量需求)排队等候。这背后就是今天讲的排队管理和调度策略!


六、把所有部件串起来:一个包的路由器之旅#

让我们追踪一个 IP 数据包穿过路由器的完整过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 包从物理链路到达输入端口 X

2. 输入端口 X:
① 物理层:光电信号 → 比特流
② 链路层:解封装 Ethernet 帧,提取 IP 数据报
③ 网络层:用目的 IP 查转发表(最长前缀匹配)
→ 决定输出端口 Y

3. 交换结构:把包从输入端口 X 送到输出端口 Y
(如果用 crossbar,可能和其他交换并行进行)

4. 输出端口 Y:
① 网络层:包进入输出队列,等待调度
② 链路层:封装成 Ethernet 帧
③ 物理层:比特流 → 信号,发到链路上

5. 包继续它的旅程,到达下一个路由器,重复以上过程

🎯 完整类比:想象你是一封信。

  1. 邮递员把你从信箱里取出来(物理层接收)
  2. 分拣员检查信封上的地址(链路层解封装 + 网络层查表)
  3. 分拣员决定你该发往哪个城市的分拣中心(最长前缀匹配 → 输出端口)
  4. 你被放到传送带上,送到了正确的发货口(交换结构)
  5. 发货口前面排了很多信,你在队列里等了一会儿(输出排队)
  6. 终于轮到你了,你被装到邮车里出发了(调度发送)
  7. 到了下一个分拣中心,重复以上过程……

一封从北京寄到上海的信,中间可能经过 5-10 个分拣中心。一个从你的电脑发到 B 站服务器的数据包,中间通常也要经过 5-15 台路由器。每经过一台路由器,就叫一跳(hop)。


七、实战分析:你家路由器的一天#

7.1 OpenWrt 路由器 = 小型路由器#

你家里的 OpenWrt 路由器就是一个麻雀虽小五脏俱全的路由器:

1
2
3
4
5
# 查看路由表(控制平面的产物)
root@openwrt:~# ip route show
default via 192.168.1.1 dev eth0 # 默认路由
10.0.0.0/24 dev br-lan proto kernel # 局域网
192.168.1.0/24 dev eth0 proto kernel # WAN 口
  • 输入端口:WAN 口(eth0)和 LAN 口(br-lan,其实是一个 bridge)
  • 交换结构:多数用总线交换(SoC 集成)
  • 输出端口:同上,WAN/LAN 口复用
  • 路由处理器:路由器的 CPU(可能就是一个小小的 ARM 核)
  • 转发表ip route 看到的路由表

7.2 frp 数据转发的路由过程#

当你用 frp 做内网穿透时,数据包经过了哪些路由器?

1
2
3
4
5
你的服务器(内网 192.168.1.100)
→ 家里路由器 (OpenWrt, 192.168.1.1)
→ 光猫 (桥接/路由模式)
→ 运营商网络 (经过多台路由器)
→ frp 服务器 (公网 IP, 如 1.2.3.4)

每一跳,路由器都在做同样的事:

  1. 收到包,看目的 IP
  2. 最长前缀匹配,决定下一跳
  3. 通过交换结构送到对应端口
  4. 从输出端口发出去

frp 服务器收到包后,解封装,看到的是你内网服务的应用层数据(比如 HTTP 请求),然后再转发给目标服务。从网络层的角度,frp 服务器只是路径上的一个中间节点,但它同时也是一个应用层代理——既走路由器的活(网络层转发,不过是通过应用层代理实现的),又做应用层的事。

💡 有趣观察:frp 本质上是在应用层模拟了网络层的转发功能。真正的路由器在 IP 层转发,而 frp 在 TCP/应用层转发。这就像快递公司(IP 路由)和跑腿小哥(frp 代理)都能帮你送东西,但快递公司走的是自动化分拣流水线,跑腿小哥走的是”人肉转交”——速度和效率不可同日耳语,但跑腿小哥能送的”东西”更灵活。


八、路由器的性能指标#

8.1 吞吐量#

路由器的吞吐量是指单位时间内能处理的数据量。这取决于三个环节中最慢的那个:

  1. 输入端口处理速度
  2. 交换结构带宽
  3. 输出端口发送速度

木桶效应——哪个环节最慢,整个路由器的吞吐量就被限制在哪里。

8.2 延迟#

一个包穿过路由器的时间包括:

  • 处理延迟:查表、检查首部(通常微秒级)
  • 排队延迟:在输出队列等待的时间(取决于拥塞程度,从 0 到毫秒级不等)
  • 交换延迟:通过交换结构的时间(通常纳秒级)

注意,路由器的延迟主要来自排队。如果你家的网速测试显示延迟很高,很多时候不是路由器处理慢,而是队列太长了!

8.3 丢包率#

当队列满了,包被丢弃。丢包率是衡量路由器拥塞程度的重要指标。TCP 会根据丢包调整发送速率——这就是 Day 8 讲的 TCP 拥塞控制的基础。现在你知道了:TCP 感知到的”丢包”,本质上很多时候就是路由器输出端口队列满了之后的尾丢弃(或 RED 丢弃)。

💡 恍然大悟时刻:TCP 拥塞控制和路由器排队管理其实是一个闭环系统。路由器丢包 → TCP 减速 → 路由器队列变短 → 不再丢包 → TCP 加速 → 路由器队列又变长 → 又开始丢包……如此循环。理解了这个循环,你就理解了互联网流量自我调节的核心机制。


关键知识点回顾#

让我们用一张表总结今天的内容:

概念 要点
网络层两大功能 转发(数据平面,本地快速决策)+ 路由(控制平面,全局拓扑计算)
数据平面 vs 控制平面 数据平面硬件实现每包转发,控制平面软件实现路由协议;SDN 将二者分离
路由器四大组件 输入端口、交换结构、输出端口、路由处理器
输入端口核心 最长前缀匹配查找,决定输出端口
交换方式 内存交换(慢)、总线交换(中等)、互联网络/crossbar(快)
输出端口核心 排队管理(DropTail、RED)+ 调度策略(FIFO、优先级、WFQ)
排队位置 主要在输出端口排队(现代交换结构足够快)
HOL 阻塞 输入排队时的队头阻塞问题
队列管理策略 尾丢弃(简单但导致全局同步)、RED(主动预警)、公平排队
TCP 与队列的闭环 路由器丢包 → TCP 减速 → 队列缩短 → TCP 加速 → 循环

一句话总结:路由器就是一个高性能的”快递分拣中心”——输入端口看地址(最长前缀匹配),交换结构搬运包裹(crossbar 并行交换),输出端口排队发货(排队管理+调度),路由处理器在后台规划路线(路由协议)。


Day 10 预告#

今天我们了解了路由器的硬件架构和工作原理,但有一个关键的东西还没深入:路由器查的那张转发表是怎么来的? 路由器怎么知道去往某个网络该走哪条路?这涉及到:

  • IP 协议详解:IPv4 数据报格式、分片与重组
  • IPv6:为什么需要 IPv6?IPv4 和 IPv6 有什么区别?
  • DHCP:你的电脑是怎么自动获得 IP 地址的?
  • NAT:你家一个公网 IP,多台设备怎么上网的?
  • 路由算法:链路状态(LS)和距离矢量(DV)算法是怎么算出最短路径的?
  • OSPF 与 BGP:互联网的两 大路由协议

Day 10,我们将深入 IP 协议的细节,看看那个 192.168.1.1 背后的故事。咱们不见不散!


📖 读书进度:《计算机网络:自顶向下方法》第 8 版 第 4 章(4.1-4.2 节)

下期见 👋

计算机网络:自顶向下方法 - Day 7 | UDP 协议详解与可靠数据传输原理

Day 7 — UDP 协议详解与可靠数据传输原理#

前情回顾:Day 6 我们聊了可靠数据传输的”甲方”——网络层只提供”尽力而为”的服务,丢包、乱序、比特出错全不管。那传输层怎么办?今天我们就来认识两位选手:一位是”摆烂大师” UDP,另一位是”完美主义者”——可靠数据传输协议(rdt 系列模型)。


一、UDP:我就主打一个”信不信由你”#

1.1 UDP 是什么?#

UDP(User Datagram Protocol,用户数据报协议),是传输层最朴素的协议。它做的事儿少到令人发指:

  • 多路复用/多路分解:把数据送到正确的应用进程(通过端口号)
  • 差错检测:可选的校验和(checksum)
  • 没了

就这两件事。不保证交付,不保证顺序,不保证不出错。你发出去的数据包,它生不生死不死,UDP 拒不负责。

🎯 生活类比:UDP 就像街头发的传单。发的人(发送方)往街上一撒就走了,你收到没收到?传单被风吹走了?发的人根本不在乎。而 TCP 像是挂号信,必须签收、必须确认。

1.2 UDP 数据报首部格式#

UDP 首部只有 8 个字节,小得可怜:

1
2
3
4
5
6
7
8
 0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
| 源端口 | 目的端口 |
+--------+--------+--------+--------+
| 长度 | 校验和 |
+--------+--------+--------+--------+
| 数据(可选) |
+----------------------------------+
字段 大小 说明
源端口 2 字节 发送方端口号(可选,全 0 表示不需要回复)
目的端口 2 字节 接收方端口号
长度 2 字节 整个 UDP 数据报(首部+数据)的字节数
校验和 2 字节 差错检测(IPv4 中可选,IPv6 中必须)

就这么简单。首部开销 8 字节,相比之下 TCP 首部 20 字节起步,UDP 省下来的带宽在高速场景下相当可观。

1.3 UDP 校验和#

虽然 UDP “摆烂”,但它还是提供了一个校验和机制——这就像摆烂的人出门前还是看了一眼天气。

原理:发送方将数据按 16 位为一个单位,做反码求和(one’s complement sum),结果取反得到校验和。接收方收到后,把所有 16 位数据和校验和一起做反码求和,如果全为 1,说明没出错。

注意:校验和只能检测错误,不能纠正错误。发现错误后怎么办?直接丢掉。没错,UDP 的纠错策略就是——

🎯 生活类比:校验和就像快递箱上的”易碎品”贴纸。快递员(网络)看到这个标签,如果发现箱子破了,他不会帮你修补,而是直接扔掉。至于你知不知道快递丢了……那不是快递员的事。

1.4 为什么应用层要”选择”UDP?#

既然 UDP 这么不靠谱,为什么还有应用要用它?因为”不靠谱”换来了”高效率”:

优势 说明
无连接 不需要握手,想发就发
低开销 8 字节首部,省带宽
无拥塞控制 发送速率不受网络拥塞影响
实时性好 延迟低,适合实时应用

常见使用 UDP 的应用:

  • DNS:查询就一个请求一个响应,握手纯属浪费时间
  • SNMP:网络管理,偶尔丢个数据包无所谓
  • 流媒体 / 视频会议:实时性 > 可靠性,丢几帧画面可以接受
  • 游戏:你不想因为一个丢包就让整个游戏卡住等重传吧?
  • QUIC:HTTP/3 的底层协议,在 UDP 上构建了自己的可靠性(取 UDP 之长,补 UDP 之短)

1.5 实战:DNS 查询中的 UDP#

在你的实际系统中,每次你打开一个网页、解析一个域名,背后都是 UDP 在工作。来看一个真实的 DNS 查询:

1
2
3
4
5
你的电脑 → DNS服务器:UDP报文(目的端口 53)
"哥们,bilibili.com 的 IP 是多少?"

DNS服务器 → 你的电脑:UDP报文(源端口 53)
"61.135.169.125,拿去"

整个过程没有握手,没有建立连接,一发一收,干净利落。如果丢了?DNS 客户端会超时重试(这是应用层的事,UDP 不管)。

你可以用 tcpdump 或 Wireshark 实际抓包看看:

1
sudo tcpdump -i any port 53 -n

你会看到所有 DNS 查询都是 UDP,快速且高效。

💡 思考题:为什么 DNS 不用 TCP?答案:DNS 查询通常很小(一个请求一个响应),建立 TCP 连接的三次握手开销太大。UDP 直接发出去,效率高得多。不过当 DNS 响应数据超过 512 字节时,会切换到 TCP(通过 EDNS0 或区域传输)。

1.6 UDP 的可靠性补救方案#

UDP 本身不可靠,但很多应用在 UDP 上构建了自己的可靠性机制:

应用 可靠性策略
QUIC 在 UDP 上实现了类似 TCP 的可靠性 + 加密
TFTP 停等协议,每个数据包都要 ACK
你的 frp UDP 心跳 + 超时重连机制

这就是 UDP 的精髓——它给你最大的自由度,可靠性你自己加


二、可靠数据传输原理:从零造一个可靠协议#

现在进入今天的重头戏。网络层(IP 层)是不可靠的:数据包可能丢失、损坏、乱序。但传输层需要给应用层提供可靠的数据传输。

问题来了:如何在不可靠的信道上实现可靠的数据传输?

这就是 rdt(reliable data transfer)协议系列要解决的问题。教材用了渐进式的方法,一步步从最简单的版本演进到接近 TCP 的模型。

🎯 生活类比:你在网上下单买了个东西,快递可能丢、可能损坏、可能寄错地址。你(发送方)和卖家(接收方)需要一套规则,确保你最终拿到正确的东西——这就需要确认、重传、序号等机制。

2.1 rdt 1.0:完美世界(理想信道)#

假设:底层信道完全可靠——不丢包、不出错、不乱序。

这种情况下,可靠传输简单到离谱:

  • 发送方:rdt_send(data) → 把数据发出去(udt_send(packet)
  • 接收方:rdt_rcv(packet) → 把数据取出来,交给上层

不需要确认,不需要重传,不需要序号。完美!

当然,现实中的网络从来不完美,所以这只是一个起点。

2.2 rdt 2.0:引入 ARQ(有比特错误的信道)#

假设:底层信道可能发生比特错误(0 变成 1),但不会丢包。

这是个更现实的场景。如果数据在传输中出了错,接收方需要能检测到错误(通过校验和),然后告诉发送方重传。这就是 ARQ(Automatic Repeat reQuest,自动重传请求) 协议。

rdt 2.0 引入了三个机制:

  1. 差错检测:校验和
  2. 接收方反馈:ACK(确认正确)或 NAK(确认有错)
  3. 重传:收到 NAK 就重传

发送方状态机

1
2
3
4
5
6
7
8
9
[等待上层数据]
|
| rdt_send(data)
v
[等待ACK/NAK] --收到NAK--> 重传 --> [等待ACK/NAK]
|
| 收到ACK
v
[等待上层数据]

接收方状态机

1
2
3
4
5
6
7
8
9
[等待下方数据]
|
| rdt_rcv(packet) 且没出错
| → 发送ACK,提取数据交给上层
|
| rdt_rcv(packet) 且出错
| → 发送NAK
v
[等待下方数据](继续等下一个)

🎯 生活类比:你网购收到快递,当面拆开检查。东西没问题就说”OK”(ACK);发现坏了就说”退换”(NAK)。卖家收到 NAK 就重新发一个。

rdt 2.0 的致命缺陷#

如果 ACK 或 NAK 本身在传输中出了错怎么办?发送方收到的反馈不可靠!

比如发送方收到一个损坏的 ACK,它不知道这是 ACK 还是 NAK,也不知道接收方到底有没有正确收到数据。

rdt 2.1:加序号#

解决方案:给数据包加序号(0 或 1,交替使用)。这样即使 ACK/NAK 出错,发送方也能通过序号判断:

  • 如果收到对”当前序号”的 ACK → 数据被正确接收,发下一个
  • 如果收到对”上一个序号”的 ACK 或收到 NAK → 重传当前包

发送方状态:

  • 等待上层调用 0(准备发序号 0 的数据)
  • 等待 ACK 0(等序号 0 的确认)
  • 等待上层调用 1
  • 等待 ACK 1

接收方也能通过序号判断:这是新数据(序号变化)还是重传(序号和上次一样)?

🎯 生活类比:卖家发货时在快递上标注”第 1 单”、”第 2 单”。你收到后回复”第 1 单 OK”。即使你的回复在路上被弄模糊了,卖家看到”第 1 单”的字样就知道第一单没问题了。

rdt 2.2:去掉 NAK#

rdt 2.2 是 2.1 的优化版——用 ACK 携带序号代替 NAK

如果接收方发现数据出错,不发 NAK,而是发送上一个正确接收包的 ACK(即”重复 ACK”)。发送方收到重复 ACK 就知道需要重传。

这样做的好处:反馈消息只有一种类型(ACK),简化了协议。

🎯 生活类比:你不再说”这个坏了”(NAK),而是重复说”上一个好的”(重复 ACK)。卖家发现你说了两次”第 1 单 OK”,就知道第 2 单出了问题。

2.3 rdt 3.0:处理丢包(现实世界)#

假设:底层信道不仅会出错,还会丢包

这是最接近真实互联网的情况。如果数据包或 ACK 丢了,发送方会一直等下去,永远收不到回复——这就是死锁

解决方案超时重传

发送方在发送数据后启动一个定时器。如果在超时前没收到 ACK,就认为数据丢了,重传。

rdt 3.0 的发送方状态机:

1
2
3
4
5
6
7
8
9
10
11
[等待上层调用 0]
|
| rdt_send(data),发送序号0的包,启动定时器
v
[等待ACK 0]
| | |
| | +--收到ACK 0--> 停止定时器 --> [等待上层调用 1]
| |
| +--收到ACK 1(或损坏)--> 忽略,继续等
|
+--超时--> 重传序号0的包,重启定时器

这个协议被称为停等协议(Stop-and-Wait)——发送方发一个包,停下来等 ACK,收到确认后再发下一个。

🎯 生活类比:你给朋友发微信,发完之后看表。如果 5 分钟没回,你就重发一遍。虽然原始,但能保证可靠性。

停等协议的性能问题#

停等协议虽然可靠,但效率极低。考虑以下场景:

  • 数据包大小:1 KB
  • 链路带宽:1 Gbps
  • 往返时延(RTT):100 ms

发送一个包的实际传输时间:1KB / 1Gbps ≈ 8 μs
但发送方要等 RTT(100 ms)才能发下一个包。

信道利用率 = 8μs / (8μs + 100ms) ≈ 0.008%

这个效率简直惨不忍睹!就好比你有一辆法拉利(高带宽链路),但每开一米就要停下来等红灯(等 ACK),大部分时间都在等。

这就引出了我们今天最后一个重要话题——流水线协议


三、流水线协议:从”一个一个来”到”批量发送”#

3.1 核心思想#

停等协议的问题在于:发送方只允许”在路上”存在一个未确认的数据包。如果我们允许同时有多个未确认的数据包在路上,就能大幅提高信道利用率。

这就是流水线(pipelining)技术。

流水线需要两个新机制:

  1. 序号范围扩大:不再是 0/1 交替,而是每个包有唯一的序号(比如 0, 1, 2, …, N)
  2. 缓冲:发送方需要缓存已发送但未确认的包(以备重传),接收方可能需要缓存乱序到达的包

🎯 生活类比:你不再是一个一个发快递、收到确认再发下一个。而是一次发出去 10 个快递(流水线),然后等确认。效率直接拉满。

流水线协议有两种经典实现:回退 N 步(GBN)选择重传(SR)

3.2 回退 N 步(Go-Back-N, GBN)#

核心规则#

GBN 允许发送方在不等待确认的情况下连续发送最多 N 个数据包(N 称为”窗口大小”)。但它的容错策略比较粗暴——只要有一个包丢了,从那个包开始后面全都要重传

关键参数

  • 窗口大小 N:最多允许 N 个未确认的包在途
  • 基号 base:最早未确认的包的序号
  • 下一个序号 nextseqnum:下一个要发的包的序号

窗口范围:[base, base + N - 1]

1
2
已确认        窗口内(已发送未确认)    不能发
[0...base-1] [base ... base+N-1] [base+N ...]

发送方行为#

  1. 收到上层新数据

    • 如果 nextseqnum < base + N → 在窗口内,发送数据,nextseqnum++
    • 如果 nextseqnum >= base + N → 窗口满了,拒绝或缓存
  2. 收到 ACK n:GBN 使用累积确认——ACK n 表示”序号 n 及之前的所有包都正确收到了”。收到 ACK n 后,base = n + 1(窗口滑动)

  3. 超时:只对最早的未确认包(base)启动一个定时器。超时后,重传从 base 开始的所有未确认包

接收方行为#

GBN 接收方也很简单:只按序接收

  • 期望序号 expected_seqnum
  • 收到正确序号的包 → 提取数据,发 ACK,expected_seqnum++
  • 收到乱序包 → 直接丢弃,只重发最后一个正确包的 ACK

🎯 生活类比:你在食堂排队打饭。你期望第 1 号、第 2 号、第 3 号菜。如果第 2 号没来直接跳到第 3 号,你拒绝接收第 3 号,让厨房从第 2 号重新做。

GBN 的优缺点#

优点:简单。接收方不需要缓存乱序包。

缺点:浪费严重。如果窗口大小是 100,第 2 个包丢了,后面 98 个包虽然可能已经到达,但全部要重传。在丢包率高的网络中性能急剧下降。

3.3 选择重传(Selective Repeat, SR)#

核心规则#

SR 是 GBN 的优化版——只重传出错的包,不再”株连九族”。

关键设计

  • 发送方和接收方各自维护一个窗口
  • 窗口内的每个包都有独立的定时器
  • 接收方可以缓存乱序到达的包

发送方行为#

  1. 收到上层新数据

    • 窗口未满 → 发送数据,启动该包的定时器
  2. 收到 ACK n

    • 标记包 n 为已确认
    • 如果 n == base → 窗口向前滑动到第一个未确认的包
    • 停止包 n 的定时器
  3. 某个包超时:只重传那一个包,重启该包定时器

接收方行为#

  • 收到期望范围内的包(无论是否按序)→ 缓存,发 ACK
  • 收到窗口外的包 → 发 ACK(确认之前收到过)
  • 当一组连续的包都收到后,一起交给上层,窗口滑动

🎯 生活类比:你网购了 10 件商品。第 2 件丢了,但第 3~10 件正常收到,你先放着。你只让卖家补发第 2 件,收到后你按顺序把所有东西整理好。

SR 的窗口大小约束#

SR 有一个重要的窗口大小约束:

窗口大小 ≤ 序号空间大小 / 2

例如如果序号用 4 位(0~15),窗口大小最大为 8。否则会出现序号歧义——接收方无法区分一个包是新的还是重传的。

3.4 GBN vs SR 对比#

特性 回退 N 步(GBN) 选择重传(SR)
累积确认 ❌(独立确认)
接收方缓存
定时器 1 个 N 个(每包一个)
丢包后重传 base 之后全部 只重传丢失的
实现复杂度 简单 较复杂
高丢包率性能

💡 TCP 的做法:TCP 的可靠传输机制融合了 GBN 和 SR 的思想——使用累积确认(像 GBN),但也支持选择性确认 SACK(像 SR)。这是个”取其精华”的混合方案,Day 8 会详细讲。


四、实战思考:frp UDP 心跳与可靠传输#

你在实际项目中用 frp 做内网穿透,frp 的心跳机制就是一个简单的”应用层可靠传输”实现:

1
2
3
4
5
6
7
8
frpc(客户端)  →  frps(服务端)
| |
| -- UDP 心跳包 --> |
| <-- 心跳回复 --- |
| |
| 超过N次没收到回复 |
| → 认为连接断开 |
| → 触发重连 |

这其实就是 rdt 3.0 的超时重传思想的应用:

  • 定期发送心跳包(数据包)
  • 等待回复(ACK)
  • 连续超时(超时重传阈值)→ 判定连接失效 → 重连

虽然简单,但原理和今天学的可靠数据传输是相通的。


五、关键知识点回顾#

UDP#

知识点 要点
UDP 首部 8 字节:源端口 + 目的端口 + 长度 + 校验和
校验和 反码求和,只能检错不能纠错
无连接 不需要握手,低延迟
适用场景 DNS、流媒体、游戏、QUIC
可靠性补救 应用层自己实现(如 QUIC)

可靠数据传输#

模型 信道假设 关键机制
rdt 1.0 完全可靠 无需额外机制
rdt 2.0 有比特错误 ACK/NAK + 重传
rdt 2.1 有比特错误(含ACK出错) 增加序号(0/1)
rdt 2.2 有比特错误 用重复 ACK 代替 NAK
rdt 3.0 丢包 + 比特错误 超时重传(停等协议)

流水线协议#

协议 策略 确认方式 接收方缓存
GBN base 之后全部重传 累积确认 不需要
SR 只重传出错包 独立确认 需要
TCP 混合方案 累积确认 + SACK

Day 8 预告#

今天我们从 UDP 出发,一路走到了可靠数据传输的理论模型。但 rdt 只是理论模型——真实的互联网上,最广泛使用的可靠传输协议是 TCP

Day 8 我们将深入:

  • 🔸 TCP 首部详解:20 字节里藏了多少玄机?
  • 🔸 TCP 连接管理:三次握手、四次挥手,为什么不是两次?
  • 🔸 TCP 流量控制:发送方怎么知道接收方”吃不下”了?
  • 🔸 TCP 拥塞控制:网络堵了怎么办?慢启动、拥塞避免、快速重传、快速恢复

TCP 是 rdt 理论的集大成者,也是在真实网络环境中最优雅的实用协议。明天见!


📖 读书进度:第 3 章 3.3(UDP)+ 3.4(可靠数据传输原理)

🎯 推荐实验:用 Wireshark 抓一个 DNS 查询,看看 UDP 数据报的结构;再抓一个 TCP 流,感受一下流水线传输。

下篇见 👋