1.Pop3Connection类:
这样,项目向导就完成了,接着我们将原来的Class1.cs改名为Pop3.cs,同时添加一个类Pop3Connection(文件名不妨为Pop3Connection.cs)。
如上所述,Pop3Connection类完成了与主机的连接、通讯和关闭连接等功能,所以我们必须调用.Net框架中进行网络通讯的类库,在此我们运用的是TcpClient类的对象作为网络连接的客户端。同时,在与主机的通讯过程中必然少不了对于输入输出流的控制。于是,我们在设计该类的时候,首先得添加如下命名空间:
using System.IO;using System.Net.Sockets;Pop3Connection类的成员变量包括以下几个:private TcpClient socket;private StreamReader reader;private StreamWriter writer;private bool connected;
其中,bool类型的connected变量用于判断是否与主机取得了连接,它是该类的一个属性,对其操作如下:
public bool Connected{ get{return connected;}}Pop3Connection类的主要方法包含以下几个:internal void Open(string host, int port){ if(host == null || host.Trim().Length == 0 || port = 0) { throw new System.ArgumentException("Invalid Argument found."); } socket.Connect(host, port); reader = new StreamReader(socket.GetStream(), System.Text.Encoding.ASCII); writer = new StreamWriter(socket.GetStream(), System.Text.Encoding.ASCII); connected = true;}internal void SendCommand(string cmd){ writer.WriteLine(cmd); writer.Flush();}internal void GetReply(out string reply, out int code){ reply = reader.ReadLine(); code = reply == null ? -1 : Int32.Parse(reply.Substring(0, 3));}internal void Close(){ reader.Close(); writer.Flush(); writer.Close(); reader = null; writer = null; socket.Close(); connected = false;}
根据这些方法的名称,我们不难知道它们的作用。第一个方法Open()就是根据主机名和端口号取得和服务器的连接。一旦连接成功,就通过TcpClient类的对象获取网络通讯流并新建一个StreamReader对象和一个StreamWriter对象。不言而喻,这两个对象的作用是控制网络通讯的输出和输入。最后,还要将connected的属性设置为true。第二个方法SendCommand()就是在上面的StreamWriter类的对象writer的基础上往网络套接字中输入信息。而第三个方法GetReply()则正好相反,它是用来从网络套接字中获取信息的。最后一个方法Close()的作用则是关闭输出、输入流的对象,然后调用TcpClient类的对象Close()方法并将connected属性设置为false,从而关闭连接,结束会话。
2.Pop3类:
这样,我们就完成了Pop3Connection类的设计和编码工作,也就完成了整个组件最关键的部分。接下来,我们就在该类的基础上设计Pop3类。Pop3类包含了邮件通讯所必须的基本属性、方法和事件。
首先,我们来设计其中的属性。该类应该包括主机名、端口号、用户名、密码、邮件数量、邮件总体积、邮件内容和状态信息等属性。其中前四个属性是可读又可写的,后四个属性是只可读的。具体的设置如下:
////// 主机名///public string Host{ get {return host;} set { if(value == null || value.Trim().Length == 0) { throw new ArgumentException("Invalid host name."); } host = value; }}////// 端口号///public int Port{ get {return port;} set { if(value = 0) { throw new ArgumentException("Invalid port."); } port = value; }}////// 用户名///public string UserName{ get {return username;} set { if(value == null || value.Trim().Length == 0) { throw new ArgumentException("Invalid user name."); } username = value; }}////// 密码///public string PassWord{ get {return password;} set { if(value == null) { throw new ArgumentException("Invalid password."); } password = value; }}////// 邮件数量///public int NumOfMails{ get {return numofmails;}}////// 邮件总体积///public double TotalSize{ get {return totalsize;}}////// 邮件内容///public string Body{ get {return body;}}////// 状态信息///public string Status{get {return status;}}
完成了该类的属性设计,我们接下来就完成该类的方法设计。该类主要的方法就一个ReceiveMessage(),顾名思义就是接收邮件信息的意思。在这个方法中,我们运用了上面的Pop3Connection类的对象。通过这个对象,我们就可以更加方便的进行网络通讯的操作。不过,在具体介绍这个方法的实现以前,我先得向大家介绍一下邮件接收的基本原理。
其基本原理如下:
一开始便是客户端与服务器的连接。不过,在客户端连接到服务器之前,注意把端口设为POP3协议默认的110号。
客户端连接服务器成功后,服务器会返回以下信息:
+OK
字符+OK是POP3协议的返回信息。它的回应信息不像SMTP协议那样用丰富多变的数字表示,只有两个:+OK或者-ERR。其中,+OK表示连接成功,而-ERR则表示连接失败。
接下来,客户端输入USER 用户名
该命令告诉服务器你的用户名。注意,有些服务器会区分大小写字母的。
服务器返回+OK后,客户端输入PASS 口令
服务器返回+OK后,还返回一些邮箱的统计信息,比如:+OK 1 message(s) [1304 byte(s)]
不同的服务器返回的信息格式不太一样,所以我们可以用STAT命令来查看邮箱的情况。STAT命令的回应中有两个数字,分别表示邮件的数量和邮件的大小。
如果信箱里有信,就可以用RETR命令来获取邮件的正文。RETR命令的格式为:
RETR 邮件编号
如果返回结果第一行是+OK信息,则表示成功。第二行起便是邮件的正文。最后一行和SMTP协议一样,是一个单独的英文句号,表示邮件的结尾部分。
把邮件存储起来后要用DELE命令删除邮箱中的邮件,否则原有的邮件会继续保留在服务器上,一旦邮件一多,你的邮箱就爆了。DELE命令的格式为:
DELE 邮件编号
如果删错了,可以用RSET命令来恢复所有已被删除的邮件。条件是你还没有退出,一旦退出,那就一切Bye Bye了。全部完成以后,输入QUIT命令就可以退出POP3服务器了。
在了解了邮件接收的基本原理的基础上,我就向大家介绍ReceiveMessage()方法的具体实现:
////// 接收邮件信息///public void ReceiveMessage(){ // 避免线程冲突 lock(this) { // 设置初始连接 con = new Pop3Connection(); if(port = 0) port = 110; con.Open(host, port); StringBuilder buf = new StringBuilder(); string response; int code; // 获取欢迎信息 con.GetReply(out response, out code); status += response; //登录服务器过程 buf.Append("USER"); buf.Append(username); buf.Append(CRLF); con.SendCommand(buf.ToString()); con.GetReply(out response, out code); status += response; buf.Length = 0; buf.Append("PASS"); buf.Append(password); buf.Append(CRLF); con.SendCommand(buf.ToString()); con.GetReply(out response, out code); status += response; //向服务器发送STAT命令,从而取得邮箱的相关信息:邮件数量和大小 buf.Length = 0; buf.Append("STAT"); buf.Append(CRLF); con.SendCommand(buf.ToString()); con.GetReply(out response, out code); status += response; //将总邮件数和邮件大小分离 string[] TotalStat = response.Split(new char[] {' '}); numofmails = Int32.Parse(TotalStat[1]); totalsize = (double)Int32.Parse(TotalStat[2]); for( int x = 0; x numofmails; ++x) { //根据邮件编号从服务器获得相应邮件 buf.Length = 0; buf.Append("RETR"); buf.Append(x.ToString()); buf.Append(CRLF); con.SendCommand(buf.ToString()); con.GetReply(out response, out code); if(response[0]!='-') { //不断地读取邮件内容,只到结束标志:英文句号 while(response!=".") { body += response; con.GetReply(out response, out code); } } else status += response; } //向服务器发送QUIT命令从而结束和POP3服务器的会话 buf.Length = 0; buf.Append("QUIT"); buf.Append(CRLF); con.SendCommand(buf.ToString()); con.GetReply(out response, out code); status += response; con.Close(); // 邮件接收成功后触发的事件 if(OnMailReceived != null) { OnMailReceived(); } }}
根据邮件接收的基本原理和代码中的注释,我想读者应该不难读懂上面的代码。不过下面几点仍得说明:其中的CRLF为"rn",它的作用是在每个命令后面加上一个换行符。另外,在该方法的一开始处有一句:lock(this),它的作用是避免线程冲突。考虑到接收邮件的过程比较漫长而且占用的资源较多,所以在设计的时候我用到了多线程(有关多线程的资料读者可参考相关的书籍或文章,此处不再赘述)。在实际的程序中,上面的方法其实被另一个方法ReceiveMessageAsync()作为一个单独的线程调用。方法如下:
////// 通过一个独立的线程接收邮件///public void ReceiveMessageAsync(){ new Thread(new ThreadStart(ReceiveMessage)).Start();}
最后,在ReceiveMessge()方法的末尾处,它调用了事件处理函数OnMailReceived()。在C#中,事件的声明是用代表完成的,首先我们在Pop3类的开始处进行声明如下:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)
public delegate void MailReceivedDelegate();
接着,就进行事件的声明:
public event MailReceivedDelegate OnMailReceived;
这样,只要在应用程序中调用了OnMailReceived()事件,在邮件接收成功后,OnMailReceived()事件就会被触发。
到此为止,我们已经完成了核心类-Pop3类的属性、方法和事件的设计,这样整个组件也就完成了(按Ctrl+Shift+B就可以生成解决方案)。不过,由于是组件,所以不可以直接运行,我们必须做一个测试程序来测试之。下面我就用该组件做了一个简单的邮件信史,它可以向用户报告邮箱中的新邮件数目。
四.测试程序
首先,在原来的解决方案的基础上添加一个新项目。项目类型为"Visual C#项目",模板为"Windows应用程序",名称不妨为"MailNotifier"。
接着,设计主界面如下:
设计好主界面后,我们进行代码设计。首先,要添加对上面的组件-Pop3Com的引用。在项目菜单下选择"添加引用",出现"添加引用"对话框,在"项目"一页下将Pop3Com组件添加到本项目中。图示如下:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)同时,在代码的开始处添加引用:using Pop3Com。这样,我们就可以在本程序中调用Pop3Com组件中的类的方法完成相应功能了。下面就是"开始检查"按钮的事件处理函数了:
private void checkBtn_Click(object sender, System.EventArgs e){ // 正确性检查 if(host == null || host.Text.Trim().Length == 0) { MessageBox.Show("请填入服务器地址!"); } else if(username == null || username.Text.Trim().Length == 0) { MessageBox.Show("请填入用户名!"); } else if(password == null || password.Text.Trim().Length == 0) { MessageBox.Show("请填入密码!"); } else { mailer = new Pop3(); mailer.Host = host.Text; mailer.Port = Int32.Parse(port.Text); mailer.UserName = username.Text; mailer.PassWord = password.Text; statusBar.Text = "正在接收信息"; mailer.OnMailReceived += new Pop3.MailReceivedDelegate(OnMailReceived); mailer.ReceiveMessageAsync(); }}
其中,mailer为Pop3类的一个实例对象,它是完成邮件检查的核心对象。同时,OnMailReceived()事件函数如下:
private void OnMailReceived(){ statusBar.Text = "邮件接收完毕!"; MessageBox.Show("你有" + mailer.NumOfMails.ToString() + "个邮件!","信息", MessageBoxButtons.OK,MessageBoxIcon.Information);}
如此,测试程序-邮件信史也就完成了。最后,按下Ctrl+F5运行我们的程序如下:
五.小结:
通过对Pop3Com组件的设计,我想读者对Visual C#下的组件编程应该有了个基本的了解,对其中类的属性、方法和事件的设计也应该是相当清楚了。组件编程是Visual C#的强项,所以希望读者能进一步学习。同时,对于上面的组件,读者也可试着进一步完善,并不妨将之运用于自己的应用程序中,让它发挥其强大的重用功能。