1、电子邮件客户端软件,指导教师:,1.引言,1.1电子邮件简单介绍电子邮件(简称E-mai1)又称电子信箱、电子邮政,它是种用电子手段提供信息交换的通信方式。它是全球多种网络上使用最普遍的一项服务。这种非交互式的通信,加速了信息的交流及数据传送,它是个简易、快速的方法。通过连接全世界的Internet,实现各类信号的传送、接收、存贮等处理,将邮件送到世界的各个角落。到目前为止,可以说电子邮件是Internet资源使用最多的一种服务,E-mai1不只局限于信件的传递,还可用来传递文件、声音及图形、图像等不同类型的信息。例如比较著名的邮件收发软件有系统自带的OutLook,还有比较著名的第三方软件F
2、oxMaiL,FastMail等等。,1.2开发背景介绍,当前流行的各大邮件客户端软件的除了最主要的收发信件之外,功能越来越复杂,但是人们平常真正用到的功能很少,很多功能尤其对于那些计算机知识相对缺乏的人来说,更加显得太过于华丽而不太实用。有鉴于此,在了解RFC底层协议的基础上,我们开发了这个各种功能相对简单实用的邮件客户端程序,简化了很多不必要的功能。,1.3.1 开发环境 AMD Athlon(TM),512M内存,80G硬盘 Microsoft Windows XP Professional Microsoft Visual Studio 2003(C Sharp) Microsoft
3、Developer Network for Visual Studio.NET 20031.3.2 运行环境 Intel Pentium 2及以上处理器,32M以上内存,4G以上硬盘 Microsoft Windows 9X/NT操作系统 800*600或以上的屏幕分辨率 确保机器上安装有.Net FrameWork 1.0或者以上版本,2.软件架构及系统用例图,2.1 系统架构 软件的总体架构如图2.1:,22 系统总体用例,2.3 程序功能框图,3 SMTP协议的研究,由于要开发的是邮件客户端程序,就不得不用到SMTP协议和POP协议。而我个人负责的是邮件发送功能的实现,因此就必然会涉及到
4、SMTP(Simple Mail Transfer Protocol)协议。SMTP被用来在因特网上发送邮件,该协议规定了一些基本的命令和方法使客户端与服务器进行交互,以达到发送邮件的目的。,31 SMTP协议简介及工作原理,311 介绍 简单邮件传输协议(SMTP)的目标是可靠高效地传送邮件,它独立于传送子系统而且仅要求一条可以保证传送数据单元顺序的通道。 SMTP的一个重要特点是它能够在传送中接力传送邮件,传送服务提供了进程间通信环境(IPCE),此环境可以包括一个网络,几个网络或一个网络的子网。理解到传送系统(或IPCE)不是一对一的是很重要的。进程可能直接和其它进程通过已知的IPCE通
5、信。邮件是一个应用程序或进程间通信。邮件可以通过连接在不同IPCE上的进程跨网络进行邮件传送。更特别的是,邮件可以通过不同网络上的主机接力式传送。,32 SMTP协议的命令和应答,321 SMTP协议的命令SMTP命令定义了邮件传输或由用户定义的系统功能。它的命令是由结束的字符串。而在带有参数的情况下,命令本身由和参数分开,如果未带参数可以直接和连接。邮箱的语法格式必须和接收站点的格式一致。下面讨论SMTP几个常用的命令和应答。 下面是SMTP命令: HELO MAIL FROM: RCPT TO: DATA NOOP QUIT TURN ,322 SMTP的应答码,对SMTP命令的响应是多样
6、的,它确定了在邮件传输过程中请求和处理的同步,也保证了发送SMTP知道接收SMTP的状态。每个命令必须有且只有一个响应。 SMTP响应由三位数字组成,其后跟一些文本。数字帮助决定下一个应该进入的状态,而文本对人是有意义的。三位的响应已经包括了足够的信息,不用再阅读文本,文本可以直接抛弃或者传递给用户。特别的是,文本是与接收和环境相关的,所以每次接收到的文本可能不同。在附录E中可以看到全部的响应码。正规的情况下,响应由下面序列构成:三位的数字,一行文本和一个,或者也可以是一个多行响应。只有EXPN和HELP命令可以导致多行应答,然而,对所有命令,多行响应都是允许的。 REPLY CODES BY
7、 FUNCTION GROUPS 500 格式错误,命令不可识别(此错误也包括命令行过长) 501 参数格式错误 502 命令不可实现,4 命名控件MailSend,由于在C Sharp语言中,都是以命名控件来组织程序的。而所有的类都归属于一个特定的命名空间下。需要的命名空间系统本身自带了一部分,而且如果系统没有你需要的命名空间的话,就可以自己编写,本节中的这个命名空间就是由于需要而编写的。而调用某一个类中的某个变量成员的方法就是通过 命名空间名.类名.变量成员 来访问的,当然在C Sharp 中如果在程序开始通过Using 命名空间名,就可以直接的象C+那样来访问成员变量,可以说相当的方便,
8、这些都会在程序中体现出来,在此不再做过多的叙述。,41 发送邮件类SmtpMail,411 主要成员变量说明 1) 网络连接类及实例TcpClient tc 为 TCP 网络服务提供客户端连接类TcpClient实例对象tc。TcpClient 类提供了一些简单的方法,用于在同步阻塞模式下通过网络来连接、发送和接收流数据。而实例化的过程也是连接SMTP服务器的过程。它的重载方法之一的两个参数一个为服务器名称字符串,另一个为服务器的埠。 2)提供用于网络访问的基础数据流及其实例 NetworkStream ns 此类提供访问网络的基础数据流的方法。其中最基本也是最重要的两个方法就是Write()
9、和Read()方法,至于参数不再次赘述。 3)一维字符串数组变量FilePath 此字符串数组主要用来存放用户选择的附件的绝对路径名,并在发送带附件的邮件时用到。,4)发送邮件所需的基本参数 比如用于ESMTP等录检验用的用户名、密码,发送邮件需要的收信人,发信人地址以及主题等等在此不再赘述。 412 主要成员函数说明 1)重载的构造函数 SmtpMail()此函数主要用于在初始化过程中,把用户选择的附件的路径以参数的形式传给FilePath。 2)添加附件的函数 AddAttachment传给FilePath的路径,通过这样一个函数就可以循环的动态的添加到IList接口的一个对象中了,方便以
10、后在具体的实现的过程中的使用。 3)得到上传的附件的文件流 GetStream由于在网络中的操作都是以网络流的形式来实现的,因此先将上传的附件转换成文件流,然后再用Write的方法把这些附件的文件流写入到网络中,来完成发送附件的操作。,4)将字符串编码为Base64字符串的函数 Base64Encode由于ESMTP的LOGIN认证机制是采用Base64编码,当用户发出AUTHLOGIN的命令后,服务器返回334的应答码等待用户输入。如果身份确认后服务器返回235的应答码,否则返回失败信息。所以要将用户名和密码转换成Base64编码然后再发给服务器。此函数的作用就是把给定的字符串转换成相应的B
11、ase64编码的字符串。 5) 发送SMTP命令的函数 SendCommand这个函数的作用是把SMTP命令的字符串转换成对应的字节型值(C中规定的Write方法只能写入字节型的数据)然后写入网络中,如果操作成功就返回一个标志为真的布尔型变量,如果操作失败或者发生异常就返回标志为假的布尔型变量,6) 接受服务器应答的函数 RecvResponse 它的作用就是从网络流中读取服务器返回的字节型的信息,将其转换成字符串型的变量,然后将其返回,可以通过其返回值来判断操作是否成功。具体实现代码如下所示: 7) 重载的函数 Dialog 它们的作用是与服务器交互,发送命令并接收回应。不同的是参数是字符串
12、类型的那个函数,每次发送一条命令,并接受服务器的响应,根据响应的信息来判断交互的结果是否成功。而参数是字符串数组的函数每次发送的是一组命令,用于和服务器的交互,这个函数主要是用于ESMTP服务器的验证的功能,因为验证的过程是一个等待然后又输入的过程,因此将他们放在一个数组中有利于理解和操作。而他们的实现主要是通过调用上面的发送SMTP命令函数SendCommand以及接受SMTP服务器响应的函数RecvResponse来实现的。具体的代码如下所示:,8) 邮件发送程序 SendMail 这是整个程序的核心部分。具体的实现SMTP协议的程序正是通过它一步一步实现并最终实现发送简单邮件甚至带附件的
13、邮件的功能。而它的实现是调用以上给出的各个函数的结果。以下就简单的通过几个SMTP命令的格式来实现,private bool SendEmail() /连接网络 try /建立一个TCP连接tc=new TcpClient(mailserver,mailserverport); catch MessageBox.Show (“连接失败“,“请确认“);return false; /获取当前流的资料ns = tc.GetStream(); SMTPCodeAdd(); /验证网络连接是否正确 if(RightCodeHTRecvResponse().Substring(0,3)=null) re
14、turn false; string SendBuffer;,if(ESmtp) SendBuffer=new String4; SendBuffer0=“EHLO “ + mailserver + enter; SendBuffer1=“AUTH LOGIN“ + enter; SendBuffer2=Base64Encode(username) + enter; SendBuffer3=Base64Encode(password) + enter; if(!Dialog(SendBuffer,“SMTP服务器验证失败,请核对用户名和密码。“) return false; else Send
15、Bufferstr=“HELO “ + mailserver + enter; if(!Dialog(SendBufferstr,“) return false; SendBufferstr=“MAIL FROM:“ + enter; if(!Dialog(SendBufferstr,“发件人地址错误,或不能为空“) return false; /把传过来的收件人的地址分割然后提交给服务器string split=“;“;,string address=Regex.Split (Recipient,split);SendBuffer=new string address.Length;for(
16、int i=0;i“ + enter;if(!Dialog(SendBuffer,“收件人地址有误“) return false; SendBufferstr=“DATA“ + enter; if(!Dialog(SendBufferstr,“) return false; SendBufferstr=“From:“ + FromName + “ +enter; SendBufferstr += enter + “.“ + enter; if(!Dialog(SendBufferstr,“错误信件信息“) return false; SendBufferstr=“QUIT“ + enter;
17、if(!Dialog(SendBufferstr,“断开连接时错误“) return false; /关闭流对象ns.Close(); /关闭连接tc.Close();FilePath=null;return true; ,42 AddExtra类,这个附加的小类只是提供一些返回当前系统时间,获取主机名,主机IP,有关帮助等小的功能,在此仅对帮助信息中的“关于”操作函数稍加说明。因为它说明了在C Sharp 中调用 Windows API 函数所需如下几个步骤: 421 调用Windows API 所需的命名空间 -using System.Runtime.InteropServices; 而
18、调用显示关于对话框的函数ShellAbout还需要用到两个命名空间如下所示 -using System.Reflection; -using System.Diagnostics ; 422 在程序中声明所需的API函数 DllImport(“shell32.dll“) static extern int ShellAbout(IntPtr hWnd, string szApp, string szOtherStuff,IntPtr hIcon);,423 在程序中具体的使用 Assembly ass=Assembly.GetExecutingAssembly(); FileVersionIn
19、fo myVersion=FileVersionInfo.GetVersionInfo(ass.Location ); ShellAbout(this.Handle ,“邮件收发系统#“,“版本“+myVersion.FileMajorPart +“.“+myVersion.FileMinorPart+“.“ +myVersion.CompanyName ,this.Icon .Handle ); 至此就完成了在C Sharp中调用 Windows API 函数的过程。 而在此程序中最主要的调用了两个WINDOWSAPI 函数,一个 就是动态的获取图标句柄的函数SHGetFileInfo()和
20、动态调用 相关联的打开指定程序的函数ShellExecute().下面章节将对这 两个函数进行完全的用法解析。,4.3 程序用到的主要API函数完全解析,SHGetFileInfo用法完全解析 一函数名:SHGetFileInfo 二作用:动态的获取图标的句柄 三在C#中的声明方式: DllImport(“shell32.dll”) Private static extern int ShGetFileInfo(string pszPath,uint dwAttributes, ref SHFILEINFO psi, uint cbFileInfo,uint flags); 四各个参数的解析:
21、 1.pszPath指定的文件名(可以是绝对的路径,也可以是相对的路径)。当uFlags的取值中不包含 SHGFI_PIDL时,可直接指定;否则 pszPath要通过计算获得,不能直接指定; 2dwFileAttributes文件属性。仅当uFlags的取值包含SHGFI_USEFILEATTRIBUTES时 有效,一般不用此参数。 3psfi返回获取的文件的信息。是一个结构体类型的变量。它的原型如下所示:,private struct SHFILEINFOpublic IntPtr hIcon;public int iIcon;public uint dwAttributes;Marshal
22、As(UnmanagedType.LPStr, SizeConst =256)public string szDisplayName;MarshalAs(UnmanagedType.LPStr, SizeConst = 80)public string szTypeName;各个参数代表的含义:1) hIcon文件的图标句柄。2) iIcon图标的系统索引号。3) dwAttributes文件的属性值。4) szDisplayName文件的显示名(名称的最大长度)。5) szTypeName文件的类型名(最大长度 默认是80个字符)。,4cbFileInfopsfi的字节数。 5uFlags指
23、明需要返回的文件信息标识符。 常用的是: SHGFI_ICON; /获得图标 SHGFI_DISPLAYNAME; /获得显示名 SHGFI_TYPENAME; /获得类型名 SHGFI_ATTRIBUTES;/获得属性 SHGFI_LARGEICON; /获得大图标 SHGFI_SMALLICON; /获得小图标 SHGFI_PIDL;/ pszPath是一个标识符五.补充说明 : 函数SHGetFileInfo()的返回值也随uFlags的取值变化而有所不同。通过调用SHGetFileInfo()可以由psfi参数得到文件的图标句柄,但要注意在uFlags参数中不使用SHGFI_PIDL时
24、,SHGetFileInfo()不能获得“我的电脑“等虚似文件夹的信息。,ShellExecute用法完全解析,一函数名:ShellExecute 二作用:ShellExecute不仅可以运行EXE文件,也可以运行已经关 联的文件。 三在C#中的声明: /EntryPoint 给出Dll入口点的名称,如果没有给出,则用方法本身的名称。 DllImport(“shell32.dll“,EntryPoint=“SHGetFileInfo“) private static extern int ShellExecute(IntPtr hwnd, string Operation,string Fil
25、eName, string Parameters, string Directory, int ShowCmd); 四各个参数解析:hWnd:用于指定父窗口句柄。当函数调用过程出现错误时,它将作为Windows消息窗口的父窗口。例如,可以将其设置为应用程序主窗口句柄,即Application.Handle,也可以将其设置为桌面窗口句柄(用GetDesktopWindow函数获得)。 Operation:用于指定要进行的操作。其中“open”操作表示执行由FileName参数指定的程序,或打开由FileName参数指定的文件或文件夹;“print”操作表示打印由FileName参数指定的文件;“
26、explore”操作表示浏览由FileName参数指定的文件夹。当参数设为nil时,表示执行默认操作“open”。,FileName:用于指定要打开的文件名、要执行的程序文件名或要浏览的文件夹名。 Parameters:若FileName参数是一个可执行程序,则此参数指定命令行参数,否则此参数应为nil或PChar(0)。 Directory:用于指定默认目录。 ShowCmd:若FileName参数是一个可执行程序,则此参数指定程序窗口的初始显示方式,否则此参数应设置为0。 若ShellExecute函数调用成功,则返回值为被执行程序的实例句柄。若返回值小于32,则表示出现错误。 五.补充说
27、明上述仅仅是ShellExecute函数的标准用法,5.软件运行界面,51 新建邮件帐号 用户打开软件之后,需要新建一个邮件帐号,在这个信件帐号的过程中,需要指定SMTP服务器,SMTP的端口,以及用于ESMTP验证的用户名和密码。指定这些发邮件的必须参数之后,再回到系统的主界面如下所示:,52 发送邮件界面 521 发送不带附件的邮件在新建帐号的过程中已经指定了邮件地址,和帐号名称,所以默认的以这些参数来发送邮件。通过调用参数的不同程序会自动的调用相对应的代码来执行不同的操作。发送简单的邮件运行界面如下。,522 发送带附件的邮件和简单的邮件不同之处在于多了发送附件的功能,软件模拟FoxMa
28、il里面发送邮件时,在程序的下面自动显示增添的附件的名称,以及图标等信息。并且邮件支持添加,删除,排列图标等功能。 运行界面如下所示:,53 验证邮件发送是否成功邮件发送出去之后,用FoxMail跟踪接受之后的,证明邮件和附件都可以正常接收,具体的FoxMail的接收界面如下所示:,6 结论,这次编写的邮件客户端系统,我负责的是邮件的发送的功能。在熟悉了专门用于发送邮件的SMTP协议以及RFC规定的邮件的格式的基础上,运用了微软新推出的C Sharp这一新型的面向对象语言的便利性和灵活性,从SMTP协议规定的底层命令做起,一步步的与服务器进行交互操作,最终实现发送多附件多接收人的功能。其中,具
29、体的和服务器的交互操作,都封装了在SmtpMail.dll这个动态链接库里面了。而为了方便最终的调用和整合,所有的有关后台操作发送邮件的类以及其他的附加功能的类,全部都归属于MailSend这个命名空间了。在力求达到FoxMail功能的同时,又加了一点个人的思想并把它体现到了这一软件上。最主要的体现就是新建帐号的提前检测这一特色上,这一功能类似于很多Web页面的“检测新帐号”的功能,这样就免去了用户一直到确定注册完成时,才因为帐户因为已经被使用而注册失败的麻烦。总之,通过这次的编程,使我对网络编程有了一个很好的认识和锻炼,也使我对C Sharp这一语言的掌握程度又上了一个新台阶,虽然编出来的软件不能和功能强大的FoxMail相提并论,但是相信它简单,易操作性,和FoxMail的很多强大但却“鸡肋”似的功能比较起来,更多了几分实用性。以后的日子,随着我技术的提高和思想的成熟,我一定会把它做的更好,更趋近于完美。,