MIME顶级类型中有一个相当重要的名为MultiPart的类型需要专门讨论。就像一个web页面中可以包含多个对象(例如文本、图像、JAVA小应用程序等)那样,一个电子邮件消息也可以包含多个对象。我们已经知道,Web是通过各自独立的HTTP响应消息传送每个对象的。因特网电子邮件则相反,它把同一个邮件消息的所有对象(即部分)封装在单个消息中。具体地说,当一个多媒体消息含有不止一个对象时(例如多个图像或ASCII文本与图像共存),其Content-Type:头部的值通常为multipart/mixed。这头部向接收用户代理指出本消息中含有多个对象。既然多个对象共处同一个邮件消息,接收用户代理就得有办法确定:(1)每个对象的起止位置,(2)每个非ASCII文本对象的传送编码方式,(3)每个对象的内容类型。这是通过在每个对象之间放置边界字符串,并在每个对象之前定义Content-Type:和Content-Transfer-Encoding:头部实现的。
为便于理解multipart/mixed,让我们看一个例子。假设Alice想给BOb发送一个邮件消息,其内容为一些ASCII文本,后跟一个JPEG图像,再跟一些ASCII文本。Alice使用自己的用户代理编辑文本并附上图像后,该用户代理生成一个大体如下的邮件消息;
From:alice@crepes.fr
To:bob@hamburger.edu
MIME-Version:1.0
Content-type:multipart/mixed;Boundary=StartOfNextPart
--StartOfNextPart
Dear bob,
Please look at the picture
--StartOfNextPart
Content-Transfer-Encoding:base64
Content-type:image/jpeg
(...base64编码的数据...
...base64编码的数据...)
--StartOfNextPart
there is some acsii letter here
从中我们可以看出,Content-type:头部的Boundary参数用于指定分隔各个部分的边界字符串。在邮件消息体中,该分隔字符串以两个短划线开头,以CRLF结尾。
Received:头部
我们已经知道一个电子邮件消息由多个部件构成。信体是邮件消息的核心,它是发送者发送给接收者的真正数据。对于多部分邮件消息来说,其信体本身由多个部分组成,而每个部分又有一个或多个说明其数据性质的头部。信体之前是一个空行和由多个邮件消息头部组成的信头。这些头部既包括在RFC 822中定义的头部,例如From:、To:和subject:也包括MIME头部,例如Content-type:和Content-Transfer-Encoding:。除此之外,我们还得提及由SMTP接收服务器插到每个邮件消息的项端的Received:头部,它给出了发出本消息的SMTP服务器的主机名(“from”)、收取本消息的SMTP服务器的主机名(“by”)以及接收服务器收取本消息的时间。因此,作为接收者的用户看到的邮件消息大体如下:
Received:from crepes.fr by hamburger.edu;18 Oct 2005 05:53:37 GMT
From:alice@crepes.fr
To:bob@hamburger.edu
MIME-Version:1.0
Content-type:multipart/mixed;Boundary=StartOfNextPart
--StartOfNextPart
Dear bob,
Please look at the picture
--StartOfNextPart
Content-Transfer-Encoding:base64
Content-type:image/jpeg
(...base64编码的数据...
...base64编码的数据...)
--StartOfNextPart
there is some acsii letter here
有时候,单个邮件消息会有多个Received:头部,有的还会有一个较复杂的Return-path:头部。这是因为邮件消息在从发送者的主机到接收者的主机的传送过程中,可能会被转发到不止一个SMTP服务器。例如,如果Bob指示他在主机hamburger.edu上的邮件服务器把他的所有邮件转发到主机sushi.jp,那么他通过其用户代理看到的邮件消息可能以大体如下的两行开头:
Received:from hamburger.edu by sushi.jp;18 Oct 2005 05:55:37 GMT
Received:from crepes.fr by hamburger.edu;18 Oct 2005 05:53:37 GMT
这些头部给接收用户代理提供了相应邮件消息访问过的SMTP服务器及访问时间的踪迹。SMTP规范所在的RPC 822详细定义丁Received:头部的语法。
邮件访问协议
一旦SMTP把Alice发给Bob的邮件消息从Alice的邮件服务器传送到Bob的邮件服务器,该邮件消息就存放在Bob的邮箱中。在此前的讨论中,我们已假设Bob通过直接登录到自己的邮件服务器主机启动用户代理来阅读该邮件消息。直到20世纪90年代早期,这仍然是标准的做法。然而,当今的用户一般使用在本地PC机(或Mac机)上执行的用户代理来阅读邮件,而不管是办公室PC机、家庭PC机还是便携机。用户在本地PC机上执行用户代理可享受诸多好处,包括方便查看多媒体邮件消息和附件。
邮件消息的接收者在本地PC机上执行用户代理时,很自然的一个想法是在本地PC机上也运行邮件服务器。然而这种方法存在一个问题。我们已经知道,邮件服务器是管理邮箱并运行SMlP的客户端和服务器端的,这意味着如果收信人把自己的邮件服务器驻留在本地PC上,那么他不得不始终开着这台PC机并连接在因特网上,以便接收可能在任意时刻到达的新邮件。对于大多数因特网用户来说,这显然是不现实或不经济的做法。相反,用户一般只在本地PC机上运行一个用户代理,由它远程访问存放在某台共享的邮件服务器主机上的邮箱,而该邮件服务器主机总是连接在因特网上并为多个用户所共享。该主机及其上的邮件服务器—般由该用户的ISP(例如大学或公司)维护。
既然用户代理运行在各个用户的本地PC机上,邮件服务器则运行在ISP或机构内部网络中的某台服务器主机上,用户代理和邮件服务器之间就得有一个彼此通信的协议。我们先查看一下出自从Alice的本地PC机的某个邮件消息如何设法到达Bob的SMTP邮件服务器。这个任务可简单地由A11ce的用户代理使用SMTP直接与Bob的邮件服务器进行通信来完成。具体地说,从Alice的用户代理发起建立一个到Bob的邮件服务器的TCP连接,并通过该连接发出SMTP握手命令,再用DATA命令上传邮件消息,最后关闭连接。这种方法尽管切实可行,却很少被采用,因为它没有给Alice的用户代理提供任何资源来应对目标邮件服务器临时崩溃的情况。相反,通常采用的方法是先由Alice的用户代理发起与自己的邮件服务器的一个SMTP会话,把邮件消息上传到该邮件服务器;再由Alice的邮件服务器发起与Bob的邮件服务器的一次SMTP会话,把邮件消息中转给Bob的邮件服务器。如果Bob的邮件服务器暂时不可用,Alice的邮件服务器就暂存该邮件消息,以后继续尝试。SMTP的RFC定义了可用于跨多个邮件服务器中转邮件消息的SMTP命令。
现在的问题是,像Bob这样在本地PC机上运行用户代理的收信人该如何获取已到达自己的邮件服务器的邮件消息(该邮件服务器运行在Bob的ISP中的某台主机上)。通过引入用于从自己的邮件服务器到本地PC机上的用户代理传送邮件消息的邮件访问协议,这个问题彻底得以解决。日前流行的邮件访问协议有两个:邮局协议版本3(Post office ProtocolVersion 3,简称POP3)和因特网邮件访问协议(Internet Mail Access Protocol,简称IMAP)。注意,用户代理不可能使用SMTP从邮件服务器获取邮件消息,因为邮件消息的获取是一个内拉操作,而SMTP是一个外推协议。图3汇总了因特网电子邮件系统个所用的协议:SMTP用于从发送者的邮件服务器到接收者的邮件服务器传送邮件消息,也用于从发送者的用户代理到发送者的邮件服务器传送邮件消息;POP3或IMAP用于从接收者的邮件服务器到接收者的用户代理传送邮件消息。
图3 电子邮件协议及它们的通信实体