一、电子邮件
邮件是互联网上最早的杀手级应用(1971 年),它的设计留下了大量今天仍在影响我们的痕迹。
1.1 三个组成部分
┌─────────┐ SMTP ┌──────────┐ SMTP ┌──────────┐ IMAP ┌─────────┐
│用户代理 │ ──────→ │ 发送方 │ ──────→ │ 接收方 │ ←───── │用户代理 │
│(Outlook)│ │ 邮件服务器 │ │ 邮件服务器 │ │ │
└─────────┘ └──────────┘ └──────────┘ └─────────┘
报文队列 收件箱
- 用户代理(Mail User Agent):撰写、阅读的客户端
- 邮件服务器(Mail Transfer Agent):存放收件箱、维护待发送报文队列
- SMTP:服务器之间(以及用户代理到服务器)传递邮件的协议
⚠️ 注意箭头方向:SMTP 全程是向前推的。只有最后一段(收件人取信)用的是 IMAP/POP3/HTTP,方向相反。
1.2 SMTP 的报文交互
SMTP 运行在 TCP 25 端口(现代提交用 587 或 465),采用 ASCII 文本命令。
S: 220 mail.example.com ESMTP ready
C: HELO client.edu
S: 250 Hello client.edu
C: MAIL FROM: <[email protected]>
S: 250 [email protected] ... Sender ok
C: RCPT TO: <[email protected]>
S: 250 [email protected] ... Recipient ok
C: DATA
S: 354 Enter mail, end with "." on a line by itself
C: Subject: 关于明天的会议
C:
C: 会议改到下午三点。
C: .
S: 250 Message accepted for delivery
C: QUIT
S: 221 mail.example.com closing connection
⭐ 注意报文结束标志是一行单独的句点 .。那么如果邮件正文里真有一行只写一个句点怎么办?答案是点填充(dot-stuffing):客户端把行首的 . 变成 ..,服务端再还原。
📌 这是一个协议设计中的经典问题:如何在数据流中标记边界,同时允许数据里出现边界符号。你会在第 23 讲的链路层「字节填充」里遇到一模一样的问题和一模一样的解法。同一个问题在不同层反复出现,是这门课很重要的一条暗线。
1.3 SMTP vs HTTP:四点差异
| SMTP | HTTP | |
|---|---|---|
| 方向 | ⭐ 推(push):发送方主动送出 | ⭐ 拉(pull):接收方主动索取 |
| 编码限制 | 历史上要求 7 位 ASCII | 8 位二进制自由传输 |
| 多对象 | 所有对象放在一个报文里(MIME 多部分) | 每个对象独立的响应 |
| 连接使用 | 持续连接(可连续发多封) | 持续连接 |
| 端口 | 25 / 587 | 80 / 443 |
⚠️ 7 位 ASCII 的历史包袱:SMTP 诞生时链路只保证 7 位透明。因此发送图片、中文、任何二进制都必须先编码成 ASCII——这就是 Base64 的由来,代价是体积膨胀 33%。
今天有 8BITMIME 扩展,但由于兼容性,Base64 仍然普遍使用。一个 1982 年的假设,让全世界今天仍在为邮件附件多付三分之一的带宽。
1.4 邮件访问协议
| 协议 | 端口 | 特点 |
|---|---|---|
| POP3 | 110 / 995 | 下载后(可选)删除;状态在客户端;多设备体验差 |
| IMAP | 143 / 993 | 邮件保留在服务器;支持文件夹、部分下载、多设备同步 |
| Web 邮件 / API | 443 | 用 HTTPS,服务器端全权管理 |
📌 为什么 IMAP 赢了:多设备时代,「状态必须在服务器上」不是可选项。POP3 的模型假设你只有一台电脑。
1.5 反垃圾与身份验证(现代必备)
SMTP 同样没有内建认证——MAIL FROM 可以随便写,这就是伪造发件人如此容易的原因。三个补丁都建在 DNS TXT 记录上(第 7 讲):
| 机制 | 作用 |
|---|---|
| SPF | 在 DNS 中声明「哪些 IP 有权代表本域发信」 |
| DKIM | 发送方对邮件数字签名,公钥放在 DNS 中 |
| DMARC | 声明「SPF/DKIM 校验失败时该怎么处理」(放行/隔离/拒收)并接收报告 |
🎯 注意这个模式:DNS 再一次成为承载安全策略的基础设施。这也意味着 DNS 被攻破,邮件安全同时失效——安全依赖的传递性是第 27 讲的重要主题。
二、P2P 文件分发
2.1 核心问题
一台服务器要把大小为 F 的文件分发给 N 个用户。设:
u_s= 服务器上传速率u_i= 第 i 个用户的上传速率d_min= 所有用户中最小的下载速率
问:把文件送达所有人,最少需要多长时间?
2.2 客户机–服务器模式
两个约束,取较大者:
约束 1:服务器必须把文件发出去 N 次
服务器上传总量 = N × F
所需时间 ≥ N·F / u_s
约束 2:最慢的那个用户必须把文件下完
所需时间 ≥ F / d_min
因此:
D_cs = max( N·F / u_s , F / d_min )
⭐ 关键观察:第一项随 N 线性增长。用户翻倍,分发时间翻倍。
2.3 P2P 模式
三个约束:
约束 1:服务器至少要把文件发出去一次
时间 ≥ F / u_s
约束 2:最慢的用户要下完
时间 ≥ F / d_min
约束 3:系统的总上传能力(这一条是精髓)
系统必须总共上传 N·F 比特(每个用户各要一份),而系统的总上传能力是服务器加上所有已加入的 peer:
时间 ≥ N·F / (u_s + Σ u_i)
综合:
D_p2p = max( F/u_s , F/d_min , N·F / (u_s + Σu_i) )
2.4 为什么 P2P 自扩展
看第三项的分母:每增加一个用户,分子加 F,但分母也加了一个 u_i。
如果所有用户的上传速率都是 u,那么:
N·F / (u_s + N·u) ——→ F/u (当 N → ∞)
它收敛到一个常数,不随 N 增长。
分发时间
↑
│ ╱ 客户机-服务器(线性增长)
│ ╱
│ ╱
│ ╱
│ ────────────────────── P2P(趋于常数)
└──────────────────────────→ N
🎯 这就是「自扩展」的数学含义:新用户既是负担,也是资源。这是 P2P 相对于 C/S 唯一但决定性的优势。
2.5 BitTorrent
基本概念:
- 文件被切成 chunk(典型 256 KB)
- 参与同一文件分发的所有 peer 构成一个 torrent
- tracker 追踪谁在这个 torrent 里(现代也用 DHT 去中心化实现)
- 下载完仍在上传的 peer 叫 seed,未下载完的叫 leecher
三个核心机制:
1️⃣ 最稀有优先(Rarest First)
优先请求在邻居中副本数最少的块。
为什么:让稀有块快速扩散,避免某个块因为只有一个 seed 持有而成为瓶颈,均衡块的分布,提高整体可用性。
2️⃣ 一报还一报(Tit-for-Tat) ⭐ 激励机制
每个 peer 每 10 秒测量一次:谁给我发得最快?
选出上传速率最高的 4 个邻居 → "unchoke" 它们(向它们发送数据)
其余邻居 → "choke"(不发)
效果:你上传得越多,别人越愿意给你发。搭便车(free-riding)会被自动惩罚。
📌 这是网络课上少有的、明确引入博弈论的地方:协议不是靠中心权威强制,而是靠让合作成为个体的最优策略来维持系统健康。
3️⃣ 乐观疏通(Optimistic Unchoking)
每 30 秒随机选一个额外的邻居 unchoke。
为什么必须有它:
- 新加入的 peer 没有任何块,无法上传,因此永远不会被任何人选中——会被永久饿死
- 也让 peer 有机会发现比当前四个更快的伙伴
这是一个「探索 vs 利用」的经典权衡:99% 的资源用于已知最优(利用),少量资源用于随机探索。同样的思想出现在路由、缓存替换和强化学习中。
三、流媒体与 DASH
3.1 视频的本质
视频 = 以固定速率播放的图像序列。压缩后码率范围极大:
- 低清 100 kbps
- 1080p 约 3–6 Mbps
- 4K 约 15–25 Mbps
根本挑战:网络带宽是波动的、不可预测的(第 3 讲),而播放要求恒定的数据供应。
3.2 客户端缓冲:把抖动变成时延
网络到达速率(波动) 播放速率(恒定)
╱╲ ╱╲╱╲ ╱╲ ────────────────
╱ ╲╱ ╲╱ ╲╱ ╲ ───→ [ 缓冲区 ] ───→ 播放
缓冲区把不确定的到达速率转换成确定的播放速率,代价是初始播放时延。
这是一个通用的工程模式:用时延换确定性。
- 缓冲大 → 抗抖动强,但起播慢、切换清晰度迟钝
- 缓冲小 → 起播快,但容易卡顿(rebuffering)
3.3 DASH(Dynamic Adaptive Streaming over HTTP)
⭐ DASH 的核心思想:把视频切成若干秒一段的块,每一块编码成多个码率版本,让客户端在每次请求时自己决定要哪个版本。
manifest 文件(客户端先取):
块 1: [240p URL] [480p URL] [720p URL] [1080p URL]
块 2: [240p URL] [480p URL] [720p URL] [1080p URL]
...
客户端逻辑(每几秒一次):
测量当前可用带宽 & 缓冲区水位
→ 选择一个可持续的码率
→ 发一个普通的 HTTP GET 取该块
四个设计优点:
- ⭐ 智能全在客户端(又是端到端原则)——服务器只是普通的 HTTP 服务器
- 走 HTTP over TCP——能穿过所有防火墙和 NAT,能用所有现成的 CDN 缓存
- 客户端可以随时切换码率,也可以随时切换 CDN 节点
- 每个块是独立的 HTTP 对象,天然可缓存
码率选择算法的两大流派:
- 基于带宽:根据近期实测吞吐量预测
- 基于缓冲区:根据缓冲区水位决定(水位高就升清晰度,低就降)
实践中多数播放器两者结合,因为纯带宽预测在带宽突变时反应过慢,纯缓冲区法在起播阶段没有信息。
3.4 完整链路
用户 → DNS 解析(CDN 选节点,第 7 讲)
→ HTTP GET manifest(从云端)
→ 循环:测量 → 选码率 → HTTP GET 块(从 CDN 边缘)
→ 缓冲 → 播放
这条链路把第 5、6、7、8 讲串了起来:HTTP 的对象模型、缓存与 CDN、DNS 的节点选择、DASH 的自适应。这也是为什么应用层要放在传输层之前讲——你现在能完整看懂一个真实系统了。
四、交互式实时多媒体:RTP 与播放缓冲
4.1 和 DASH 不是一类问题
上一节的 DASH 是点播:视频早就录好放在服务器上,播放器可以提前缓冲十几秒。缓冲越深越流畅,代价只是「点开到出画面」慢一点。
交互式实时(视频会议、VoIP、云游戏)完全不同:内容是此刻才产生的,而且对面的人在等你回话。
| 点播流(DASH) | 交互式实时(VoIP / 会议) | |
|---|---|---|
| 内容何时存在 | 早已存在 | ⭐ 此刻才产生 |
| 可缓冲深度 | 5–30 秒 | ⭐ 最多几百毫秒 |
| 时延要求 | 只影响启动 | ⭐ 单向 < 150 ms 感觉自然;> 400 ms 无法正常对话 |
| 丢包处理 | ⭐ 可以重传(时间充裕) | ⭐ 来不及重传,只能隐藏 |
| 传输层 | TCP / QUIC | UDP(+ RTP) |
⭐ 这一栏解释了第 10 讲那句「宁可丢帧不可卡顿」:一个 RTT 通常已经超过了这一帧还能被播放的期限,重传回来的数据即使正确也已经没有播放时刻可用了。丢了就是丢了,重传毫无意义。
4.2 三种损伤
- 丢包——网络拥塞或无线误码。
- 端到端时延——超过阈值后交互体验崩溃。
- ⭐ 时延抖动(jitter)——同一条流里不同分组经历的时延不同。
抖动是三者里最麻烦的。发送方每 20 ms 产生一个语音块,节奏是完全均匀的;但排队时延随时在变,到达接收方时间隔已经不均匀了。如果收到就播,声音会忽快忽慢、断断续续。
4.3 播放缓冲:把可变时延变成恒定时延
解决办法是接收方故意延后播放:收到分组后先放进缓冲区,等到一个统一约定的时刻再播。
第 i 个分组:
生成时刻 t_i (发送方按固定间隔产生,如每 20 ms 一个)
到达时刻 r_i (经历了可变的网络时延)
播放时刻 p_i = t_i + q
q = 固定播放时延(从生成算起,统一延后 q 再播)
判定:若 r_i > p_i,该分组【错过播放时刻】,等同丢失而被丢弃
⭐ 核心权衡:
q越大 → 越少分组错过播放 → 但交互时延越大q越小 → 交互越自然 → 但更多分组来不及,有效丢包率上升
这是一个没有最优解、只有取舍点的问题,而且取舍点取决于当前网络状况——所以实际系统用自适应播放时延:在每一段静默期(talk spurt 之间)重新估计网络时延与抖动,调整 q。选在静默期调整,是因为此时拉长或压缩一小段静音人耳听不出来,而在说话中间改播放时延会导致音调变化。
4.4 RTP:给 UDP 补上时序信息
UDP 只提供端口和校验和。要重建时序,需要在应用层自己加——这就是 RTP(Real-time Transport Protocol,RFC 3550)。
| RTP 字段 | 作用 |
|---|---|
| 序号(16 位) | ⭐ 检测丢包与乱序(UDP 不保证顺序) |
| 时间戳(32 位) | ⭐ 标记该块的采样时刻,接收方据此重建原始节奏并计算抖动 |
| SSRC | 同步源标识,区分同一会话里的不同发送者 |
| 载荷类型 | 标明编解码器(G.711、Opus、H.264…),允许中途切换 |
⚠️ RTP 本身不提供任何服务质量保证。它不重传、不保证时延、不预留带宽——它只是把时序信息带上,让接收方有能力去重建。RTP 跑在 UDP 之上,是一个应用层协议。
RTCP 是它的控制伴侣:接收方周期性回送报告,包含丢包率、抖动估计、往返时延。发送方据此调整编码码率——⭐ 这是一个应用层的、非 TCP 的拥塞响应机制。RTCP 的报文量被限制在会话带宽的 5% 以内,以免控制流量本身成为负担。
SIP 则负责会话本身:找到对方(用户可能在任何设备上)、协商双方都支持的编解码器、通话中途改变会话参数、结束通话。⭐ SIP 管「建立通话」,RTP 管「传语音」——和 HTTP 与 TCP 的分工类似。
4.5 丢不起又重传不了,怎么办
既然不能重传,只能在发送前加冗余或在接收端猜:
| 手段 | 做法 | 代价 |
|---|---|---|
| FEC(前向纠错) | 每 n 个块额外发一个冗余块,丢任意一个都能恢复 | 带宽开销 1/n;且要多等一个块的时间 |
| 低码率冗余 | 第 i 个包里捎带第 i−1 个包的低质量版本 | 丢一个包时用低质量顶上,⭐ 只增加一个包的时延 |
| 交织(interleaving) | 把相邻采样打散到不同包里 | ⭐ 丢一个包变成多处小间隙而非一处大间隙,人耳更易接受;但增加时延,不适合交互式 |
| 接收端隐藏 | 用前一个块的波形重复或内插 | 零开销、零时延,但丢包多时明显失真 |
⭐ 注意交织和 FEC 都要「多等一点」——在交互式场景里,时延预算本身就是最稀缺的资源,所以实际的会议系统更倾向低码率冗余与接收端隐藏。
五、例题(Worked Example)
题目:一个 F = 15 Gbit 的文件要分发给 N 个用户。服务器上传速率 u_s = 30 Mbps,所有用户上传速率均为 u = 2 Mbps,下载速率均为 d = 10 Mbps。
分别求 N = 10、100、1000 时 C/S 与 P2P 的最小分发时间。
解答:
客户机–服务器:D_cs = max(N·F/u_s, F/d)
F/d = 15,000 Mbit / 10 Mbps = 1500 s(恒定)
N=10: N·F/u_s = 10×15000/30 = 5,000 s → max = 5,000 s
N=100: N·F/u_s = 100×15000/30 = 50,000 s → max = 50,000 s
N=1000: N·F/u_s = 1000×15000/30 = 500,000 s → max = 500,000 s
P2P:D_p2p = max(F/u_s, F/d, N·F/(u_s + N·u))
F/u_s = 15000/30 = 500 s
F/d = 1500 s
N=10: 10×15000/(30+20) = 150000/50 = 3,000 s → max = 3,000 s
N=100: 100×15000/(30+200) = 1500000/230 = 6,522 s → max = 6,522 s
N=1000: 1000×15000/(30+2000) = 15000000/2030 = 7,389 s → max = 7,389 s
对比表:
| N | C/S | P2P | 倍数 |
|---|---|---|---|
| 10 | 5,000 s | 3,000 s | 1.7× |
| 100 | 50,000 s | 6,522 s | 7.7× |
| 1000 | 500,000 s | 7,389 s | 68× |
📌 注意 P2P 的增长趋势:N 从 100 到 1000(10 倍),时间只从 6522 涨到 7389(1.13 倍),并且正在向渐近值 F/u = 15000/2 = 7500 s 收敛。而 C/S 是精确的 10 倍。
这张表就是 P2P 存在的全部理由。
六、随堂自测
- SMTP 是推还是拉?HTTP 呢?这个差异带来了什么后果?
- 什么是点填充(dot-stuffing)?链路层里对应的机制叫什么?
- Base64 为什么存在?它的代价是多少?
- SPF、DKIM、DMARC 三者的分工是什么?它们共同依赖什么基础设施?
- 写出 P2P 分发时间的三项约束,并说明第三项为什么导致「自扩展」。
- BitTorrent 里如果没有乐观疏通会发生什么?
- DASH 为什么把码率选择的智能放在客户端而不是服务器?(用一条课程原则回答)
- ⭐ 为什么交互式实时应用「来不及重传」?用时延预算解释,不要只答「因为用 UDP」。
- 播放时延
q取大和取小各有什么代价?为什么自适应调整要选在静默期做? - RTP 提供了哪两个关键字段?它保证服务质量吗?
七、本讲要点回顾
- 邮件三件套:用户代理、邮件服务器、SMTP。SMTP 是推协议,HTTP 是拉协议。
- SMTP 的 7 位 ASCII 遗产导致了 Base64 和 33% 的体积膨胀。
- IMAP 赢过 POP3,因为多设备时代状态必须在服务器上。
- SMTP 无认证 → SPF / DKIM / DMARC,全部建在 DNS 之上。
- C/S 分发时间随 N 线性增长;P2P 收敛到常数——这是自扩展的数学定义。
- BitTorrent 三机制:分块、最稀有优先、tit-for-tat(+ 乐观疏通防止新 peer 饿死)。
- DASH 用客户端自适应把不确定的带宽变成确定的播放,智能在端上,服务器只是普通 HTTP 服务器。
- ⭐ 点播能重传,交互式不能——一个 RTT 已超过该帧的播放期限,重传回来也没有播放时刻可用。
- 播放缓冲把可变的网络时延换成恒定的播放时延;
q大则丢包少但交互差,q小则反之。 - RTP = 序号(检丢包乱序)+ 时间戳(重建节奏),⭐ 它不提供任何 QoS 保证;RTCP 回报丢包与抖动,SIP 负责建立会话。
- 丢包对策:FEC、低码率冗余、交织、接收端隐藏——⭐ 前三者都要多等一点,时延预算是最稀缺的资源。
八、自测答案
1. SMTP 是推(发送方主动把邮件送到接收方服务器),HTTP 是拉(接收方主动请求)。后果之一:垃圾邮件成为可能——任何人都能主动往你的邮箱推东西,而 HTTP 世界里没有人能强迫你的浏览器去下载什么。这个方向性差异是邮件反垃圾问题如此顽固的结构性原因。
2. 点填充:SMTP 用单独一行的 . 表示报文结束,因此发送方把正文中行首的 . 替换为 ..,接收方再还原。链路层的对应机制叫字节填充 / 字符填充(byte stuffing)(第 23 讲),HDLC/PPP 用它来防止数据中出现帧定界符。同一个「边界符号 vs 数据透明」问题在不同层的相同解法。
3. 因为 SMTP 历史上只保证 7 位 ASCII 透明传输,二进制数据(图片、附件、非 ASCII 文本)必须先编码为可打印字符。Base64 用 4 个字符表示 3 个字节,体积膨胀 4/3 ≈ 33%(外加换行开销)。
4. SPF:声明哪些 IP 可以代表本域发信(路径认证)。DKIM:对邮件内容做数字签名(内容认证)。DMARC:声明校验失败时的处置策略并收集报告(策略与反馈)。三者都通过 DNS TXT 记录发布——共同依赖 DNS 这一基础设施。
5. 三项:① F/u_s(服务器至少发一次);② F/d_min(最慢的用户要下完);③ N·F/(u_s + Σu_i)(系统总上传能力)。第三项中,N 增大时分子增加 F,但分母也增加了新 peer 的上传能力 u_i,于是比值趋于 F/u 这个常数而不发散——新用户带来的负担被它自身带来的资源抵消,这就是自扩展。
6. 新加入的 peer 一个块都没有,因而无法向任何人上传,也就永远不会进入任何人的「上传最快的 4 个」名单,永远得不到数据,被永久饿死——系统将无法接纳新成员。乐观疏通用随机的少量资源打破这个死锁。
7. 因为端到端原则(第 4 讲):只有客户端才知道自己真实的可用带宽、缓冲区水位、屏幕尺寸和用户偏好,服务器无法完整、正确地代它做这个决定。把智能放在客户端还带来了副产品:服务器退化为普通 HTTP 服务器,因而可以直接复用整个 CDN 缓存体系。
8. 交互式对话的单向时延预算约为 150 ms(超过 400 ms 无法正常对话),而这个预算要装下采集、编码、网络传播、缓冲、解码、播放的全部开销。发现丢包本身就要等到后续分组到达(或超时),再请求重传、等待重传到达,⭐ 至少多花一个 RTT。等它回来时,这一帧对应的播放时刻早已过去——数据即使完全正确也无处可放。所以问题不是「UDP 不支持重传」,而是重传在时间上无意义;正因为如此才选 UDP,而不是反过来。
9. q 取大:更多分组能在播放时刻前到达,有效丢包率低、听感连续;⭐ 但端到端时延增大,对话开始出现「抢话」和尴尬停顿。q 取小:交互自然;⭐ 但更多分组来不及,被当作丢包丢弃,听感断续。
自适应调整必须选在静默期(talk spurt 之间),因为改变 q 意味着要拉长或压缩一段音频:在静音段上做,人耳完全察觉不到;⭐ 在说话中间做会直接改变音调和语速,比抖动本身更难听。
10. ⭐ 序号(检测丢包与乱序,因为 UDP 不保证顺序)和时间戳(标记采样时刻,用于重建原始节奏、计算抖动、做音视频同步)。
⭐ RTP 不提供任何服务质量保证——它不重传、不预留带宽、不保证时延上限。它是一个跑在 UDP 之上的应用层协议,作用只是把时序信息带上,让接收方有能力去重建时序。保证从来不是它给的。