电子邮件系统的设计与开发
山东农业大学
毕业论文
题目:
二○○八年六月
中英文摘要
第一章 电子邮件的发展背景和意义
电子邮件翻译自英文的E-mail ,它表示通过电子通讯系统进行信件的书写、发送和接收。
30多年前,人们发明了电子邮件这种便捷的信息传递方式,这是人类通信历史上的一次革命。
电子邮件的兴起是在20世纪80年代,70年代的沉寂主要是因为当时使用Arpanet 网络的人太
少,网络的速度也仅为目前56Kbps 标准速度的二十分之一。受网络速度的限制,那时的用户
只能发送些简单的信息,根本无法象现在这样发送大量照片;到80年代中期,个人电脑兴起,
电子邮件开始在电脑迷以及大学生中广泛传播开来;到90年代中期,随着计算机技术的发展,
出现了功能强大的Internet 。在Internet 众多的服务功能中,电子邮件E-mail 就是其强大功
能中的一个。自此,电子邮件被广为使用。
今天使用的最多的通讯系统是互联网,同时电子邮件也是互联网上最受欢迎的功能之
一。通过电子邮件系统,您可以用非常低廉的价格,以非常快速的方式, 与世界上任何一个
角落的网络用户联络系,这些电子邮件可以是文字、图象、声音等各种方式。同时,您可以
得到大量免费的新闻、专题邮件,并实现轻松的信息搜索。这是任何传统的方式也无法相比
的。正是由于电子邮件的使用简易、投递迅速、收费低廉,易于保存、全球畅通无阻,使得
电子邮件被广泛地应用,它使人们的交流方式得到了极大的改变。
随着网络越来越普及,现在已经是人人一个电子邮箱甚至有多个电子邮箱。在Internet
上使用最多的服务当属电子邮件服务了。可见,电子邮件与人们的工作、学习和生活息息相
关,它已经深深的融入到每个人的生活中,生活在这个网络时代的人是不可能没有电子邮箱
的。电子邮箱已经由以前科技工作者用于科技信息交流转变到现在大多数人用来进行日常生
活工作学习的交流工具。同时,电子邮箱的功能也发生了翻天覆地的变化。由原来只能收发
文本到现在的收发多媒体文件。简化邮件的编码,减少编码造成的数据冗余。这些都得宜于
与电子邮箱有关的网络协议的发展和有关硬件的改进。
现在我们每天都习惯于通过E-mail 进行交流,各大网站也几乎都推出了自己的基于WEB
的免费邮件系统。电子邮件已无可争议的地成为互联网上最受用户欢迎的服务。邮件的安全
也逐渐受到人们的重视。至今已出现了各种安全保障措施来加强E-mail 的安全性。随着电子
邮件服务的完善和安全性的不断提高。电子邮件毫无疑问将在人类的通信史上占有一席之
地。如同其他的网络服务,电子邮件系统也有其使用的传输协议,包括SMTP (Simple Mail
Transfer Protocol ,简单邮件传输协议)、POP (Post Office Protocol ,邮局协议)和IMAP
(Internet Message Access Protocal )消息访问协议)等,这些协议应用于电子邮件的发
送和接收。一些邮件处理软件如OutLook Express和FoxMail 等就是按照SMTP 和POP3 协议结
合Windows Sockets套接字进行设计来收发邮件的。
第二章 电子邮件收发原理及相关技术
本章简单介绍了SMTP 协议,POP3协议的工作原理。只有较深入的掌握了这两个协议的主
要内容, 才能理解基于SMTP,POP3的邮件收发系统是怎样工作。
2.1 SMTP协议简介
2.1.1 SMTP协议原始命令码和工作原理
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到
目的地址传送邮件的规则,或者说是由它来控制信件传输的一种中转方式。SMTP 协议属于
TCP/IP协议族,它帮助每台计算机在发送或中转信件时找到下一个目的地。通过SMTP 协议
所指定的服务器,我们就可以把Email 寄到收信人的服务器上了,整个过程只要几分钟。
SMTP 服务器则是遵循SMTP 协议的发送邮件服务器,用来发送或中转电子邮件。
(1)SMTP协议的通信模型
首先针对用户发出的邮件请求,由发送SMTP 建立一条连接到接收SMTP 的双工通讯链路,
这里的接收SMTP 是相对于发送SMTP 而言的,实际上它既可以是最终的接收者也可以是中间传
送者。发送SMTP 负责向接收SMTP 发送SMTP 命令,而接收SMTP 则负责接收并反馈应答。
图2-1 SMTP协议通信模型示意图
(2)SMTP协议的命令和应答
从前面的通讯模型可以看出SMTP 协议在发送SMTP 和接收SMTP 之间的会话是靠发送SMTP
的SMTP 命令和接收SMTP 反馈的应答来完成的。在通讯链路建立后,发送SMTP 发送MAIL FROM:
命令指令邮件发送者,若接收SMTP 此时可以接收邮件则作出OK 的应答,然后发送SMTP 继续发
出RCPT 命令以确认邮件是否收到,如果接收到就作出OK 的应答,否则就发出拒绝接收应答,
但这并不会对整个邮件操作造成影响。双方如此反复多次,直至邮件处理完毕。SMTP 协议共
包含10个SMTP 命令,列表如下:
-------------------------------------------------------------------------------
表2-1 SMTP命令 命令说明
-------------------------------------------------------------------------------
HELLO <domain ><CRLF > 识别发送方到接收SMTP 的一个HELLO 命令
MAIL FROM:<reverse-path ><CRLF > <reverse-path > 为发送者地址。此命令告诉接
收方一个新邮件发送的开始并对所有的状态和缓冲区进行初始化。此命令开始一个邮件传 输处理最终完成将邮件数据传送到一个或多个邮箱中。
RCPT TO:<forward-path ><CRLF > 标识各个邮件接收者的地址
DATA <CRLF > 接收SMTP 将把其后的行为看作邮件数据去处理,以<CRLF >. <CRLF >标识
数据的结尾。
REST <CRLF > 退出/复位当前的邮件传输
NOOP <CRLF > 要求接收SMTP 仅做OK 应答。(用于测试)
QUIT <CRLF > 要求接收SMTP 返回一个OK 应答并关闭传输。
VRFY <string > <CRLF > 验证指定的邮箱是否存在,由于安全因素,服务器多禁止此命
令。
EXPN <string > <CRLF > 验证给定的邮箱列表是否存在,扩充邮箱列表,也常禁止使用。
HELP <CRLF > 查询服务器支持什么命令
VRFY <CRLF > 用于验证给定的用户邮箱是否存在,以及接收关于该用户的详细信息。
EXPN <CRLF > 用于扩充邮件列表。
-------------------------------------------------------------------------------
注:<CRLF >为回车、换行,ASCII 码分别为13、10(十进制)。
-------------------------------------------------------------------------------
MAIL FROM 命令中指定的地址是称作 envelope from 地址,不需要和发送者自己的地
址是一致的。RCPT TO 与之等同,指明的接收者地址称为envelope to 地址,而与实际的
to :行是什么无关。邮件被分为信封部分,信头部分和信体部分envelope from, envelope
to 与message from:, message to:完全不相干。evnelope 是由服务器主机间SMTP 后台
提供的,而message from/to是由用户提供的。有无冒号也是区别。
SMTP 协议的每一个命令都会返回一个应答码,应答码的每一个数字都是有特定含义的,
如第一位数字为2时表示命令成功;为5表失败;3表没有完成。一些较复杂的邮件程序利用
该特点,首先检查应答码的首数字,并根据其值来决定下一步的动作。下面将SMTP 的应答码
列表如下:
----------------------------------------------------------------
表2-2 应答码说明
----------------------------------------------------------------
501 参数格式错误
502 命令不可实现
503 错误的命令序列
504 命令参数不可实现
211 系统状态或系统帮助响应
214 帮助信息
220 <domain >服务就绪
221 <domain >服务关闭
421 <domain >服务未就绪,关闭传输信道
250 要求的邮件操作完成
251 用户非本地,将转发向<forward-path >
450 要求的邮件操作未完成,邮箱不可用 (例如,邮箱忙)
550 要求的邮件操作未完成,邮箱不可用 (例如,邮箱未找到,或不可访问) 451 放弃要求的操作;处理过程中出错
551 用户非本地,请尝试<forward-path >
452 系统存储不足,要求的操作未执行
552 过量的存储分配,要求的操作未执行
553 邮箱名不可用,要求的操作未执行
354 开始邮件输入,以"." 结束
554 操作失败
(3)SMTP 协议工作原理
SMTP 协议规定的命令是以明文方式进行的。SMTP 工作在两种情况下:一是电子邮件从客户机传输到服务器;二是从某一个服务器传输到另一个服务器。SMTP 是个请求/响应协议,命令和响应都是基于ASCII 文本,并以CR 和LF 符结束。响应包括一个表示返回状态的三位数字代码。SMTP 服务器在TCP 协议25号端口监听连接请求。连接和发送过程如下:
a.建立TCP 连接
b.客户端发送HELO 命令以标识发件人自己的身份,然后客户端发送MAIL 命令服务器端正希望以OK 作为响应,表明准备接收。
c.客户端发送RCPT 命令,以标识该电子邮件的计划接收人,可以有多个RCPT 行。服务器端则表示是否愿意为收件人接受邮件。
d.协商结束,发送邮件,用命令DATA 发送。
e.以. 表示结束输入内容一起发送出去。
f.结束此次发送,用QUIT 命令退出。
2.1.2 SMTP协议的会话流程
在进行程序设计之前有必要弄清SMTP 协议的会话流程,其实前面介绍的内容已经可以大致勾勒出用SMTP 发送邮件的框架了,对于一次普通的邮件发送,其过程大致为:先建立TCP 连接,随后客户端发出HELLO 命令以标识发件人自己的身份,并继续由客户端发送MAIL 命令,如服务器应答为"OK" ,可继续发送RCPT 命令来标识电子邮件的收件人,在这里可以有多个RCPT 行,而服务器端则表示是否愿意为收件人接受该邮件。在双方协商结束后,用命令DATA 将邮件发送出去,其中对表示结束的"." 也一并发送出去。随后结束本次发送过程,以QUIT 命令退出。下面通过一个实例,从[email protected]发送邮件到[email protected]来更详细直观地描述此会话流程:
(S ;SENDER R:RECIEVOR)
s: 建立连接
R:220 sina.com Simple Mail Transfer Service Ready
S:HELLO sohu.com //客户端发出HELLO 命令以标识发件人自己的身份
R:250 sina.com
S:MAIL FROM:<[email protected]> //客户端发送MAIL 命令
R:250 OK //250 要求的邮件操作完成
S:RCPT TO:<[email protected]> //发送RCPT 命令来标识电子邮件的收件人,在这里可
以有多个RCPT 行
R:250 OK //250 服务器端则表示是否愿意为收件人接受该邮件
S:DATA //命令DATA 将邮件发送出去
R:354 Start mail input;end with "<CRLF >. <CRLF >"
S: Blah blah blah...
S: ...等等
S: .
R:250 OK
S:QUIT
R:221 sina.com Service closing transmission channel
2.2 POP3协议
2.2.1 POP3协议用于电邮接收的工作模式和原理
POP 的全称是 Post Office Protocol,即邮局协议,用于电子邮件的接收,它使用TCP 的110端口。现在常用的是第三版 ,所以简称为 POP3。POP3仍采用Client/Server工作模式,Client 被称为客户端,Server 是服务器端。 应用层协议建立在网络层协议之上,相应的软件会调用应用层的相应协议。如当打开Foxmail 这个邮件软件收取邮件时,Foxmail 这个软件就会调用TCP/IP参考模型中的应用层协议-POP 协议。POP 这个应用层的协议会指挥TCP 协议,利用IP 协议将一封大邮件拆分成若干个数据包在Internet 上传送。
(1)POP3标准命令
这个协议只包含12个命令(其中有3个完全可以忽略它们的存在)。这些命令被客户端计算机用来发送给远程服务器。反过来,服务器返回给客户端计算机两个回应代码。
POP3命令由一个命令和一些参数组成。所有命令以一个CRLF 对结束。命令和参数由可打印的ASCII 字符组成,它们之间由空格间隔。命令一般是三到四个字母,每个参数却可达40个字符长。
POP3服务器响应由一个状态码和一个可能跟有附加信息的命令组成。不同服务器的回应会有所不同,但开头部分都是一样的所有响应也是由CRLF 对结束。现在有两种状态," 确定"("+OK")和" 失败"("-ERR")。
------------------------------------------------------------------------ 表2-3 POP3标准命令
(2) POP工作原理简介
下面简单介绍一下电子邮件软件收取电子邮件的过程,一般我们在电子邮件软件的账号属性上设置一个POP 服务器的URL(Uniform Resource Locator ) (比如pop.163.com ),以及邮箱的账号和密码。当我们按下电子邮件软件中的收取键后,电子邮件软件首先会调用DNS(Domain Name Server) 协议对POP 服务器进行解析IP 地址,当IP 地址被解析出来后,邮件程序便开始使用TCP 协议连接邮件服务器的110端口,因为POP 服务器是比较忙的,所以在这个过程中我们相对要等比较长的时间。当邮件程序成功地连上POP 服务器后,其先会使用USER 命令将邮箱的账号传给POP 服务器,然后再使用PASS 命令将邮箱的账号传给服务器,当完成这一认证过程后,邮件程序使用STAT 命令请求服务器返回邮箱的统计资料,比如邮件总数和邮件大小等,然后LIST 便会列出服务器里邮件数量。然后邮件程序就会使用RETR 命令接收邮件,接收一封后便使用DELE 命令将邮件服务器中的邮件置为删除状态。当使用QUIT 时,邮件服务器便会将置为删除标志的邮件给删了。通俗地讲,邮件程序从服务器接收邮件,其实就是一个对话过程,POP 协议就是用于电子邮件的一门语言。
(3) POP3会话的3个状态
a." 确认" 状态
首先TCP 连接由POP3客户打开,POP3服务器发送一个单行的确认。这个消息可以是由CRLF 结束的任何字符。例如,它可以是:
S:+OKPOP3serverready
此时POP3会话就进入了" 确认" 状态。此时,客户必须向服务器证明它的身份。在此介绍两种可能的处理机制,一种是USER 和PASS 命令,另一种是在后面要介绍的APOP 命令用USER 和PASS 命令进行确认过程,客户必须首先发送USER 命令,如果POP3服务器以" 确认" 状态码响应,客户就可以发送PASS 命令以完成确认,或者发送QUIT 命令终止POP3会话。如果POP3服务器返回" 失败" 状态码,客户可以再发送确认命令,或者发送QUIT 命令。
当客户发送了PASS 命令后,服务器根据USER 和PASS 命令的附加信息决定是否允许访问相应的存储邮件。一旦服务器通过这些数据决定允许客户访问储存邮件,服务器会在邮件上加上排它锁,以防止在进入" 更新" 状态前对邮件的改变。如果成功获得了排它锁,服务器返回
一个" 确认" 状态码。会话进入" 操作状态" ,同时没有任何邮件被标记为删除。如果邮件因为某种原因不能打开(例如,排它锁不能获得,客户不能访问相应的邮件或者邮件不能进行语法分析),服务器将返回" 失败" 状态码。在返回" 失败" 状态码后,服务器会关闭连接。如果服务器没有关闭连接,客户可以重新发送确认命令,重新开始,或者发送QUIT 命令。
在服务器打开邮件后,它为每个消息指定一个消息号,并以八进制表示每个消息的长度。第一个消息被指定为1,第二个消息被指定为2,以此类推,第N 个消息被指定为N 。在POP3命令和响应中,所以的消息号和长度以十进制表示。下面是在" 确认" 状态中可用的命令:
USER username;PASS password; QUIT
b." 操作" 状态
一旦客户向服务器成功地确认了自己的身份,服务器将锁住并打开相应的邮件,这时POP3会话进入" 操作" 状态。现在客户可以重复下面的POP3命令,对于每个命令服务器都会返回应答。最后,客户发送QUIT 命令,会话进入" 更新" 状态。下面是在" 操作" 状态中可用的命令:STAT;LISTmsg;RETRmsg;DELEmsg;NOOP;RSET; TOPmsgn;UIDL[msg]
c." 更新" 状态
当客户在" 操作" 状态下发送QUIT 命令后,会话进入" 更新" 状态。(注意:如果客户在" 确认" 状态下发送QUIT 后,会话并不进入" 更新" 状态。)如果会话因为QUIT 命令以外的原因中断,会话并不进入" 更新" 状态,也不从服务器中删除任何信件。QUIT 在" 更新" 状态有效。; 可选的POP3命令:
APOPnamedigest 在" 确认" 状态有效
TOPmsgn; UIDL[msg]在" 操作" 状态有效 注:除了STAT ,LIST 和UIDL 的响应外,其它命令的响应均为"+OK"和"-ERR" 。响应后的所有文本将被客户略去。
2.2.2 POP3会话流程
S:
C:
S:+OKPOP3serverready
C:USER username
S:+OK
C:PASS password
S:+OK
C:STAT //请求服务器发回关于邮箱的统计资料,如邮件总数和总字节数 S:+OK2320
C:LIST //返回邮件数量和每个邮件的大小
S:+OK2messages(320octets)
S:1120
S:2200
S:….
C:RETR1 //回由参数标识的邮件的全部文本
S:+OK..
S:
S:……
C:DELE1
S:+OKmessage1deleted
C:QUIT
S:+OKdeweyPOP3serversigningoff(maildropempty)
C:
S:
2.3 RFC822简介
电子邮件是全世界通用的。你可以给任何一个人发送电子邮件而不用考虑他所处的地理位置在哪或他使用的是什么软件作为邮件接受客户端。和其他与Internet 相关的事物一样(或者可以说,与计算机技术相关的事物),电子邮件也是基于一种标准的,而人们把这个特殊的标准称作RFC 822。这个标准早在二十多年前(准确的日期是一九八二年八月十三日)就公布于世,在这期间它基本没有改变。在2001年公布的RFC 2822标准取代了RFC 822,但是它的基本内容和RFC 822是完全一致的,只是做了一些很小的更新和改动。而其他的一些邮件标准,比如RFC 850(在USENET 上使用),同样也基于RFC 822标准。(RFC,Request For Comments )
2.4 Winsock简介
许多网络程序都是采用Socket 套接字实现的,对于一些标准的网络协议如HTTP 、FTP 和SMTP 等协议的编程也是基于套接字程序的,只是端口号不再是随意设定而要由协议来指定,比如HTTP 端口在80、FTP 是21,而SMTP 则是25。Socket 只是提供在指定的端口上同指定的服务器从事网络上的通讯能力,至于客户和服务器之间是如何通讯的则由网络协议来规定,这对于套接字是完全透明的。因此可以由Socket 套接字为SMTP 提供网络通讯基础,而对于网络通讯连路建立好之后采取什么样的通讯应答则要按SMTP 协议的规定去执行了。本科题为方便起见,没有采用编写较复杂的Windows Sockets API 进行编程,而是使用经过较好封装的MFC 的CSocket 类。
2.5 MFC简介
MFC (Microsoft Foundation Class Library) 中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows 下的应用程序,这是一种相对SDK
来说更为简单的方法。因为总体上,MFC 框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C++提供了相应的工具来完成这个工作:AppWizard 可以用来生成初步的框架文件(代码和资源等);资源编辑器用于帮助直观地设计用户接口;ClassWizard 用来协助添加代码到框架文件;最后,编译,则通过类库实现了应用程序特定的逻辑。 MFC封装了Win32 API ,OLE API ,ODBC API 等底层函数的功能,并提供更高一层的接口,简化了Windows 编程。同时,MFC 支持对底层API 的直接调用。
MFC提供了一个Windows 应用程序开发模式,对程序的控制主要是由MFC 框架完成的,而且MFC 也完成了大部分的功能,预定义或实现了许多事件和消息处理,等等。框架或者由其本身处理事件,不依赖程序员的代码;或者调用程序员的代码来处理应用程序特定的事件。 MFC是C++类库,程序员就是通过使用、继承和扩展适当的类来实现特定的目的。例如,继承时,应用程序特定的事件由程序员的派生类来处理,不感兴趣的由基类处理。实现这种功能的基础是C++对继承的支持,对虚拟函数的支持,以及MFC 实现的消息映射机制。
第三章 电子邮件系统的设计和开发
本章首先对SMTP 协议,POP3协议的工作流程作了详细的分析,然后按照SMTP 和POP3 协议结合Windows Sockets套接字进行设计实现代码并封装成CSMTP 协议类和CPOP3协议类。最后根据客户端的基本需求设计成客户端邮件收发软件,并以友好的界面形式呈现出来。
3.1 服务器端作用简介
SMTP ,POP3采用Client/Server工作模式,Client 被称为客户端,一般我们日常使用电脑都是作为客户端,而Server (服务器)则是网管人员进行管理的。SMTP 服务器的主要功能就是接收来自客户端的SMTP 协议命令,然后对这些命令进行解析,根据不同的命令进行不同的处理,处理完成之后,将结果发送给客户端。
图要作说明
图3-1 Client/Server工作模式示意图
以下用简单的流程图来分别示范SMTP ,POP3的服务器端与他们对应的客户端的会话:
图3-2 SMTP 会话流程示意图
如图示,连接和发送过程如下:
a.建立TCP 连接
b.客户端发送HELO 命令以标识发件人自己的身份,然后客户端发送MAIL 命令服务器端正希望以OK 作为响应,表明准备接收。
c.客户端发送RCPT 命令,以标识该电子邮件的计划接收人,可以有多个RCPT 行。服务器端则表示是否愿意为收件人接受邮件。
d.协商结束,发送邮件,用命令DATA 发送。
e.以. 表示结束输入内容一起发送出去。
f.结束此次发送,用QUIT 命令退出。
图3-2 POP3会话流程示意图
如图示,当邮件程序成功地连上POP 服务器后,其先会使用USER 命令将邮箱的账号传给POP 服务器,然后再使用PASS 命令将邮箱的账号传给服务器,当完成这一认证过程后,邮件程序使用STAT 命令请求服务器返回邮箱的统计资料,比如邮件总数和邮件大小等,然后LIST 便会列出服务器里邮件数量。然后邮件程序就会使用RETR 命令接收邮件,也可在接收一封后便使用DELE 命令将邮件服务器中的邮件置为删除状态。当使用QUIT 时,邮件服务器便会将置为删除标志的邮件给删了。
3.2 客户端程序设计
本节主要介绍客户端的实现。本人选择使用VC++6.0来实现本系统的客户端,VC++6.0是使用面向对象语言C++的开发环境,更有利于集中精力于主要功能的实现。由图3-2,图3-3可以明显看出客户端需完成的任务:
1.发送SMTP 命令,处理相应服务器的响应,将邮件按RFC822格式编码并发送。
2.发送POP3命令,处理相应服务器的响应,将邮件按RFC822格式解码并发送。
本系统的现实主要使用了CSMTP,CPOP3,CmailMessage 封装类。CSMTP 类中的成员函数主
要实现发送SMTP 命令并处理SMTP 服务器响应 ,CPOP3的成员函数主要实现发送POP3命令并处理POP3服务器响应,CMailMessage 实现邮件的编码解码功能。
3.2.1邮件的格式化
由于电子邮件结构上的特殊性,在传输时是不能当作简单的文本来直接处理的,而必须按照一定的格式对邮件头和邮件体进行格式化处理之后才可以被发送。需要进行格式化的部分主要有:发件人地址、收件人地址、主题和发送日期等。在RFC (Request For Comments )文档的RFC 822里对邮件的格式化有详尽的说明. 在本论文中由CMailMessage 类中的成员函数实现邮件的编码解码功能。
BOOL EncodeHeader();
BOOL DecodeHeader();
void EncodeBody();
void DecodeBody();
下面通过VC++6.0按照RFC 822文档规定将格式化邮件的部分编写如下(部分代码): BOOL CMailMessage::EncodeHeader()//此函数为邮件头部编码(此处仅列出了关键部分) {
CString sTo; CString sDate; CString sEmail = ""; CString sFriendly = ""; m_tDateTime = m_tDateTime.GetCurrentTime(); // Format: Mon, 01 Jun 98 01:10:30 GMT m_sHeader.Format( "From: %s\r\n"\ "To: %s\r\n"\ "Date: %s\r\n"\ "Subject: %s\r\n", ………… // Include other extension lines if desired
}
void CMailMessage::EncodeBody() //此函数为邮件体编码(此处仅列出了关键部分)
{
CString sCooked = ""; LPTSTR szBad = "\r\n.\r\n"; (LPCTSTR)m_sFrom, //sender (LPCTSTR)sTo, //recipients (LPCTSTR)sDate, //日期 (LPCTSTR)m_sSubject);//主题
符
LPTSTR szGood = "\r\n..\r\n"; int nPos; int nBadLength = strlen( szBad ); if( m_sBody.Left( 3 ) == ".\r\n" ) m_sBody = "." + m_sBody; while( (nPos = m_sBody.Find( szBad )) > -1 ) { //npos中是结束符所在的位子,即共有多少有意义的字 sCooked = m_sBody.Mid( 0, nPos ); sCooked += szGood; m_sBody = sCooked + m_sBody.Right( m_sBody.GetLength() - (nPos + nBadLength) );
}
3.2.2 SMTP&POP3协议封装类
很多EMAIL 系统都是使用SMTP 协议来作为发送协议,而POP3协议来作为接受协议。本节编写了有关于SMTP,POP3协议的C++类,这样的话呢,我们可以在连接的时候使用它。并且,类中实现的一些功能类似于SMTP,POP3协议中的一些命令。以下程序是通过使用MFC 中CSocket 类中成员函数的使用实现的,它包含在afxsock.h 中。
CSMTP class中的关键函数如下:
BOOL Connect(); ////格式化并发送HELLO 命令,连接SMTP 服务器
BOOL Disconnect(); //断开连接SMTP 服务器
virtual BOOL FormatMailMessage( CMailMessage* msg );
// 调用CMailMessage 类EncodeHeader()EncodeBody()对邮件格式化
BOOL SendMessage( CMailMessage* msg );
// 调用CSMTP 类 FormatMailMessage( ) transmit_message( )
BOOL get_response( UINT response_expected ); //判断应答码是否为期望值 BOOL transmit_message( CMailMessage* msg ); } //最后以"\r\n.\r\n"结尾
//发送SMTP 命令调用get_response()处理服务器应答码并发送邮件头和邮件体 SMTP 协议类中部分成员函数的实现如下(此处仅列出了关键部分) :
CSMTP::CSMTP( LPCTSTR szSMTPServerName, UINT nPort )
{
………
AfxSocketInit(); // 用AfxSocketInit()函数对套接字进行初始化 ………;
}
BOOL CSMTP::Connect()
{
………
if( !m_wsSMTPServer.Create() )//用Create()创建套接字对象
{
m_sError = _T( "Unable to create the socket." );
return FALSE;
}
if( !m_wsSMTPServer.Connect( GetServerHostName(), GetPort() ) )
{ //由该套接字通过Connect ()建立同邮件服务器的连接。 m_sError = _T( "Unable to connect to server" );
m_wsSMTPServer.Close();
}
if( !get_response( CONNECT_SUCCESS ) )
{
m_sError = _T( "Server didn't respond." );
m_wsSMTPServer.Close();
return FALSE;
}
………
sHello.Format( "HELO %s\r\n", local_host );
m_wsSMTPServer.Send( (LPCTSTR)sHello, sHello.GetLength() );
if( !get_response( GENERIC_SUCCESS ) )
{
m_wsSMTPServer.Close();
return FALSE;
}
m_bConnected = TRUE;
return TRUE;
}
BOOL CSMTP::SendMessage(CMailMessage * msg)
{
………
if( FormatMailMessage( msg ) == FALSE )
{
return FALSE;
}
if( transmit_message( msg ) == FALSE )
{
return FALSE;
}
return TRUE;
}
BOOL CSMTP::FormatMailMessage( CMailMessage* msg )
{
………
if( msg->EncodeHeader() == FALSE )
{
return FALSE;
}
msg->EncodeBody();
if( msg->m_sBody.Right( 2 ) != "\r\n" )
msg->m_sBody += "\r\n"; //确认最后以\r\n.\r\n结束
return TRUE;
}
BOOL CSMTP::transmit_message(CMailMessage * msg)
{ ………
// Send the MAIL command
sFrom.Format( "MAIL From: \r\n", (LPCTSTR)msg->m_sFrom );
m_wsSMTPServer.Send( (LPCTSTR)sFrom, sFrom.GetLength() );
if( !get_response( GENERIC_SUCCESS ) )
return FALSE;
// Send RCPT commands (one for each recipient)
for( int i = 0; i GetNumRecipients(); i++ )
{
msg->GetRecipient( sEmail, sTemp, i );
sTo.Format( "RCPT TO: \r\n", (LPCTSTR)sEmail );
m_wsSMTPServer.Send( (LPCTSTR)sTo, sTo.GetLength() );
get_response( GENERIC_SUCCESS );
}
// Send the DATA command
sTemp = "DATA\r\n";
m_wsSMTPServer.Send( (LPCTSTR)sTemp, sTemp.GetLength() );
………
// Send the header
m_wsSMTPServer.Send( (LPCTSTR)msg->m_sHeader, msg->m_sHeader.GetLength() ); // Send the body
m_wsSMTPServer.Send( (LPCTSTR)msg->m_sBody, msg->m_sBody.GetLength() ); // Signal end of data"<CRLF >. <CRLF >"
sTemp = "\r\n.\r\n";
m_wsSMTPServer.Send( (LPCTSTR)sTemp, sTemp.GetLength() );
return TRUE;
}
到此为止,已基本在程序中体现出了SMTP 协议类的功能,能在Socket 套接字所提供的网络通讯能力基础之上实现以SMTP 命令和SMTP 应答码为基本会话内容的通讯交互过程,从而最终实现SMTP 协议对电子邮件的发送。
CPOP3 class中的关键函数如下:
BOOL Disconnect();//发送QUIT 命令
BOOL GetMessage( UINT nMsg, CMailMessage* msg); //发送RETR 命令,获得邮件
int CPOP3::GetNumMessages()//发送STAT 获得邮件总数及字节数
BOOL DeleteMessage( UINT nMsg );//发送DELETE 命令 BOOL get_response( UINT executed_action );
CPOP3协议类中部分成员函数的实现如下(此处仅列出了关键部分) :
CPOP3::CPOP3( LPCTSTR szPOP3ServerName, UINT nPort, LPCTSTR sUsername, LPCTSTR sPassword)
{
……… AfxSocketInit();//在正式使用套接字之前,先用AfxSocketInit()函数对套接字进行初始化
}
BOOL CPOP3::Connect()
{ ………
if( !m_wsPOP3Server.Create() ) //用Create()创建套接字对象 { }
//由该套接字通过Connect ()建立同邮件服务器的连接
if( !m_wsPOP3Server.Connect( GetServerHostName(), GetPort() ) ) { } ……… //命令 USER & PASS对在网络上发送明文用户名和 口令给服务器 sUser.Format( "USER %s\r\n", GetUsername()); m_wsPOP3Server.Send( (LPCTSTR)sUser, sUser.GetLength() ); if( !get_response( IDENTIFICATION ) ) { m_wsPOP3Server.Close(); return FALSE; m_sError = _T( "Unable to connect to server" ); m_wsPOP3Server.Close(); return FALSE; m_sError = _T( "Unable to create the socket." ); return FALSE; ………
} sPass.Format( "PASS %s\r\n", GetPassword()); m_wsPOP3Server.Send( (LPCTSTR)sPass, sPass.GetLength() ); if( !get_response( AUTHENTIFICATION ) ) { } m_bConnected = TRUE; return TRUE; m_wsPOP3Server.Close(); return FALSE;
BOOL CPOP3::GetMessage( UINT nMsg, CMailMessage* msg)
{
CString sMsg; CString sRetr; sRetr.Format("RETR %d\r\n",nMsg); m_wsPOP3Server.Send( (LPCTSTR)sRetr, sRetr.GetLength() ); while ( sMsg.Find("\r\n.\r\n")
sMsg=sMsg.Mid(sMsg.Find("\r\n")+2); //first line of output is +OK
sMsg=sMsg.Left(sMsg.GetLength()-3); //last line is always .\r\n int br=sMsg.Find("\r\n\r\n"); //breakpoint between header and body msg->m_sHeader=sMsg.Left(br); msg->m_sBody=sMsg.Mid(br+4); msg->DecodeHeader(); msg->DecodeBody(); int nChars = m_wsPOP3Server.Receive( response_buf, RESPONSE_BUFFER_SIZE ); if ( nChars == SOCKET_ERROR ) return FALSE; m_sResponse=response_buf; sMsg+=m_sResponse.Left( nChars );
return TRUE;
}
3.2.3 客户端邮件收发系统介绍
(1)客户端邮件收发系统应用界面…
图注
(2)此系统的使用方法:
发送邮件时,先输入SMTP 邮件服务器(如smtp.sina.com.cn), 发信人邮箱,收信人邮箱,SMTP 服务器用户名和密码,然后写好信的标题和内容,最后单击发送按钮即可。
收信时,先输入 POP3 邮件服务器(如pop3.sina.com.cn ),POP3服务器用户名和密码,然后单击接收按钮,第一封信件就会显示出来。
在Messag 文本框中输入要阅读的邮件的索引号。然后点击Receive 按钮。 在Messag 文本框中输入要删除的邮件的索引号。然后点击Delete 按钮。
点击Status, 将会弹出错误信息对话框。
3.2.4 各功能模块实现方法及流程
根据本人对邮件收发系统的使用需求的理解,将此系统分为 接收,发送,编辑,删除四个模块。以下是对他们的实现方法的具体介绍。以下程序是通过使用MFC 中CSocket 类,自定义的CSMTP 类,CPOP3类中成员函数的使用实现的。
a. 发送
在正式使用套接字之前,也要先用AfxSocketInit()函数对套接字进行初始化,然后用Create()创建套接字对,这是在CSMTP 创建对象时由构造函数自动执行的。并由该套接字通过Connect ()建立同邮件服务器的连接。
图3-1 邮件发送模块程序设计流程图
实现此模块所调用的CSMTP 中关键函数:
BOOL Connect(); //格式化并发送HELLO 命令,连接SMTP 服务器
BOOL Disconnect(); //断开连接SMTP 服务器
virtual BOOL FormatMailMessage( CMailMessage* msg );
// 调用CMailMessage 类EncodeHeader()EncodeBody()对邮件格式化
BOOL SendMessage( CMailMessage* msg );
// 调用CSMTP 类 FormatMailMessage( ) transmit_message( )
BOOL transmit_message( CMailMessage* msg );
//发送SMTP 命令调用get_response()处理服务器应答码并发送邮件头和邮件体
图3-1中的smtp 是CSMTP 类创建的对象,以下为发送功能的实现代码:
void CMailDlg::OnSend()
{
UpdateData( TRUE ); CSMTP smtp( m_SMTP ); //加载winsock dll版本并为SMTP 的对象smtp 中成员变量赋值 CMailMessage msg; msg.m_sFrom = m_From; msg.AddMultipleRecipients( m_To ); msg.m_sSubject = m_Subject; msg.m_sBody = m_Body; if( !smtp.Connect() )
}
{ }
if( !smtp.SendMessage( &msg ) ) { }
if( !smtp.Disconnect() ) { }
AfxMessageBox( _T( "Message Sent Successfully") );
AfxMessageBox( smtp.GetLastError() ); return;
AfxMessageBox( smtp.GetLastError() ); return;
AfxMessageBox( smtp.GetLastError() ); return;
b. 接收
实现此模块,也要先用AfxSocketInit()函数对套接字进行初始化,然后用Create()创建套接字对,这是在CSMTP 创建对象时由构造函数自动执行的。并由该套接字通过Connect ()建立同邮件服务器的连接。
图3-2 邮件接收模块程序设计流程图
实现此模块所调用的POP3中关键函数:
BOOL Disconnect();//发送QUIT 命令
BOOL Connect(); //创建并初始化套节字,发送USER,PASS 命令
BOOL GetMessage( UINT nMsg, CMailMessage* msg); //发送RETR 命令,获得邮件 int CPOP3::GetNumMessages()//发送STAT 获得邮件总数及字节数
BOOL DeleteMessage( UINT nMsg );//发送DELETE 命令
上图中的pop3是CPOP3类创建的对象,以下为发送功能的实现代码: void CMailDlg::OnRetr() {
UpdateData( TRUE ); CPOP3 pop3( m_POP3 );
pop3.SetUserProperties(m_User,m_Password); if (!pop3.Connect()) { }
CMailMessage msg;
if (!pop3.GetMessage(m_MN,&msg)) { }
m_Body=msg.m_sBody; m_Subject=msg.m_sSubject; m_From=msg.m_sFrom; m_To="";
for (int a=0; a
m_To.TrimRight();
CString sEmail; CString sFriendly;
msg.GetRecipient(sEmail,sFriendly,a); m_To+=sEmail; m_To+=" ";
AfxMessageBox( pop3.GetLastError() ); return;
AfxMessageBox( pop3.GetLastError() ); return;
UpdateData(FALSE); }
c. 删除
if( !pop3.Disconnect() ) { }
AfxMessageBox( _T( "Successfully disconnected" ) );
AfxMessageBox( pop3.GetLastError() ); return;
图3-3 邮件删除模块程序设计流程图
上图中的pop3是CPOP3类创建的对象,以下为删除功能的实现代码: 此函数调用BOOL DeleteMessage( UINT nMsg ); //发送DELETE 命令 void CMailDlg::OnDele() {
UpdateData( TRUE ); CPOP3 pop3( m_POP3 );
pop3.SetUserProperties(m_User,m_Password); if (!pop3.Connect()) {
AfxMessageBox( pop3.GetLastError() );
}
if (!pop3.DeleteMessage(m_MN)) { }
AfxMessageBox( pop3.GetLastError() ); return;
if( !pop3.Disconnect() ) { }
AfxMessageBox( pop3.GetLastError() ); return;
AfxMessageBox( _T( "Successfully disconnected" ) ); } }
第四章 总 结
通过此次对客户端邮件收发系统的设计与实现,我们深刻体会到电子邮件类软件作为Internet 上的应用软件,其设计开发必须符合Internet 上成熟的技术规范和相关协议。只有在遵循了上述规范和协议的基础上进行编程才能真正实现邮件类软件产品和服务的开放性和标准化。本文对SMTP 协议及其在VC++编程中的实现做了介绍,并按照SMTP 协议对电子邮件的收发进行了开放性和标准性较好的程序设计。本文所述程序在WindowsXP 下,由Microsoft Visual C++ 6.0编译通过。
本客户端邮件收发系统仍存在一定的缺陷,如未能实现多媒体附件收发功能,未能使显示框支持网页显示。而且此系统未实现客户端邮件的管理及邮件的安全监测功能。由于本人专业知识水平有限,对编译环境尚未熟练运用的制约,设计上的不足之处,还望指导老师批评指正。
致谢
参考文献
[1]殷肖川、刘志宏《网络编程与开发技术》西安交通大学出版社,2003.9 [2] 李凌《Winsock2网络编程实用教程》清华大学出版社,2003.4 [3]萧秋水、文娟《Windows 网络编程之VC 篇》清华大学出版社 ,2001.6
[4]肖宏伟《VisualC++实效编程百例》人民邮电出版社,2005.8 [5]候俊杰《深入浅出MFC 》华中科技大学出版社 ,2000.9 [6]古槿《学Visual C++30例 》人民邮电出版社 ,2004.10
[7]丁展、刘海英《Visual C++ 网络通信编程实用案例精选》人民邮出版社,2000.8 [8]臧桂鹏、肖佳放《Visual C++网络与数据库编程百例》中国电力出版社,2002.7 [9]洪志全《网络实用技术教程》电子工业出版社,2000
[10] Microsoft. MSDNLibrary[CP/DK]. Microsoft Corp,2001. [11] 李非,《安全电子邮件系统的设计与实现》,2004;
[12] 杨义先, 《网络安全理论与技术》, 人民邮电大学出版社,2003年; 文献再加几篇,英文6、7篇