电子邮件自 Internet 出现以来就已存在。 当 Internet 处于起步阶段 [Segaller 1998] 时,它是最受欢迎的应用程序,并且多年来变得更加复杂和强大。 它仍然是互联网最重要和最常用的应用程序之一。
与普通邮政邮件一样,电子邮件是一种异步通信媒介——人们可以在方便的时候发送和阅读消息,而无需协调其他人的日程安排。 与邮政邮件相比,电子邮件速度快、易于分发且价格低廉。 现代电子邮件具有许多强大的功能,包括带有附件的报文、超链接、HTML 格式的文本和嵌入的照片。
在本节中,我们将研究作为 Internet 电子邮件核心的应用层协议。 但在我们深入讨论这些协议之前,让我们从高层次上了解 Internet 邮件系统及其关键组件。
图 2.14 展示了 Internet 邮件系统的高级视图。 我们从这个图中看到它具有三个主要组件:用户代理(user agents)、邮件服务器(mail servers)和简单邮件传输协议 (Simple Mail Transfer Protocol,SMTP)。 我们现在在发件人 Alice 向收件人 Bob 发送电子邮件报文的内容中描述这些组件中的每一个。 用户代理允许用户阅读、回复、转发、保存和撰写消息。 电子邮件的用户代理示例包括 Microsoft Outlook、Apple Mail、基于 Web 的 Gmail、在智能手机中运行的 Gmail 应用程序等。 当 Alice 完成她的消息编写后,她的用户代理将报文发送到她的邮件服务器,在那里该报文被放置在邮件服务器的外发报文队列(outgoing message queue)中。 当 Bob 想要阅读消息时,他的用户代理会从他的邮件服务器中的邮箱中检索报文。
image.png
Figure 2.14 ♦ A high-level view of the Internet e-mail system
邮件服务器构成了电子邮件基础设施的核心。 每个收件人(例如 Bob)在其中一台邮件服务器中都有一个邮箱(mailbox)。 Bob 的邮箱管理和维护已发送给他的报文。 一条典型的报文在发件人的用户代理中开始它的旅程,然后传输到发件人的邮件服务器,然后传输到收件人的邮件服务器,并存放在收件人的邮箱中。 当 Bob 想要访问他邮箱中的报文时,包含他邮箱的邮件服务器会验证 Bob(使用他的用户名和密码)。 Alice 的邮件服务器还必须处理 Bob 的邮件服务器中的故障。 如果 Alice 的服务器无法将邮件发送到 Bob 的服务器,Alice 的服务器会将报文保存在报文队列(message queue)中,并尝试稍后传输报文。 重试通常每 30 分钟左右进行一次; 如果几天后没有成功,服务器将删除该邮件并通过电子邮件通知发件人 (Alice)。
SMTP 是 Internet 电子邮件的主要应用层协议 它使用 TCP 的可靠数据传输服务将邮件从发件人的邮件服务器传输到收件人的邮件服务器。 与大多数应用层协议一样,SMTP 有两个方面:客户端,在发件人的邮件服务器上执行,服务器端,在收件人的邮件服务器上执行。 SMTP 的客户端和服务器端都在每个邮件服务器上运行。 当邮件服务器向其他邮件服务器发送邮件时,它充当 SMTP 客户端。 当邮件服务器从其他邮件服务器接收邮件时,它充当 SMTP 服务器。

2.3.1 SMTP

RFC 5321 中定义的 SMTP 是 Internet 电子邮件的核心。如上所述,SMTP 将报文从发件人的邮件服务器传输到收件人的邮件服务器。 SMTP 比 HTTP 古老得多。 (最初的 SMTP RFC 可以追溯到 1982 年,而 SMTP 早在此之前就已经存在了。)尽管 SMTP 具有许多美妙的品质,正如其在 Internet 中的普遍性所证明的那样,但它仍然是一种具有某些过时特征的遗留技术。例如,它将所有邮件报文的正文(不仅仅是报头)限制为简单的 7 位 ASCII。这一限制在 1980 年代初期是有意义的,当时传输容量稀缺,没有人通过电子邮件发送大型附件或大型图像、音频或视频文件。但今天,在多媒体时代,7 位 ASCII 限制有点痛苦——它需要在通过 SMTP 发送之前将二进制多媒体数据编码为 ASCII;它需要在 SMTP 传输后将相应的 ASCII 报文解码回二进制。回想一下 2.2 节,HTTP 不要求多媒体数据在传输前进行 ASCII 编码。
为了说明 SMTP 的基本操作,我们先来看一个常见的场景。 假设 Alice 想给 Bob 发送一个简单的 ASCII 报文。

  1. Alice 为电子邮件调用她的用户代理,提供 Bob 的电子邮件地址(例如 bob@someschool.edu),撰写消息,并指示用户代理发送报文。
  2. Alice 的用户代理将报文发送到她的邮件服务器,在那里它被放置在一个报文队列中。
  3. 运行在 Alice 的邮件服务器上的 SMTP 客户端会看到报文队列中的报文。 它打开一个到 SMTP 服务器的 TCP 连接,在 Bob 的邮件服务器上运行。
  4. 在一些初始 SMTP 握手之后,SMTP 客户端将 Alice 的报文发送到 TCP 连接中。
  5. 在 Bob 的邮件服务器上,SMTP 的服务器端接收报文。 Bob 的邮件服务器然后将报文放入 Bob 的邮箱中。
  6. Bob 调用他的用户代理在他方便的时候阅读消息。

该场景在图 2.15 中进行了总结。
image.png
Figure 2.15 ♦ Alice sends a message to Bob
重要的是要注意 SMTP 通常不使用中间邮件服务器来发送邮件,即使两个邮件服务器位于世界的两端也是如此。 如果 Alice 的服务器在香港,Bob 的服务器在圣路易斯,则 TCP 连接是香港和圣路易斯服务器之间的直接连接。 特别是,如果 Bob 的邮件服务器宕机,报文会保留在 Alice 的邮件服务器中并等待新的尝试——报文不会被放置在某个中间邮件服务器中。
现在让我们仔细看看 SMTP 如何将报文从发送邮件服务器传输到接收邮件服务器。 我们将看到 SMTP 协议与用于面对面的人类交互的协议有许多相似之处。 首先,客户端 SMTP(在发送邮件服务器主机上运行)让 TCP 与服务器 SMTP(在接收邮件服务器主机上运行)的端口 25 建立连接。 如果服务器关闭,客户端稍后再试。 一旦建立了这种连接,服务器和客户端就会执行一些应用层握手——就像人们在将信息从一个传输到另一个之前经常介绍自己一样,SMTP 客户端和服务器在传输信息之前介绍自己。在此 SMTP 握手阶段,SMTP 客户端指示发件人(生成报文的人)的电子邮件地址和收件人的电子邮件地址。 一旦 SMTP 客户端和服务器相互介绍了自己,客户端就会发送报文。 SMTP 可以依靠 TCP 的可靠数据传输服务将报文准确无误地发送到服务器。 如果客户端有其他报文要发送到服务器,则客户端会通过相同的 TCP 连接重复此过程; 否则,它会指示 TCP 关闭连接。
接下来让我们看一下在 SMTP 客户端 (C) 和 SMTP 服务器 (S) 之间交换的报文的示例文本。 客户端的主机名为 crepes.fr,服务器的主机名为 hamburger.edu。 以 C: 开头的 ASCII 文本行正是客户端发送到其 TCP 套接字的行,而以 S: 开头的 ASCII 文本行正是服务器发送到其 TCP 套接字的行。 TCP 连接一建立就开始下面的记录。

  1. S: 220 hamburger.edu
  2. C: HELO crepes.fr
  3. S: 250 Hello crepes.fr, pleased to meet you
  4. C: MAIL FROM: <alice@crepes.fr>
  5. S: 250 alice@crepes.fr ... Sender ok
  6. C: RCPT TO: <bob@hamburger.edu>
  7. S: 250 bob@hamburger.edu ... Recipient ok
  8. C: DATA
  9. S: 354 Enter mail, end with ”.” on a line by itself
  10. C: Do you like ketchup?
  11. C: How about pickles?
  12. C: .
  13. S: 250 Message accepted for delivery
  14. C: QUIT
  15. S: 221 hamburger.edu closing connection
  1. 在上面的例子中,客户端从邮件服务器 crepes.fr 向邮件服务器 hamburger.edu 发送一条消息(“你喜欢番茄酱吗?泡菜怎么样?”)。作为对话的一部分,客户端发出五个命令:HELOHELLO 的缩写)、MAIL FROMRCPT TODATA QUIT。这些命令是不言自明的。**客户端还向服务器发送由单个句点组成的行,表示报文的结束。 **(在 ASCII 行话中,每条报文都以 CRLF.CRLF 结尾,其中 CR LF 分别代表回车和换行。)服务器对每个命令发出回复,每个回复都有一个回复码和一些(可选的)英文-语言(English-language)解释。我们在这里提到 SMTP 使用持久连接:如果发送邮件服务器有几条报文要发送到同一个接收邮件服务器,它可以通过同一个 TCP 连接发送所有报文。对于每条报文,客户端以一个新的 MAIL FROMcrepes.fr 开始该过程,以独立的句点指定报文的结尾,并且仅在所有报文都已发送后才发出 QUIT。<br />强烈建议您使用 Telnet SMTP 服务器进行直接对话。 为此,请发出<br />`telnet serverName 25`<br />其中 serverName 是本地邮件服务器的名称。 执行此操作时,您只是在本地主机和邮件服务器之间建立 TCP 连接。 输入此行后,您应该立即收到来自服务器的 220 回复。 然后在适当的时间发出 SMTP 命令 HELOMAIL FROMRCPT TODATACRLF.CRLF QUIT 还强烈建议您完成本章末尾的编程作业 3 在该作业中,您将构建一个实现 SMTP 客户端的简单用户代理。 它将允许您通过本地邮件服务器向任意收件人发送电子邮件。

2.3.2 邮件报文格式 Mail Message Formats

当 Alice 给 Bob 写一封普通的蜗牛邮件(snail-mail)时,她可能会在信的顶部包含各种外围报头信息(peripheral header information),例如 Bob 的地址、她自己的回信地址和日期。类似地,当一封电子邮件从一个人发送给另一个人时,包含外围信息的报头位于邮件本身的正文之前。此外围信息包含在一系列报头行中,这些报头行在 RFC 5322 中定义。报头行和报文正文由一个空行(即 CRLF)分隔。 RFC 5322 指定了邮件报头行的确切格式及其语义解释。与 HTTP 一样,每个报头行都包含可读文本,由关键字后跟冒号和值组成。一些关键字是必需的,而其他关键字是可选的。每个报头都必须有一个 From: 报头行和一个 To: 报头行;报头可以包括 Subject:报头行以及其他可选的报头行。需要注意的是,这些报头行与我们在第 2.3.1 节中研究的 SMTP 命令不同(即使它们包含一些常用词,例如“from”和“to”)。该部分中的命令是 SMTP 握手协议的一部分;本节中检查的报头行是邮件报文本身的一部分。
典型的报文报头如下所示:

  1. From: alice@crepes.fr
  2. To: bob@hamburger.edu
  3. Subject: Searching for the meaning of life.
  1. 在报文头之后,跟随一个空行; 然后是报文正文(ASCII)。 您应该使用 Telnet 向邮件服务器发送包含一些报头行(包括 Subject:报头行)的邮件。 为此,请发出 telnet serverName 25,如第 2.3.1 节所述。

2.3.3 邮件访问协议 Mail Access Protocols

一旦 SMTP 将报文从 Alice 的邮件服务器发送到 Bob 的邮件服务器,该报文就会放入 Bob 的邮箱中。 鉴于 Bob(收件人)在他的本地主机(例如,智能手机或 PC)上执行他的用户代理,很自然地考虑在他的本地主机上放置一个邮件服务器。 通过这种方法,Alice 的邮件服务器将直接与 Bob 的 PC 对话。 但是,这种方法存在问题。 回想一下,邮件服务器管理邮箱并运行 SMTP 的客户端和服务器端。 如果 Bob 的邮件服务器驻留在他的本地主机上,那么 Bob 的主机必须始终保持开启状态并连接到 Internet,以便接收随时可能到达的新邮件。 这对许多互联网用户来说是不切实际的。 相反,典型用户在本地主机上运行用户代理,但访问其存储在始终在线共享邮件服务器上的邮箱。 此邮件服务器与其他用户共享。
现在让我们考虑电子邮件从 Alice 发送到 Bob 时所采用的路径。我们刚刚了解到,在路径的某个时刻,电子邮件报文需要存放在 Bob 的邮件服务器中。这可以通过让 Alice 的用户代理直接将报文发送到 Bob 的邮件服务器来完成。但是,通常发件人的用户代理不会直接与收件人的邮件服务器对话。相反,如图 2.16 所示,Alice 的用户代理使用 SMTP 或 HTTP 将电子邮件报文传送到她的邮件服务器,然后 Alice 的邮件服务器使用 SMTP(作为 SMTP 客户端)将电子邮件报文传递(relay)到 Bob 的邮件服务器。为什么是两步程序?主要是因为如果没有通过 Alice 的邮件服务器进行传递,Alice 的用户代理无法求助于无法访问的目标邮件服务器。通过让 Alice 首先将电子邮件存放在她自己的邮件服务器中,Alice 的邮件服务器可以反复尝试将报文发送到 Bob 的邮件服务器,比如每 30 分钟一次,直到 Bob 的邮件服务器开始运行。 (如果 Alice 的邮件服务器出现故障,那么她可以向系统管理员投诉!)
image.png
Figure 2.16 ♦ E-mail protocols and their communicating entities
但是拼图仍然缺少一块! 像 Bob 这样的收件人如何在他的本地主机上运行用户代理,获取他位于邮件服务器中的报文? 请注意,Bob 的用户代理不能使用 SMTP 获取报文,因为获取报文是 pull 操作,而 SMTP 是 push 协议。
今天,Bob 从邮件服务器检索电子邮件的常用方法有两种。 如果 Bob 正在使用基于 Web 的电子邮件或智能手机应用程序(例如 Gmail),那么用户代理将使用 HTTP 来检索 Bob 的电子邮件。 这种情况需要 Bob 的邮件服务器具有 HTTP 接口和 SMTP 接口(以与 Alice 的邮件服务器通信)。 另一种方法通常与 Microsoft Outlook 等邮件客户端一起使用,是使用 RFC 3501 中定义的 Internet 邮件访问协议 (Internet Mail Access Protocol,IMAP)。 HTTP 和 IMAP 方法都允许 Bob 管理在 Bob 的邮件服务器中维护的文件夹。 Bob 可以将报文移动到他创建的文件夹中、删除报文、将报文标记为重要等。