RFC 3030 – SMTP 二进制MIME和分块
ELI5: 标准 SMTP 像大声朗读一本书一样逐页发送消息数据,用来表示"我完成了"的特殊词是单独一行的句号。这意味着消息中每个行首的句号都需要特殊处理(点填充)。CHUNKING 用"接下来是 5,000 字节"替代了这种方式——服务器确切地知道要接收多少数据,所以不需要转义特殊字符。BINARYMIME 更进一步,允许原始二进制数据而无需任何编码开销。
此RFC存在的原因
传统SMTP使用DATA命令传输消息内容。消息正文作为文本流发送,以仅包含句点的行(.\r\n)终止。此设计有两个问题:
- 点转义开销:消息中以句点开头的任何行都必须通过添加额外的句点进行转义。服务器必须扫描每一行以删除转义符。这增加了处理开销,并可能引入错误。
-
不支持二进制:
DATA命令假设使用7位ASCII文本和CRLF行尾。二进制内容(如附件)必须进行Base64编码,这会使数据大小增加约33%。
RFC 3030定义了两个相关扩展来解决这些问题。CHUNKING引入BDAT命令,以显式大小的块传输数据——无需点转义。BINARYMIME建立在CHUNKING之上,允许原始二进制内容而无需任何编码。
工作原理
CHUNKING(BDAT命令)
- 服务器在其EHLO响应中公布
CHUNKING。 - 客户端使用
BDAT <size>代替DATA来发送恰好<size>个八位字节的块。 - 服务器读取恰好该数量的字节,然后发送响应。
- 客户端可以发送多个
BDAT块。最后一个块包括LAST关键字:BDAT <size> LAST。 - 因为大小是显式的,所以无需点转义。
BINARYMIME
- 服务器在其EHLO响应中同时公布
CHUNKING和BINARYMIME。 - 客户端在
MAIL FROM命令上声明BODY=BINARYMIME。 - 消息数据通过
BDAT发送,可能包含原始二进制内容——无需Base64编码,无需CRLF行尾要求。
SMTP示例
使用BDAT而不是DATA发送消息:
S: 220 mx.example.com ESMTP
C: EHLO sender.example.net
S: 250-mx.example.com
S: 250-CHUNKING
S: 250-BINARYMIME
S: 250-SIZE 52428800
S: 250 PIPELINING
C: MAIL FROM:<alice@example.net>
S: 250 2.1.0 OK
C: RCPT TO:<bob@example.com>
S: 250 2.1.5 OK
# 发送消息头和正文的第一部分(2048字节)
C: BDAT 2048
C: [2048字节的消息数据]
S: 250 2.0.0 2048 bytes received
# 发送剩余正文(4096字节,最后一个块)
C: BDAT 4096 LAST
C: [4096字节的消息数据]
S: 250 2.0.0 OK, 6144 bytes total, queued
使用BINARYMIME处理原始二进制内容:
C: MAIL FROM:<alice@example.net> BODY=BINARYMIME
S: 250 2.1.0 OK
C: RCPT TO:<bob@example.com>
S: 250 2.1.5 OK
# 消息包含二进制MIME部分,无需Base64编码
C: BDAT 1048576 LAST
C: [1,048,576字节的原始消息,包含二进制附件]
S: 250 2.0.0 OK, queued
关键技术细节
BDAT与DATA的对比
| 方面 | DATA | BDAT(CHUNKING) |
|---|---|---|
| 终止 |
.\r\n(单独一行的句点) |
显式字节数+LAST关键字 |
| 点转义 | 必需 | 不需要 |
| 二进制内容 | 必须经过Base64编码 | 使用BINARYMIME的原始二进制 |
| 流处理 | 服务器扫描以寻找终止符 | 服务器读取精确的字节数 |
| 多个块 | 不适用 | 是,每个块后有中间状态 |
块大小
没有强制的块大小。客户端通常根据可用内存和网络条件选择块大小。常见策略包括将整个消息作为单个BDAT ... LAST发送,或分割成几百KB的块。较小的块允许中间错误检测;较大的块减少协议开销。
错误恢复
如果服务器拒绝BDAT块(例如消息过大),客户端可以发出BDAT 0 LAST以干净地中止事务。这比DATA模型更简洁,其中即使客户端知道服务器会拒绝它,也必须发送整个消息和点终止符。
BINARYMIME转发
接收BINARYMIME消息的服务器不得将其转发到不支持BINARYMIME的服务器。如果下一跳未公布BINARYMIME,转发服务器必须在中继前将二进制内容部分转换为Base64编码。这是一项关键的互操作性要求。
常见错误
-
未检查CHUNKING就使用BDAT。并非所有服务器都支持CHUNKING。在使用
BDAT前始终验证EHLO响应。如果未公布CHUNKING,回退到DATA。 - 下一跳不支持时发送BODY=BINARYMIME。如果您是中继服务器,且下游MTA未公布BINARYMIME,您必须在转发前将二进制部分重新编码为Base64。将原始二进制发送给不理解它的服务器会破坏消息。
-
字节数计算错误。
BDAT中的大小必须与后面八位字节的数量完全匹配。发送太少或太多字节会导致协议不同步并破坏会话。 -
忘记LAST关键字。最后的
BDAT块必须包括LAST。没有它,服务器会等待更多块,会话会挂起。 -
混合BDAT和DATA。客户端必须为给定消息使用
DATA或BDAT之一,绝不能同时使用两者。在事务中间切换是协议错误。 -
不处理中间块错误。如果非最终
BDAT块返回错误,客户端应该发送BDAT 0 LAST以重置事务,而不是继续发送数据。
可交付性影响
- 减少二进制附件的带宽。BINARYMIME消除了Base64编码33%的开销。对于10MB附件,这节省了大约3.3MB的传输数据。这在规模上很重要。
- 更快处理大消息。具有显式大小的BDAT让服务器提前分配内存并跳过逐行扫描。这减少了双方的CPU开销。
- 更干净的错误处理。中间块响应让客户端在不传输整个消息的情况下尽早检测到故障。当消息将被拒绝时,这节省带宽和连接时间。
- 主要提供商广泛支持。Google、Microsoft和大多数大型邮件平台支持CHUNKING。在可用时使用它是高效交付的最佳做法。
- 消除点转义错误。BDAT消除了与点转义和行尾处理相关的整个潜在错误类别。消息完全如发送那样到达,字节对字节。