📚 第八章 应用协议
📄 一句话概括

IMAP 的模型是「遥控」:信一直躺在服务器上,客户端只是租一个窗口在看。 它把「已读、标记、文件夹」都变成了服务器上的属性,所以多设备能同步。

📝 本节和前面几节的关系

这一节先算「只取头部」能省多少流量,再模拟两台设备先后读同一封信,看状态怎么同步。 最后说清它的代价:协议变复杂。

一、只取头部能省 95%

POP3 是全下载:500 封邮件、平均 4 KB,一次要 2 MB。 而且下次换台机器还要重下 2 MB。 IMAP 支持「只取头部」:发件人、主题、日期、大小这几行,每封约 200 字节。 500 封只要 100 KB,是 POP3 的二十分之一。 等用户真点开某一封,才去取它的正文。 这一层优化在「邮件多、网速慢」的场景里是决定性的: 手机上翻一遍收件箱,POP3 要下载几百兆,IMAP 只要几百 K。 所以「按需加载」这件事,在协议层做比在应用层做更彻底。

二、状态存在服务器上

IMAP 的核心不是「只取头部」,是「状态在服务器上」。 「已读」\Seen、「已回复」\Answered,「已标记」\Flagged、「已删除」\Deleted, 这四个标记都存在服务器上,所有客户端看到的是同一份。 所以手机会话和电脑会话看到的是同一封信的同一个状态, 不会出现「手机上读过了,电脑上还是未读」这种情况。 这一点看起来平淡,但它是「多设备」体验的基础。 文件夹在 IMAP 里就是「服务器上的目录」, 所以你在电脑上把信拖进「项目 A」,手机上就能看到它在「项目 A」。 POP3 里「文件夹」纯粹是客户端本地的概念,服务器根本不知道。

三、代价:协议变复杂

IMAP 的命令有几十条,而且每条前面要带一个标签。 标签的作用是「配对请求和响应」——因为 IMAP 允许「同时有多条命令在途」。 这是它和 POP3 最大的实现差异: POP3 是严格一问一答,一个 socket 读一行就够;IMAP 要维护一个「标签到回调」的表。 功能多了,出 bug 的地方也就多了。 所以现实里有一句经验:「邮件客户端最难写的部分不是界面,是 IMAP 的同步状态机」。 因为它要处理「服务器上被别人改了」「本地改了但没同步」「离线改了然后冲突」这三种情况。 这就是「把状态放到服务器上」的必然代价——你换来了同步,也换来了「分布式一致性」这个难题。

四、动手 Demo:IMAP 把「状态」搬回了服务器

先算「全下载」和「只取头部」的流量差,再用一个字典模拟服务器的标记表。 看两台设备先后读同一封信时,第二台看到什么。

total, avg = 500, 4 * 1024
base = total * avg
for name, nmsg, nbytes in (
    ('POP3 全下载', total, total * avg),
    ('IMAP 只取头部', total, total * 200),
    ('IMAP 读第一封', 1, avg),
):
    print('%-16s %4d 封 %10d 字节 %6.1f%%'
          % (name, nmsg, nbytes, 100.0 * nbytes / base))

# 服务器上的标记表
server = {i: set() for i in (1, 2, 3)}

def read(dev, no):
    before = set(server[no])
    server[no].add('\\Seen')
    return before, set(server[no])

for dev, no in (('手机', 1), ('电脑', 1), ('平板', 2)):
    before, after = read(dev, no)
    print(dev, no, sorted(before), '->', sorted(after))

实跑输出:

--------------------------------------------------------------------
IMAP 把「状态」搬回了服务器
--------------------------------------------------------------------
**POP3 的模型是「搬走」,IMAP 的模型是「遥控」。**
**信一直躺在服务器上,客户端只是租一个窗口在看。**
**这个差别带来的第一个后果是「流量」。**

  做法              取几封     流量               相对 POP3
  ----------------------------------------------------------
  POP3 全下载       500 封     2,048,000 字节     100.0%
  IMAP 只取头部     500 封     100,000 字节        4.9%
  IMAP 读第一封     1 封       4,096 字节          0.2%

**第二行是关键**:**IMAP 只下「发件人、主题、日期、大小」这几行,正文要用的时候才取。**
**500 封邮件,POP3 要 2 MB,IMAP 只取头部,只要 100 KB**——**少掉 95%。**
**这还没算「已读的不用再下」这一层。**

**第二个后果更重要:状态藏在服务器上。**
**下面用一个字典模拟服务器的标记表,看两台设备先后读同一封信会怎样。**

  哪台设备     读哪封      读之前服务器上的标记     读之后
  ---------------------------------------------------------
  手机         第 1 封     (无)                   \Seen
  电脑         第 1 封     \Seen                    \Seen
  平板         第 2 封     (无)                   \Seen

**第二行是重点**:**电脑打开第 1 封,服务器上已经有 `\Seen` 了**——**因为手机刚读过。**
**所以它会直接把这一封显示成「已读」,不会亮红点。**

**这就是 IMAP 的全部价值**:
**它把「已读、已回复、在哪个文件夹、打了什么标签」都变成了服务器上的属性。**
**POP3 里这些东西只存在于本地的客户端数据库里,换一台机器就全没了。**

**代价是「协议变复杂」**:
**IMAP 的命令有几十条,每条前面还要带一个标签(`a1`、`a2`)用来配对响应。**
**这个「标签」设计是为了让多条命令能同时在途**——**POP3 是严格一问一答,IMAP 允许并发。**
**功能多了,出 bug 的地方也就多了。**

第一段:POP3 是 100%,只取头部是 4.9%,单封是 0.2%。 少掉 95%。 这还没算「已读的不用再下」这一层。 第二段最关键的是第二行: 电脑打开第 1 封之前,服务器上已经有 \Seen 了——因为手机刚读过。 所以电脑会直接把这一封显示成「已读」,不会亮红点。 注意第三行平板读的是第 2 封,它的标记是空的,所以平板会看到「未读」。 这正说明「状态是按条目记的」,不是「按账号记一个全局已读数」。

30 秒实验

  1. 算一下:500 封、每封 4 KB,全下载是多少 MB?
  2. 想一下:IMAP 的标签(a1、a2)是干什么用的?
  3. 想一下:为什么说「邮件客户端最难写的是同步状态机」?

常见误区

  1. 以为「IMAP 只是 POP3 的升级版」。 两者模型不同,一个搬走、一个遥控。
  2. 以为「IMAP 会把邮件留在服务器上所以更慢」。 它只取头部,通常比 POP3 快得多。
  3. 以为「已读状态是客户端记的」。 IMAP 里它在服务器上,所以多设备才能同步。
自测三问(点开对答案) 1. IMAP 和 POP3 的模型差别是什么? —— **IMAP 遥控(信留在服务器),POP3 搬走(收完就删)。** 2. IMAP 的四个标准标记是什么? —— **`\Seen`、`\Answered`、`\Flagged`、`\Deleted`。** 3. IMAP 的标签是干什么的? —— **配对请求和响应**,因为允许同时有多条命令在途。

邮件讲完了,下一节回到 WWW。

相关笔记