点击这里给我发消息 点击这里给我发消息

C# 网络编程系列十:实现简单的邮件收发器

添加时间:2013-12-7
    相关阅读: 软件 技术 网络 程序 Access Windows C#
 

引言:

在我们的平常工作中,邮件的发送和接收应该是我们经常要使用到的功能的。因此知道电子邮件的应用程序的原理也是非常有必要的,在这一个专题中将介绍电子邮件应用程序的原理、电子邮件应用程序中涉及的协议和实现一个简答的电子邮件收发器程序。

一、邮件应用程序基本知识

1.1 电子邮件原理及相关协议

说到电子邮件的原理,其实和我们现实生活中寄邮件和寄包裹是一样的原理的。就让我们先回顾下现实生活中寄邮件的流程吧——首先,我们先写好信,信封上面写好收信人的地址,写信人的地址,然后把信放到寄信箱中,然后邮局的人会某个时候去这个信箱中的信取出来,然后邮局的人根据信封上写的收信人地址进行转发到当地的邮局,当地邮局然后把信寄到收信人的信箱中(寄包裹的话可能会电话联系,像我们在淘宝,京东买的东西的,收货人就是通过电话联系一样),最后收信人会到自己的信箱中收取信件。上面大致是我们平时生活中寄信的一个流程的。前面已经讲过电子邮件的原理和这个差不多的,下面就介绍了本专题中电子邮件的原理,大家可以和现实生活中的寄信过程进行对比下的,这样可以更加容易理解和掌握:

我们通过电子邮件应用(例如 基于客户端的Outlook电子邮件软件 和一些基于Web的电子邮件系统——新浪邮箱、谷歌邮箱、QQ邮箱等都属于电子邮件应用)将一封写好的邮件(相当于现实生活中的信,当然邮件也要写明收件人地址,邮件内容等信息的)通过电子邮件协议(SMTP,在后面的电子邮件相关协议中会介绍)发送到SMTP服务器(就是存储邮件的地方,相当于生活中的邮局一样),然后SMTP服务器根据收件人的地址通过SMTP协议转发到相应SMTP接收服务器上,(SMTP服务器进行转发相当于现实生活中邮局的人配送信的过程,配送到收件人当地的邮局,然而现实生活中邮局都是一家,所以可以相互识别——意思就是发送到当地邮局,当地邮局会接收,并且帮助你发送到指定人的信箱中,在网上上就是通过SMTP协议来规定这样的一个过程的,发送到别人的SMTP服务器上别人的服务器必须要认识发送来的邮件并接收)结束,接收端邮件服务器(POP3服务器)把邮件存放到接受者的电子信箱内(相当于当地邮局的人把信放到收信人的邮箱中),最后收件人可以登录自己的电子信箱,再与POP3服务器进行连接,从POP3服务器上下载发送来的邮件,这样在收件人的电子信箱中就可以看到发送来的电子邮件了(这就是现实生活中收信人从自己的信箱中取信的一个过程)。

注:括号中都是个人的理解,如果有什么不对的地方还望大家指出来,我好及时更正。

上面已经把电子邮件的原理和现实生活中寄信的过程进行对比,相信大家可以更加清楚电子邮件的原理和发送接收过程的,其实网络上的很多应用都可以以现实生活的例子去理解,这样的话我认为可以加深对知识的理解。下面就介绍下电子邮件中的相关协议的内容:

网络上的应用的核心就是协议,因为协议让网络上的客户端相互认识发生来的数据,所以电子邮件应用也不例外,也有相关的电子邮件协议来完成发送电子邮件和接收电子邮件的过程,这些协议主要是:SMTP(简单邮件传输协议,Simple Mail Transfer Protocol)、POP3(邮局协议,Post Office Protocol)和IMAP(网络邮件访问协议,Internet Message Access Protocol)。

? SMTP——SMTP 主要负责将邮件从一台机器转发至另一台机器(可以对照上面电子邮件的过程来理解SMTP的作用)
? POP3——3表示POP协议的版本,主要负责将邮件从邮箱中(POP3服务器)传输到本地计算机。
? IMAP——现在常用的版本为第四版本,即IMAP4,主要负责邮件的检索和处理功能,客户端不需要下载邮件到本地计算机,可直接从邮件客户端软件对服务器上的信件和文                      件目录进行操作,它是POP3的替代协议的。
1.2 邮件系统的分类

邮件系统主要分为两类的——基于客户端的邮件系统和基于Web浏览器的邮件系统。Office OutLook就是基于客户端的邮件客户端系统,而像我们经常使用的QQ邮箱、新浪、网易邮箱等都是属于基于Web浏览器的邮件系统,基于客户端的邮件系统的收发过程,通过下面的图片来描述(图片从网上摘下的):

图 1.1 基于客户端的邮件收发过程

发送方通过邮件客户端,将编辑好的邮件向邮件服务(SMTP服务器,在发送过程中也叫发送端邮件服务器)发送,发送端邮件服务器根据收件人的地址来识别接收端邮件服务器(POP3服务器),然后向POP3服务器发送邮件信息,接收端邮件服务器将邮件存放在接收者的电子信箱中,并告知接收者有新邮件,接收者通过邮件客户端与POP3服务器连接后,就可以查看新邮件。

然而,基于Web浏览器的邮件系统与基于客户端的邮件系统不同的地方有:

?基于Web浏览器邮件系统用户代理(代理的概念也就是用户不是直接与服务器进行通信,而是通过代理的方式,让代理去与服务器通信,然后用户在从代理中获的服务器的信息,代理也就是中间人的作用,相当于生活中中介,在.net中很多技术都用到了代理,例如委托的概念其实也就是代理的一个概念的)是Web浏览器,基于客户端的邮件系统而是邮件客户端应用程序,一般是Windows Form程序。
?浏览器发送邮件到SMTP服务器和从POP3服务器中获得邮件的方式都是通过HTTP协议来实现,与基于客户端的邮件系统不同(基于客户端的邮件系统发送通过SMTP协议或ESMTP(Extended SMTP),获得通过POP3或IMAP协议)。

1.3 目前主要的电子邮件服务系统

电子邮件服务系统——就是向大家提供邮箱服务的服务系统,这样的系统当然是由专门的公司进行研发的,我们一般叫这样的公司为邮件服务商,我们平常使用的网易邮箱,新、Gmail邮箱等都是建立在电子邮件服务系统(这里我的理解是——我们使用的新浪,网易等邮箱相当于现实生活中每个人的信箱,通过信箱可以获得邮局来的信,同样道理通过邮箱可以获得邮件服务系统的邮件,这样电子邮件系统相当于邮局) 。现在主要电子邮件服务系统主要有下面几种:
 
?基于Postfix/Qmail的邮件系统。例如,雅虎邮箱基于Qmail系统
?微软Exchange 邮件系统
?IBM Lotus Domino邮件系统
?Scalix邮件系统
?Zimbra邮件系统
?MDeamon邮件系统

二、.Net 平台对邮件发送功能的支持

在.NET类库中,在System.Net.Mail命名空间下定义了对邮件处理的类,这样使邮件的发送更加方便(这些类也就是对SMTP协议的封装,使我们更好地区编程,只需要使用类中的方法和属性等去完成邮件的发送,避免写复杂的SMTP协议的命令),下面是一张在System.Net.Mail命名空间下对邮件发送的支持的类截图:

从图片中类的名字中也可以看出每个类的作用的,在这里我就不一个介绍的, 大家可以参考MSDN去看每个类的使用,并且我在后面程序的实现部分也会有详细的注释去介绍程序中使用到类的使用。从图中还可以i看出一点——就是只有SMTP的字样,却没有POP3这样的字样的,这说明.Net类库本身中并没有提供对POP3协议的封装类,但是我们可以使用Jmail组件来完成从POP3服务器中收取邮件的功能,具体的使用将在后面的邮件收发器程序中邮件的接收部分介绍的。

 

三、邮件收发器程序的实现

3.1 邮件发送功能的实现

3.1.1 SMTP协议

SMTP 协议是用于电子邮件的传输的协议,电子邮件是通过SMTP服务器进行发送的,SMTP服务器的默认端口为25,通常发送邮件有两种方式——一种是不使用客户端认证,即客户端可以使用匿名发送邮件(这种方式叫做SMTP);另一种是客户端必须提供用户名和密码认证(这种方式叫做ESMTP,Extended SMTP)目前大部分邮件服务器采用用户名和密码认证的方式。

客户端发送邮件过程为——先通过客户端软件(本程序中的邮件收发器)将邮件发送到SMTP服务器,然后再由SMTP服务器发送到目标SMTP服务器。下面介绍SMTP协议的内容:

SMTP协议总共定义了14个命令,命令由命令码和气候的参数域组成, 不区别大小写的(通过前面专题的讲述可以得出各个协议的命令组成都差不多的),下面就简单介绍下5个常用的命令码

 

电子邮件由信封、首部、正文和结束符号4部分组成,下面就具体介绍下这4个部分的内容:

1. 信封

信封包括发信人的邮件地址和接收人的邮件地址,具体对应两条SMTP命令——Mail from:

发送密码命令: PASS ******(这两个命令都在代码中有给出的,大家可以参考代码来理解邮件的接收过程)

(2) 操作状态——如果客户端提供了正确的用户名和密码,则授权状态也就通过了,就相当于打开了在服务器上自己的邮箱,现在用户就有权限进去下载,查看和删除邮件等操作的,然后在现实生活中的取邮件和删除邮件都很简单(只要打开了邮箱门,用手去拿就可以了),然后在网络应用上,这些操作都需要发送POP3命令给服务器,服务器接收到命令后再给出响应。操作中常用的命令有:

? STAT 命令——该命令从服务器中获取邮件总数和总字节数,服务器响应命令返回邮件总数和总字节数
 如:

?客户端发送POP3命令: STAT
服务器响应命令: +OK 2 1340<BR>服务器响应命令:

?LIST 命令——该命令从服务器中获得邮件列表和大小,服务器响应命令返回列出邮件列表和大小。  
如:

?客户端发送POP3命令:LIST
服务器响应命令: +OK 2 message(1430 octect)
服务器响应命令:1    700
服务器响应命令:2    730
服务器响应命令:<一个空行>
?RETR 命令—— 该命令从服务器中获得一个邮件,格式为 RETR <邮件编号>
如:

?客户端发送POP3命令:RETR 1
服务器响应命令: 700 octets
服务器响应命令:<邮件头和内容>
服务器响应命令: <空行>
?DELETE 命令——该命令告诉服务器将邮件标记为删除。(此时只是逻辑删除)

(3)更新状态——客户端发送QUIT命令后,此时就进入更新状态,POP3服务器释放在操作状态中取得的资源,并将逻辑删除的邮件进行物理删除,然后关闭与客户端的TCP连接。这样整个邮件处理的过程就结束了。                            

3.2.3 接收功能的实现代码

有了前面接收邮件过程的介绍,再参考代码的实现,相信大家可以更好的理解客户端从POP3服务器中获取邮件的过程的,由于.net类库并没有帮我们封装POP3协议的实现类,要实现邮件的获取可以采用发送命令的方式,也可以使用Jmail组件,这个组件其实就是POP3协议的封装类,既然微软没有帮我们做,其他公司帮我们做好后来帮助我们简单的实现邮件的接收的一个类库罢了。然后在使用这个组件的过程中出现了好几个问题的,在源码中我都解释,大家可以下载源代码后查看的。

实现邮件接收的核心代码如下:

 // 登录邮箱(这里是本程序——邮件收发器)
        private void btnLogin_Click_1(object sender, EventArgs e)
        {
            // 与POP3服务器建立TCP连接
            // 建立连接后把服务器上的邮件下载到本地
            // 设置当前界面的光标为等待光标(就是我们看到的一个动的圆形)
            Cursor.Current = Cursors.WaitCursor;

            lsttbxStatus.Items.Clear();
            try
            {
                // POP3服务器通过监听TCP110端口来提供POP3服务的
                // 向POP3服务器发出tcp请求
                tcpClient = new TcpClient(tbxPOP3Server.Text, 110);
                lsttbxStatus.Items.Add("正在连接...");
            }
            catch
            {
                MessageBox.Show("连接失败", "错误", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
                lsttbxStatus.Items.Add("连接失败!");
                return;
            }

            // 连接成功的情况
            networkStream = tcpClient.GetStream();
            streamReader = new StreamReader(networkStream, Encoding.Default);
            streamWriter = new StreamWriter(networkStream, Encoding.Default);
            streamWriter.AutoFlush = true;
            string str;
            // 读取服务器返回的响应连接信息
            str = GetResponse();
            if (CheckResponse(str) == false)
            {
                lsttbxStatus.Items.Add("服务器拒接了连接请求");
                return;
            }
            // 如果服务器接收请求
            // 向服务器发送凭证——用户名和密码

            // 向服务器发送用户名,请求确认
            lsttbxStatus.Items.Add("核实用户名阶段...");
            SendToServer("USER " + tbxUserMail.Text);
            str = GetResponse();
            if (CheckResponse(str) == false)
            {
                lsttbxStatus.Items.Add("用户名错误.");
                return;
            }

            // 用户名审核通过后再发送密码等待确认
            // 向服务器发送密码,请求确认
            SendToServer("PASS "+txbPassword.Text);
            str = GetResponse();
            if (CheckResponse(str) == false)
            {
                lsttbxStatus.Items.Add("密码错误!");
                return;
            }

            lsttbxStatus.Items.Add("身份验证成功,可以开始会话");
            // 向服务器发送LIST 命令,请求获得邮件列表和大小
            lsttbxStatus.Items.Add("获取邮件....");
            SendToServer("LIST");
            str = GetResponse();
            if (CheckResponse(str) == false)
            {
                lsttbxStatus.Items.Add("获取邮件列表失败");
                return;
            }

            lsttbxStatus.Items.Add("邮件获取成功");

            // 窗口控件控制
            tabControlMyMailbox.Enabled = true;
            btnReadMail.Enabled = false;
            btnDownLoad.Enabled = false;
            btnDeleteMail.Enabled = false;

            // 登陆成功后实例化邮件发送对象,以便后面完成发送邮件的操作
            // 实例化邮件发送类(SmtpClient)对象
            if (smtpClient == null)
            {
                smtpClient = new SmtpClient();
                smtpClient.Host = tbxSmtpServer.Text;
                smtpClient.Port = 25;
                   
                // 不使用默认凭证,即需要认证登陆
                smtpClient.UseDefaultCredentials = false;
                smtpClient.Credentials = new NetworkCredential(tbxUserMail.Text, txbPassword.Text);
                smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
            }

            // 登陆成功后,自动接收新邮件
            // 开始接收邮件
            try
            {
                btnRefreshMailList.PerformClick();
            }
            catch
            {
                MessageBox.Show("读取邮件列表失败!", "错误", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
            }

            lsttbxStatus.Items.Add("登陆成功!");
            lsttbxStatus.TopIndex = lsttbxStatus.Items.Count - 1;
            Cursor.Current = Cursors.Default;

            // 窗口控件控制
            richtbxMailContentReview.Enabled = true;
            tbxUserMail.Enabled = false;
            txbPassword.Enabled = false;
            btnLogin.Enabled = false;
            btnLogout.Enabled = true;
            tbxSmtpServer.Enabled = false;
            tbxPOP3Server.Enabled = false;
            btnReadMail.Enabled = true;
            btnDownLoad.Enabled = true;
            btnDeleteMail.Enabled = true;
            tabControlMyMailbox.Focus();
        }

        #region 处理与POP3服务器交互事件
        // 获取服务器响应的信息
        private string GetResponse()
        {
            string str = null;
            try
            {
                str = streamReader.ReadLine();
                if (str == null)
                {
                    lsttbxStatus.Items.Add("连接失败——服务器没有响应");
                }
                else
                {
                    lsttbxStatus.Items.Add("收到:[" + str + "]");
                    if (str.StartsWith("-ERR"))
                    {
                        str = null;
                    }
                }
            }
            catch(Exception err)
            {
                lsttbxStatus.Items.Add("连接失败:[" + err.Message + "]");
            }

            return str;
        }

        // 检查响应信息
        private bool CheckResponse(string responseString)
        {
            if (responseString == null)
            {
                return false;
            }
            else
            {
                if (responseString.StartsWith("+OK"))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        // 向服务器发送命令
        private bool SendToServer(string str)
        {
            try
            {
                // 这里必须使用WriteLine方法的,因为POP3协议中定义的命令是以回车换行结束的
                // 如果客户端发送的命令没有以回车换行结束,POP3服务器就不能识别,也就不能响应客户端的请求了
                // 如果想用Write方法,则str输入的参数字符中必须包含“\r\n”,也就是回车换行字符串。
                streamWriter.WriteLine(str);
                streamWriter.Flush();
                lsttbxStatus.Items.Add("发送:[" + str + "]");
                return true;
            }
            catch(Exception ex)
            {
                lsttbxStatus.Items.Add("发送失败:[" + ex.Message + "]");
                return false;
            }
        }

        #endregion

3.3 程序运行结果演示

首先输入邮箱名和密码登录到POP3服务器来获取邮件列表的演示:

然后在邮件列表中选中一个邮件进行阅读,然后进行回复邮件的操作演示(邮件的发送都可以附加附件发送出去):

阅读邮件的界面:

回复邮件的界面:

同时点击发送按钮后,就可以把邮件发送到sina的SMTP服务器上,再由新浪的SMTP服务器转发到QQ的SMTP服务器,QQ的POP3服务器中QQ的SMTP服务器获取收到的邮件,当QQ用户输入正确的邮箱名和密码后就可以从QQ的POP3服务器上获取收到的邮件。

点击发送按钮后成功发送邮件的图片:

四、总结

介绍到这里,本专题的内容就已经介绍完了,希望通过本专题可以让大家明白邮件发送和接收的原理,并且可以自定义一个简单邮件收发器的功能的,在后面一专题将介绍FTP协议(文件传输协议),并实现一个简单的文件上传和下载的程序。

咨询热线:020-85648757 85648755 85648616 0755-27912581 客服:020-85648756 0755-27912581 业务传真:020-32579052
广州市网景网络科技有限公司 Copyright◎2003-2008 Veelink.com. All Rights Reserved.
广州商务地址:广东省广州市黄埔大道中203号(海景园区)海景花园C栋501室
= 深圳商务地址:深圳市宝源路华丰宝源大厦606
研发中心:广东广州市天河软件园海景园区 粤ICP备05103322号 工商注册