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 電子郵件協議及它們的通信實體