17.4 电子邮件

电子邮件既古老又现代。对于我们这些从很早就开始用因特网的人来说,电子邮件看上去是如此的“古老”,尤其是相对于基于网页的在线聊天,即时通讯(IM)和数字电话即VOIP (Voice Over Internet Protocol)等更新更快的通讯方式来说更是如此。下一节中,我们将从宏观上介绍一下电子邮件是如何工作的。如果你已经对此相当了解,只想看如何用Python做电子邮件相关的开发,你可以跳到后续章节。

在看电子邮件的底层的结构之前,你有没有问过自己,电子邮件的确切定义到底是什么?根据RFC2822,“消息由头域(合起来叫消息头)以及后面可选的消息体组成”。对于一般用户来说,一说起电子邮件就会让我们想到它的内容,不管它是一封真的邮件还是一封不请自来的商业广告(即spam,垃圾邮件),都应该有内容。不过,RFC规定,邮件体是可选的,只有邮件头是必要的。这一点要特别注意。

17.4.1 电子邮件系统组件和协议

不管你是怎么样想的,电子邮件实际上在现代的因特网出现之前就已经出现了。它一开始用于大型机的用户之间简单的交换信息。注意,由于他们都在使用同一台电脑,所以,这里甚至都没有涉及到网络。后来,当网络成为现实的时候,用户就可以在不同的主机之间交换信息。当然,由于用户使用着不同的电脑,电脑之间使用着不同的协议,信息交换成了一个很复杂的概念。直到20世纪80年代,因特网上用电子邮件进行信息交换才有了一个事实上的统一的标准。

在深入细节之前,我们先问问自己,电子邮件是怎么工作的?一条消息是如何从发件人那通过浩瀚的因特网,到达收件人的?简单点来说,有一台发送电脑(发件人的消息从这里发送出去),和一台目的电脑(收件人的信件服务器)。最好的解决方案是发送电脑知道如何连接到接收电脑,这样一来,它就可以直接把消息发送过去。不过,实际上一般并不这么顺利。

发送电脑要查询到某一台中间主机,这台中间主机能到达最后的收件主机。然后这台中间主机要找一台离目的主机更近一些的主机。所以,在发送主机和目的主机之间,可能会有多台叫做“跳板”的主机。如果你仔细看看你收到的电子邮件的邮件头,你会看到一个“passport”标记,其中记录了邮件寄给你这一路上都到过了哪些地方。

为了让描述清楚一些,让我们先看看电子邮件系统的各个组件。最主要的组件是消息传输代理(messagetransport agent, MTA)。这是一个在邮件交换主机上运行的一个服务器程序,它负责邮件的路由、队列和发送工作。它们就是邮件从源主机到目的主机所要经过的跳板。所以也被称为是“信息传输”的“代理”。

要让所有这些工作起来,MTA要知道两件事情:1)如何找到消息应该去的下一台MTA;2)如何与另一台MTA通讯。第一件事由域名服务(domain name service,DNS)来查找目的域名的MX(邮件交换,Mail eXchange)来完成。这对于最后的收件人是不必要的,但对其他的跳板来说,则是必要的。对于第二件事,MTA怎么把消息转给其他的MTA呢?

17.4.2 发送电子邮件

要能发送电子邮件,你的邮件客户端一定要连接到一个MTA,它们靠某种协议进行通讯。MTA之间通讯所使用的协议叫消息传输系统(MTS)。只有两个MTA都使用这个协议时才能进行通讯。在本节开始时就说过,由于以前存在很多不同的计算机系统,每个系统都使用不同的网络软件,这种通讯很危险,具有不可预知性。更复杂的是,有的电脑使用互连的网络,而有的电脑使用调制解调器拨号,消息的发送时间也是不可预知的。事实上,笔者曾经有一封邮件在发送9个月后才收到!互连网的速度怎么会这么慢?出于对这些复杂度的考虑,现代电子邮件的基础之一,简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)于1982年出现了。

SMTP

SMTP由已故的乔纳森•波斯特(Jonathan Postel,加利福尼亚大学信息学院)创建,记录在RFC 821中,于1982年8月公布。其后的修改记录在RFC 2821中,于2001年4月公布。一些已经实现了SMTP的著名MTA包括:

开源MTA

  • Sendmail

  • Postfix

  • Exim

  • qmail(免费发布,但不开源)

商业MTA

  • Microsoft Exchange

  • Lotus Notes Domino Mail Server

注意,虽然它们都实现了RFC 2821中定义的最小化SMTP协议,它们中的大多数,尤其是一些商业MTA,都在服务器中加入了协议定义之外的特有的功能。

SMTP是在因特网上MTA之间用于消息交换的最常用的MTS。它被MTA用来把电子邮件从一台主机传送到另一台主机。在你发电子邮件的时候,你必须要连接到一个外部的SMTP服务器,这时,你的邮件程序是一个SMTP客户端。你的SMTP服务器也因此成为了你的消息的第一个跳板。

17.4.3 Python和SMTP

是的,也存在一个smtplib模块和一个smtplib.SMTP类要实例化。再来看看这个已经熟悉的过程吧:

1.连接到服务器;

2.登录(如果需要的话);

3.发出服务请求;

4.退出。

像NNTP一样,登录是可选的,只有在服务器打开了SMTP认证(SMTP-AUTH)时才要登录。SMTP-AUTH在RFC 2554中定义。还是跟NNTP一样,SMTP通讯时,只要一个端口25。

下面是一些Python的伪代码:

17.4 电子邮件 - 图1

在看真实的例子之前,我们要先介绍一下smtplib.SMTP类的一些常用的方法。

17.4.4 smtplib.SMTP类方法

跟之前一样,我们会列出smtplib.SMTP类的方法,但不会列出所有的方法,只列出你创建SMTP客户端程序所需要的方法。对大多数电子邮件发送程序来说,只有两个方法是必须的,即sendmail()和quit()。

sendmail()的所有参数都要遵循RFC 2822,即电子邮件地址必须要有正确的格式,消息体要有正确的前导头,前导头后面是两个回车和换行(\r\n)对。

注意,实际的消息体不是必要的。根据RFC 2822, “唯一要求的头信息只有发送日期和发送地址”,即“Date:”和“From:”:(MAIL FROM, RCPT TO,DATA)。

还有一些方法没有被提到,不过,一般来说,它们不是发送电子邮件所必须的。请参考Python文档以获取SMTP对象的所有方法的信息。

17.4 电子邮件 - 图2

17.4.5 交互式SMTP示例

同样地,我们先给一个交互式的例子:

17.4 电子邮件 - 图3

17.4 电子邮件 - 图4

17.4.6 SMTP的其他方面

从SMTP协议定义/规范(RFC 2821)中,你可以得到更多关于SMTP的信息:ftp://ftp.isi.edu/in-notes/rfc2821.txt以及网页http://www.networksorcery.com/enp/protocol/smtp.htm。想了解更多Python对SMTP的支持,可以从这里开始:http://python.org/docs/current/lib/module-smtplib.html。

我们还没有讨论的电子邮件的一个很重要的方面是如何正确地设定因特网地址的格式和电子邮件消息。这些信息详细记录在因特网信息格式RFC 2822中,可以在ftp://ftp.isi.edu/in-notes/rfc2822. txt下载。

17.4.7 接收电子邮件

在以前,在因特网上用电子邮件通讯的只有大学学生、研究人员和工商企业的雇员。桌面电脑还都是类Unix操作系统。家庭用户只是拨号到PC上,并不真的使用电子邮件。在20世纪90年代中期因特网大爆炸的时候,电子邮件进入了千家万户。

对于家族用户来说,在家里放一个工作站来运行SMTP是不现实的。必须要设计一种新的系统,能够周期性地把信件下载到本地计算机,以供离线时使用。这样的系统就要有一套新的协议和新的应用程序来与邮件服务器通讯。

在家用电脑中运行的应用程序叫邮件用户代理(mail user agent, MUA)。MUA从服务器上下载邮件,在这个过程中可能会自动删除它们(也可能不删除,留在服务器上,让用户手动删除)。不过,MUA也必须要能发送邮件。也就是说,在发送邮件的时候,它要能直接与MTA用SMTP进行通讯。在前面讲SMTP的章节中,我们已经看过这种客户端了。那下载邮件的呢?

17.4.8 POP和IMAP

用于下载邮件的第一个协议叫邮局协议,记录在RFC 918中,于1984年10月公布。“邮局协议(POP)的目的是让用户的工作站可以访问邮箱服务器里的邮件。邮件要能从工作站通过简单邮件传输协议(SMTP)发送到邮件服务器”。POP协议的最新版本是第3版,也叫POP3。POP3在RFC 1939中定义,至今为止仍在被广泛地使用,也是我们下面的客户端例子的主要内容。

在POP之后几年,出现了另一个协议,叫交互式邮件访问协议(Interactive Mail Access Protocol, IMAP)。第一个版本是实验性的,直到第2版时,其RFC 1064才在1988年被公布。现在被使用的IMAP版本是IMAP4revl,它也被广泛地使用。事实上,当今世界上占有邮件服务器大多数市场的Microsoft Exchange就使用IMAP作为其下载机制。IMAP4revl协议定义在RFC 3501,于2003年3月公布。IMAP的目的是要提供一个更全面的解决方案。不过,它比POP更为复杂。对IMAP的进一步讨论超出了本章剩余部分的范围。我们建议感兴趣的用户参考上述RFC文档。图17-3展示的复杂系统就是我们所认为的简单的电子邮件。

17.4 电子邮件 - 图5

图 17-3 因特网上的电子邮件发件人和收件人。客户端通过他们的MUA和相应的MTA进行通讯,来下载和发送邮件。电子邮件从一个MTA “跳”到另一个MTA,直到到达目的地为止

17.4.9 Python和POP3

毫不奇怪,我们要做的是导入poplib,实例化poplib.POP3类。标准的做法如下:

1.连接到服务器;

2.登录;

3.发出服务请求;

4.退出。

Python的伪代码如下:

17.4 电子邮件 - 图6

在看真实的例子之前,我们要先看一个交互式的例子以及介绍一下poplib.POP3类的一些基本的方法。

17.4.10 交互式POP3举例

下面是使用Python poplib模块的交互式的例子:

17.4 电子邮件 - 图7

17.4 电子邮件 - 图8

17.4.11 poplib.POP3类方法

POP3类有无数的方法来帮助你下载和离线管理你的邮箱。最常用的列在表17.4中。

17.4 电子邮件 - 图9

在登录时,user()方法不仅向服务器发送了用户名,也要等待服务器正在等待用户密码的返回信息。如果pass_()方法认证失败,会引发一个poplib. error_proto的异常。如果成功,会得到一个以“+”号开头的返回信息,如“+OK ready”,然后服务器上的该邮箱就被锁定了,直到调用了quit()方法为止。

调用list()方法时,msg_list的格式为[‘msgnum msgsiz’,…],其中msgnum和msgsiz分别是每个消息的编号和消息的大小。

还有一些方法未被列出,想要了解更多信息,请参考Python手册里poplib的文档。

17.4.12 客户端程序SMTP和POP3举例

下面的例子演示了如何使用SMTP和POP3来创建一个既能接收和下载电子邮件也能上传和发送电子邮件的客户端。我们将要先用SMTP发一封电子邮件给自己(或其他测试账户),等待一段时间——我们随便选了一个时间,10秒钟——然后使用POP3下载这封电子邮件,下载下来的内容跟发送的内容应该是完全一样的。如果程序悄无声息地结束,没有输出也没有异常,那就说明我们的操作都成功了。

这个脚本(通过SMTP邮件服务器)发送一封测试电子邮件到目的地址,并马上(通过POP)把电子邮件从服务器上收回来。要让程序能正常工作,你需要修改服务器的名字和电子邮件的地址。

17.4 电子邮件 - 图10

17.4 电子邮件 - 图11

逐行解释

1 ~ 8行

跟本章前面的例子一样,程序一开始是一些导入语句和常量的定义。常量分别是发送邮件和接收邮件的服务器。

10 ~ 14行

这几行是消息内容的准备工作。这里,我们放了三行消息头然后是消息体。From和To两个头分别表示消息的发件人和收件人。14行把消息头和消息体放在一起组成一个可以发送的消息,按RFC 2822的要求,这两部分用空行隔开。

16 ~ 21行

我们连接到发送(SMTP)服务器来发送我们的消息。这里还有一对From和To的地址,这些地址是“真实”的电子邮件地址,或者说是信封格式(envlelope)的地址。收件人参数应该是一个可迭代的对象,如果传的是一个字符串,就会被转成一个只有一个元素的列表。不请自来的垃圾邮件中,消息头和信封头总是不一致的。

sendmail()的第三个参数是电子邮件信息本身。这个函数返回之后,我们就登出SMTP服务器,并判断是否有错误发生过。我们要等待一段时间,等待服务器完成消息的发送与接收。

23 ~ 30行

程序的最后一部分是下载刚刚发送的消息,并断言发送的和接收的消息是完全一样的。先给出用户名和密码,连接到POP3服务器,在登录成功后,调用stat()方法得到有效的消息的列表。我们先选第一条消息([0]),然后调用retr()下载这个消息。

我们用空行来分隔头和信息,去掉头部分,比较原始信息体和收到的信息体。如果它们相同,什么都不显示,程序正常退出;否则,会出现一个断言失败的错误。

由于错误的类型太多,我们在这个脚本里不做错误检查,这样的好处是你可以直接看到出现了什么错误。在本章末尾有一个习题就是做错误检查的。

现在,你对如何发送和接收电子邮件有了一个很全面的了解。如果你想深入了解这一方面的编程,请参阅下一章里介绍的电子邮件相关的模块,它们在程序开发方面有相当大的帮助。