C#網(wǎng)絡(luò)編程系列十:實(shí)現(xiàn)簡(jiǎn)單的郵件收發(fā)器
引言:在我們的平常工作中,郵件的發(fā)送和接收應(yīng)該是我們經(jīng)常要使用到的功能的。因此知道電子郵件的應(yīng)用程序的原理也是非常有必要的,在這一個(gè)專題中將介紹電子郵件應(yīng)用程序的原理、電子郵件應(yīng)用程序中涉及的協(xié)議和實(shí)現(xiàn)一個(gè)簡(jiǎn)答的電子郵件收發(fā)器程序。
一、郵件應(yīng)用程序基本知識(shí)
1.1 電子郵件原理及相關(guān)協(xié)議
說到電子郵件的原理,其實(shí)和我們現(xiàn)實(shí)生活中寄郵件和寄包裹是一樣的原理的。就讓我們先回顧下現(xiàn)實(shí)生活中寄郵件的流程吧——首先,我們先寫好信,信封上面寫好收信人的地址,寫信人的地址,然后把信放到寄信箱中,然后郵局的人會(huì)某個(gè)時(shí)候去這個(gè)信箱中的信取出來,然后郵局的人根據(jù)信封上寫的收信人地址進(jìn)行轉(zhuǎn)發(fā)到當(dāng)?shù)氐泥]局,當(dāng)?shù)剜]局然后把信寄到收信人的信箱中(寄包裹的話可能會(huì)電話聯(lián)系,像我們?cè)谔詫殻〇|買的東西的,收貨人就是通過電話聯(lián)系一樣),最后收信人會(huì)到自己的信箱中收取信件。上面大致是我們平時(shí)生活中寄信的一個(gè)流程的。前面已經(jīng)講過電子郵件的原理和這個(gè)差不多的,下面就介紹了本專題中電子郵件的原理,大家可以和現(xiàn)實(shí)生活中的寄信過程進(jìn)行對(duì)比下的,這樣可以更加容易理解和掌握:
我們通過電子郵件應(yīng)用(例如 基于客戶端的Outlook電子郵件軟件 和一些基于Web的電子郵件系統(tǒng)——新浪郵箱、谷歌郵箱、QQ郵箱等都屬于電子郵件應(yīng)用)將一封寫好的郵件(相當(dāng)于現(xiàn)實(shí)生活中的信,當(dāng)然郵件也要寫明收件人地址,郵件內(nèi)容等信息的)通過電子郵件協(xié)議(SMTP,在后面的電子郵件相關(guān)協(xié)議中會(huì)介紹)發(fā)送到SMTP服務(wù)器(就是存儲(chǔ)郵件的地方,相當(dāng)于生活中的郵局一樣),然后SMTP服務(wù)器根據(jù)收件人的地址通過SMTP協(xié)議轉(zhuǎn)發(fā)到相應(yīng)SMTP接收服務(wù)器上,(SMTP服務(wù)器進(jìn)行轉(zhuǎn)發(fā)相當(dāng)于現(xiàn)實(shí)生活中郵局的人配送信的過程,配送到收件人當(dāng)?shù)氐泥]局,然而現(xiàn)實(shí)生活中郵局都是一家,所以可以相互識(shí)別——意思就是發(fā)送到當(dāng)?shù)剜]局,當(dāng)?shù)剜]局會(huì)接收,并且?guī)椭惆l(fā)送到指定人的信箱中,在網(wǎng)上上就是通過SMTP協(xié)議來規(guī)定這樣的一個(gè)過程的,發(fā)送到別人的SMTP服務(wù)器上別人的服務(wù)器必須要認(rèn)識(shí)發(fā)送來的郵件并接收)結(jié)束,接收端郵件服務(wù)器(POP3服務(wù)器)把郵件存放到接受者的電子信箱內(nèi)(相當(dāng)于當(dāng)?shù)剜]局的人把信放到收信人的郵箱中),最后收件人可以登錄自己的電子信箱,再與POP3服務(wù)器進(jìn)行連接,從POP3服務(wù)器上下載發(fā)送來的郵件,這樣在收件人的電子信箱中就可以看到發(fā)送來的電子郵件了(這就是現(xiàn)實(shí)生活中收信人從自己的信箱中取信的一個(gè)過程)。
注:括號(hào)中都是個(gè)人的理解,如果有什么不對(duì)的地方還望大家指出來,我好及時(shí)更正。
上面已經(jīng)把電子郵件的原理和現(xiàn)實(shí)生活中寄信的過程進(jìn)行對(duì)比,相信大家可以更加清楚電子郵件的原理和發(fā)送接收過程的,其實(shí)網(wǎng)絡(luò)上的很多應(yīng)用都可以以現(xiàn)實(shí)生活的例子去理解,這樣的話我認(rèn)為可以加深對(duì)知識(shí)的理解。下面就介紹下電子郵件中的相關(guān)協(xié)議的內(nèi)容:
網(wǎng)絡(luò)上的應(yīng)用的核心就是協(xié)議,因?yàn)閰f(xié)議讓網(wǎng)絡(luò)上的客戶端相互認(rèn)識(shí)發(fā)生來的數(shù)據(jù),所以電子郵件應(yīng)用也不例外,也有相關(guān)的電子郵件協(xié)議來完成發(fā)送電子郵件和接收電子郵件的過程,這些協(xié)議主要是:SMTP(簡(jiǎn)單郵件傳輸協(xié)議,Simple Mail Transfer Protocol)、POP3(郵局協(xié)議,Post Office Protocol)和IMAP(網(wǎng)絡(luò)郵件訪問協(xié)議,Internet Message Access Protocol)。
SMTP——SMTP 主要負(fù)責(zé)將郵件從一臺(tái)機(jī)器轉(zhuǎn)發(fā)至另一臺(tái)機(jī)器(可以對(duì)照上面電子郵件的過程來理解SMTP的作用)
POP3——3表示POP協(xié)議的版本,主要負(fù)責(zé)將郵件從郵箱中(POP3服務(wù)器)傳輸?shù)奖镜赜?jì)算機(jī)。
IMAP——現(xiàn)在常用的版本為第四版本,即IMAP4,主要負(fù)責(zé)郵件的檢索和處理功能,客戶端不需要下載郵件到本地計(jì)算機(jī),可直接從郵件客戶端軟件對(duì)服務(wù)器上的信件和文件目錄進(jìn)行操作,它是POP3的替代協(xié)議的。
1.2 郵件系統(tǒng)的分類
郵件系統(tǒng)主要分為兩類的——基于客戶端的郵件系統(tǒng)和基于Web瀏覽器的郵件系統(tǒng)。Office OutLook就是基于客戶端的郵件客戶端系統(tǒng),而像我們經(jīng)常使用的QQ郵箱、新浪、網(wǎng)易郵箱等都是屬于基于Web瀏覽器的郵件系統(tǒng),基于客戶端的郵件系統(tǒng)的收發(fā)過程,通過下面的圖片來描述(圖片從網(wǎng)上摘下的):
圖 1.1 基于客戶端的郵件收發(fā)過程
發(fā)送方通過郵件客戶端,將編輯好的郵件向郵件服務(wù)(SMTP服務(wù)器,在發(fā)送過程中也叫發(fā)送端郵件服務(wù)器)發(fā)送,發(fā)送端郵件服務(wù)器根據(jù)收件人的地址來識(shí)別接收端郵件服務(wù)器(POP3服務(wù)器),然后向POP3服務(wù)器發(fā)送郵件信息,接收端郵件服務(wù)器將郵件存放在接收者的電子信箱中,并告知接收者有新郵件,接收者通過郵件客戶端與POP3服務(wù)器連接后,就可以查看新郵件。
然而,基于Web瀏覽器的郵件系統(tǒng)與基于客戶端的郵件系統(tǒng)不同的地方有:
基于Web瀏覽器郵件系統(tǒng)用戶代理(代理的概念也就是用戶不是直接與服務(wù)器進(jìn)行通信,而是通過代理的方式,讓代理去與服務(wù)器通信,然后用戶在從代理中獲的服務(wù)器的信息,代理也就是中間人的作用,相當(dāng)于生活中中介,在.net中很多技術(shù)都用到了代理,例如委托的概念其實(shí)也就是代理的一個(gè)概念的)是Web瀏覽器,基于客戶端的郵件系統(tǒng)而是郵件客戶端應(yīng)用程序,一般是Windows Form程序。
瀏覽器發(fā)送郵件到SMTP服務(wù)器和從POP3服務(wù)器中獲得郵件的方式都是通過HTTP協(xié)議來實(shí)現(xiàn),與基于客戶端的郵件系統(tǒng)不同(基于客戶端的郵件系統(tǒng)發(fā)送通過SMTP協(xié)議或ESMTP(Extended SMTP),獲得通過POP3或IMAP協(xié)議)。
1.3 目前主要的電子郵件服務(wù)系統(tǒng)
電子郵件服務(wù)系統(tǒng)——就是向大家提供郵箱服務(wù)的服務(wù)系統(tǒng),這樣的系統(tǒng)當(dāng)然是由專門的公司進(jìn)行研發(fā)的,我們一般叫這樣的公司為郵件服務(wù)商,我們平常使用的網(wǎng)易郵箱,新、Gmail郵箱等都是建立在電子郵件服務(wù)系統(tǒng)(這里我的理解是——我們使用的新浪,網(wǎng)易等郵箱相當(dāng)于現(xiàn)實(shí)生活中每個(gè)人的信箱,通過信箱可以獲得郵局來的信,同樣道理通過郵箱可以獲得郵件服務(wù)系統(tǒng)的郵件,這樣電子郵件系統(tǒng)相當(dāng)于郵局) ?,F(xiàn)在主要電子郵件服務(wù)系統(tǒng)主要有下面幾種:
基于Postfix/Qmail的郵件系統(tǒng)。例如,雅虎郵箱基于Qmail系統(tǒng)
微軟Exchange 郵件系統(tǒng)
IBM Lotus Domino郵件系統(tǒng)
Scalix郵件系統(tǒng)
Zimbra郵件系統(tǒng)
MDeamon郵件系統(tǒng)
二、.Net 平臺(tái)對(duì)郵件發(fā)送功能的支持
在.NET類庫中,在System.Net.Mail命名空間下定義了對(duì)郵件處理的類,這樣使郵件的發(fā)送更加方便(這些類也就是對(duì)SMTP協(xié)議的封裝,使我們更好地區(qū)編程,只需要使用類中的方法和屬性等去完成郵件的發(fā)送,避免寫復(fù)雜的SMTP協(xié)議的命令),下面是一張?jiān)赟ystem.Net.Mail命名空間下對(duì)郵件發(fā)送的支持的類截圖:
從圖片中類的名字中也可以看出每個(gè)類的作用的,在這里我就不一個(gè)介紹的, 大家可以參考MSDN去看每個(gè)類的使用,并且我在后面程序的實(shí)現(xiàn)部分也會(huì)有詳細(xì)的注釋去介紹程序中使用到類的使用。從圖中還可以i看出一點(diǎn)——就是只有SMTP的字樣,卻沒有POP3這樣的字樣的,這說明.Net類庫本身中并沒有提供對(duì)POP3協(xié)議的封裝類,但是我們可以使用Jmail組件來完成從POP3服務(wù)器中收取郵件的功能,具體的使用將在后面的郵件收發(fā)器程序中郵件的接收部分介紹的。
三、郵件收發(fā)器程序的實(shí)現(xiàn)
3.1 郵件發(fā)送功能的實(shí)現(xiàn)
3.1.1 SMTP協(xié)議
SMTP 協(xié)議是用于電子郵件的傳輸?shù)膮f(xié)議,電子郵件是通過SMTP服務(wù)器進(jìn)行發(fā)送的,SMTP服務(wù)器的默認(rèn)端口為25,通常發(fā)送郵件有兩種方式——一種是不使用客戶端認(rèn)證,即客戶端可以使用匿名發(fā)送郵件(這種方式叫做SMTP);另一種是客戶端必須提供用戶名和密碼認(rèn)證(這種方式叫做ESMTP,Extended SMTP)目前大部分郵件服務(wù)器采用用戶名和密碼認(rèn)證的方式。
客戶端發(fā)送郵件過程為——先通過客戶端軟件(本程序中的郵件收發(fā)器)將郵件發(fā)送到SMTP服務(wù)器,然后再由SMTP服務(wù)器發(fā)送到目標(biāo)SMTP服務(wù)器。下面介紹SMTP協(xié)議的內(nèi)容:
SMTP協(xié)議總共定義了14個(gè)命令,命令由命令碼和氣候的參數(shù)域組成, 不區(qū)別大小寫的(通過前面專題的講述可以得出各個(gè)協(xié)議的命令組成都差不多的),下面就簡(jiǎn)單介紹下5個(gè)常用的命令碼
電子郵件由信封、首部、正文和結(jié)束符號(hào)4部分組成,下面就具體介紹下這4個(gè)部分的內(nèi)容:
1. 信封
信封包括發(fā)信人的郵件地址和接收人的郵件地址,具體對(duì)應(yīng)兩條SMTP命令——Mail from: mytest1989@sina.cn(發(fā)信人的地址)和Rcpt to: 794170314@qq.com
2. 首部
首部中常用的命令有:
Subject:<郵件主題>——表示郵件的主題
Date:<時(shí)間>——表示發(fā)郵件的時(shí)間
reply-to:<郵件地址>——表示郵件的回復(fù)地址
Content-Type:<郵件類型>——表示郵件包含文本、HTML超文本和附件的類型。
X-Priority:<郵件優(yōu)先級(jí)>——表示郵件發(fā)送的優(yōu)先級(jí),優(yōu)先級(jí)為3表示為普通郵件;如 X-Priority:3
3. 正文
正文當(dāng)然指的就是郵件的內(nèi)容了, 用Data命令指定,首部以一個(gè)空行結(jié)束,下面就是正文部分
4. 結(jié)束符號(hào)
郵件以“."結(jié)束,
接收方收到SMTP命令之后,會(huì)給出一個(gè)響應(yīng)碼,每個(gè)命令都只有一個(gè)響應(yīng)碼,SMTP響應(yīng)碼也是由3位數(shù)字組成,后面附加一些文本信息,響應(yīng)信息的格式為:
響應(yīng)碼<空格>文本信息<回車換行>
客戶端發(fā)出一條命令后,服務(wù)器端返回一個(gè)響應(yīng),發(fā)送者在發(fā)送下一條命令前必須等待服務(wù)器的響應(yīng),成功接收到響應(yīng)碼后才繼續(xù)發(fā)送命令。
附:SMTP常用的響應(yīng)碼:
3.1.2 郵件的發(fā)送過程
第一步:客戶端與服務(wù)器建立連接(該步中客戶端首先發(fā)送EHLO local 連接命令,服務(wù)器如果返回“220”響應(yīng)碼表示服務(wù)器準(zhǔn)備就緒了,客戶端再繼續(xù)發(fā)送“Auto login”命令,請(qǐng)求登錄,服務(wù)器收到命令后返回“334”響應(yīng)碼,表示要輸入用戶名,之后客戶端發(fā)送用戶名命令,等到響應(yīng)后再發(fā)送密碼命令,具體在程序的實(shí)現(xiàn)中也會(huì)有注釋。)
第二步:客戶端發(fā)送郵件的信封
第三步:開始發(fā)送郵件數(shù)據(jù),(包括郵件首部,正文和結(jié)束符號(hào),注:結(jié)束符號(hào)要單獨(dú)占一行,表示郵件發(fā)送結(jié)束)
第四步: 客戶端與服務(wù)器斷開連接。
3.1.3 發(fā)送功能的實(shí)現(xiàn)代碼
相信有了上面的理論解釋郵件發(fā)送的過程后,實(shí)現(xiàn)郵件發(fā)送的功能并不難的,并且.net類庫中SMTPClient類幫我們封裝了SMTP協(xié)議,使得我們實(shí)現(xiàn)郵件發(fā)送功能就不要記住那些具體的命令了, 只需要使用該類中提供的方法來完成郵件的發(fā)送(當(dāng)然你也可以通過發(fā)送命令的方式實(shí)現(xiàn),SMTPClient類的方法也是幫我們完成發(fā)送命令功能而已的),下面是郵件發(fā)送功能的核心代碼:
- View Code
- #region 郵件發(fā)送功能代碼
- // 添加附件
- private void btnAddFile_Click(object sender, EventArgs e)
- {
- OpenFileDialog openFileDialog = new OpenFileDialog();
- openFileDialog.CheckFileExists = true;
- // 只接受有效的文件名
- openFileDialog.ValidateNames = true;
- // 允許一次選擇多個(gè)文件作為附件
- openFileDialog.Multiselect = true;
- openFileDialog.Filter = "所有文件(*.*)|*.*";
- if (openFileDialog.ShowDialog() != DialogResult.OK)
- {
- return;
- }
- if (openFileDialog.FileNames.Length > 0)
- {
- // 因?yàn)檫@里允許選擇多個(gè)文件,所以這里用AddRange而沒有用Add方法
- cmbAttachment.Items.AddRange(openFileDialog.FileNames);
- }
- }
- // 刪除附件
- private void btnDeleteFile_Click(object sender, EventArgs e)
- {
- int index = cmbAttachment.SelectedIndex;
- if (index == -1)
- {
- MessageBox.Show("請(qǐng)選擇要?jiǎng)h除的附件!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
- return;
- }
- else
- {
- cmbAttachment.Items.RemoveAt(index);
- }
- }
- // 發(fā)送郵件
- private void btnSend_Click(object sender, EventArgs e)
- {
- this.Cursor = Cursors.WaitCursor;
- // 實(shí)例化一個(gè)發(fā)送的郵件
- // 相當(dāng)于與現(xiàn)實(shí)生活中先寫信,程序中把信(郵件)抽象為郵件類了
- MailMessage mailMessage = new MailMessage();
- // 指明郵件發(fā)送的地址,主題,內(nèi)容等信息
- // 發(fā)信人的地址為登錄收發(fā)器的地址,這個(gè)收發(fā)器相當(dāng)于我們平時(shí)Web版的郵箱或者是OutLook中配置的郵箱
- mailMessage.From = new MailAddress(tbxUserMail.Text);
- mailMessage.To.Add(txbSendTo.Text);
- mailMessage.Subject = txbSubject.Text;
- mailMessage.SubjectEncoding = Encoding.Default;
- mailMessage.Body = richtbxBody.Text;
- mailMessage.BodyEncoding = Encoding.Default;
- // 設(shè)置郵件正文不是Html格式的內(nèi)容
- mailMessage.IsBodyHtml = false;
- // 設(shè)置郵件的優(yōu)先級(jí)為普通優(yōu)先級(jí)
- mailMessage.Priority = MailPriority.Normal;
- //mailMessage.ReplyTo = new MailAddress(tbxUserMail.Text);
- // 封裝發(fā)送的附件
- System.Net.Mail.Attachment attachment = null;
- if (cmbAttachment.Items.Count > 0)
- {
- for (int i = 0; i < cmbAttachment.Items.Count; i++)
- {
- string fileNamePath = cmbAttachment.Items[i].ToString();
- string extName = Path.GetExtension(fileNamePath).ToLower();
- if (extName == ".rar" || extName == ".zip")
- {
- attachment = new System.Net.Mail.Attachment(fileNamePath, MediaTypeNames.Application.Zip);
- }
- else
- {
- attachment = new System.Net.Mail.Attachment(fileNamePath,MediaTypeNames.Application.Octet);
- }
- // 表示MIMEContent-Disposition標(biāo)頭信息
- // 對(duì)于ContentDisposition具體類的解釋大家可以參考MSDN
- // 這里我就不重復(fù)貼出來了,給個(gè)地址: http://msdn.microsoft.com/zh-cn/library/System.Net.Mime.ContentDisposition.aspx (著重看備注部分)
- ContentDisposition cd = attachment.ContentDisposition;
- cd.CreationDate = File.GetCreationTime(fileNamePath);
- cd.ModificationDate = File.GetLastWriteTime(fileNamePath);
- cd.ReadDate = File.GetLastAccessTime(fileNamePath);
- // 把附件對(duì)象加入到郵件附件集合中
- mailMessage.Attachments.Add(attachment);
- }
- }
- // 發(fā)送寫好的郵件
- try
- {
- // SmtpClient類用于將郵件發(fā)送到SMTP服務(wù)器
- // 該類封裝了SMTP協(xié)議的實(shí)現(xiàn),
- // 通過該類可以簡(jiǎn)化發(fā)送郵件的過程,只需要調(diào)用該類的Send方法就可以發(fā)送郵件到SMTP服務(wù)器了。
- smtpClient.Send(mailMessage);
- MessageBox.Show("郵件發(fā)送成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- catch(SmtpException smtpError)
- {
- MessageBox.Show("郵件發(fā)送失?。篬" + smtpError.StatusCode + "];["
- + smtpError.Message+"];\r\n["+smtpError.StackTrace+"]."
- ,"錯(cuò)誤",MessageBoxButtons.RetryCancel,MessageBoxIcon.Error);
- }
- finally
- {
- mailMessage.Dispose();
- this.Cursor = Cursors.Default;
- }
- }
- #endregion
3.2 郵件接收功能的實(shí)現(xiàn)
3.2.1 POP3協(xié)議
前面介紹了郵件的發(fā)送,當(dāng)然接收者需要登錄郵箱來查看收到的郵件了,此時(shí)就必有有一個(gè)協(xié)議去讀取服務(wù)器上郵件,POP3就是這樣的一個(gè)協(xié)議。還有兩外一種協(xié)議也是用來接收郵件的——IMAP協(xié)議,它與POP3協(xié)議區(qū)別有:
1. IMAP使用的端口號(hào)是143而POP3郵件服務(wù)器通過監(jiān)聽110端口來提供POP3服務(wù);
2 . IMAP 允許客戶端在郵件服務(wù)器上建立文件夾來保持郵件,而不用把郵件下載到本地。而POP3需要把郵件下載到本地。
和SMTP協(xié)議一樣,客戶端要通過POP3協(xié)議從POP3服務(wù)器上獲取郵件,也需要先與POP3服務(wù)器建立TCP連接,等待服務(wù)器向客戶端發(fā)送確認(rèn)信息表明連接成功時(shí),客戶端才可以繼續(xù)發(fā)送命令給服務(wù)器來獲取郵件,在POP3協(xié)議中,規(guī)定的命令也是幾十條的,每條命令由命令和參數(shù)兩部分組成,都是以回車換行結(jié)束,并且命令和參數(shù)之間由空格分隔,命令通常也是由3-4個(gè)字母組成,參數(shù)最多可以為40個(gè)字符的長(zhǎng)度,而服務(wù)器的響應(yīng)信息是由一個(gè)狀態(tài)碼和可能附加信息的字符組成,所有的響應(yīng)信息也是以回車換行結(jié)束的。狀態(tài)碼和其他協(xié)議定義的狀態(tài)碼有點(diǎn)不一樣,POP3服務(wù)器響應(yīng)的狀態(tài)碼有兩種——“+OK”(確定)和"-ERR"(失敗)。這樣客戶端可以通過檢查響應(yīng)的狀態(tài)碼所包含的字符來判斷服務(wù)器是否響應(yīng)客戶端發(fā)送的命令,即響應(yīng)信息中包含“+OK”表示成功響應(yīng),包含“-ERR”表示服務(wù)器未響應(yīng)。同時(shí)在程序的實(shí)現(xiàn)中大家可以通過Debug來查看響應(yīng)消息的組成,這樣可以加深理解。
3.2.2 郵件接收的過程
客戶端從服務(wù)器接收郵件的過程主要經(jīng)歷3個(gè)狀態(tài):授權(quán)狀態(tài)、操作狀態(tài)和更新狀態(tài)
(1)授權(quán)狀態(tài)——客戶端發(fā)送與POP3服務(wù)器的TCP連接請(qǐng)求,服務(wù)器接收后發(fā)送一個(gè)響應(yīng)確認(rèn)信息之后,此時(shí)客戶端需要發(fā)送正確的用戶名和密碼進(jìn)行確認(rèn),因?yàn)樵卩]件服務(wù)器上有很多用戶郵箱,只有提供正確的用戶名和密碼才有權(quán)限訪問自己的郵箱,就像現(xiàn)實(shí)生活中我們郵箱的鑰匙一樣的。
發(fā)送用戶名命令: USER mytest1989@sina.cn
發(fā)送密碼命令: PASS ******(這兩個(gè)命令都在代碼中有給出的,大家可以參考代碼來理解郵件的接收過程)
(2) 操作狀態(tài)——如果客戶端提供了正確的用戶名和密碼,則授權(quán)狀態(tài)也就通過了,就相當(dāng)于打開了在服務(wù)器上自己的郵箱,現(xiàn)在用戶就有權(quán)限進(jìn)去下載,查看和刪除郵件等操作的,然后在現(xiàn)實(shí)生活中的取郵件和刪除郵件都很簡(jiǎn)單(只要打開了郵箱門,用手去拿就可以了),然后在網(wǎng)絡(luò)應(yīng)用上,這些操作都需要發(fā)送POP3命令給服務(wù)器,服務(wù)器接收到命令后再給出響應(yīng)。操作中常用的命令有:
STAT 命令——該命令從服務(wù)器中獲取郵件總數(shù)和總字節(jié)數(shù),服務(wù)器響應(yīng)命令返回郵件總數(shù)和總字節(jié)數(shù)。如:
- 客戶端發(fā)送POP3命令: STAT
- 服務(wù)器響應(yīng)命令: +OK 2 1340<BR>服務(wù)器響應(yīng)命令:
LIST 命令——該命令從服務(wù)器中獲得郵件列表和大小,服務(wù)器響應(yīng)命令返回列出郵件列表和大小。如:
- 客戶端發(fā)送POP3命令:LIST
- 服務(wù)器響應(yīng)命令: +OK 2 message(1430 octect)
- 服務(wù)器響應(yīng)命令:1 700
- 服務(wù)器響應(yīng)命令:2 730
- 服務(wù)器響應(yīng)命令:<一個(gè)空行>
RETR 命令—— 該命令從服務(wù)器中獲得一個(gè)郵件,格式為 RETR <郵件編號(hào)>如:
- 客戶端發(fā)送POP3命令:RETR 1
- 服務(wù)器響應(yīng)命令: 700 octets
- 服務(wù)器響應(yīng)命令:<郵件頭和內(nèi)容>
- 服務(wù)器響應(yīng)命令: <空行>
DELETE 命令——該命令告訴服務(wù)器將郵件標(biāo)記為刪除。(此時(shí)只是邏輯刪除)
(3)更新狀態(tài)——客戶端發(fā)送QUIT命令后,此時(shí)就進(jìn)入更新狀態(tài),POP3服務(wù)器釋放在操作狀態(tài)中取得的資源,并將邏輯刪除的郵件進(jìn)行物理刪除,然后關(guān)閉與客戶端的TCP連接。這樣整個(gè)郵件處理的過程就結(jié)束了。
3.2.3 接收功能的實(shí)現(xiàn)代碼
有了前面接收郵件過程的介紹,再參考代碼的實(shí)現(xiàn),相信大家可以更好的理解客戶端從POP3服務(wù)器中獲取郵件的過程的,由于.net類庫并沒有幫我們封裝POP3協(xié)議的實(shí)現(xiàn)類,要實(shí)現(xiàn)郵件的獲取可以采用發(fā)送命令的方式,也可以使用Jmail組件,這個(gè)組件其實(shí)就是POP3協(xié)議的封裝類,既然微軟沒有幫我們做,其他公司幫我們做好后來幫助我們簡(jiǎn)單的實(shí)現(xiàn)郵件的接收的一個(gè)類庫罷了。然后在使用這個(gè)組件的過程中出現(xiàn)了好幾個(gè)問題的,在源碼中我都解釋,大家可以下載源代碼后查看的。
實(shí)現(xiàn)郵件接收的核心代碼如下:
- // 登錄郵箱(這里是本程序——郵件收發(fā)器)
- private void btnLogin_Click_1(object sender, EventArgs e)
- {
- // 與POP3服務(wù)器建立TCP連接
- // 建立連接后把服務(wù)器上的郵件下載到本地
- // 設(shè)置當(dāng)前界面的光標(biāo)為等待光標(biāo)(就是我們看到的一個(gè)動(dòng)的圓形)
- Cursor.Current = Cursors.WaitCursor;
- lsttbxStatus.Items.Clear();
- try
- {
- // POP3服務(wù)器通過監(jiān)聽TCP110端口來提供POP3服務(wù)的
- // 向POP3服務(wù)器發(fā)出tcp請(qǐng)求
- tcpClient = new TcpClient(tbxPOP3Server.Text, 110);
- lsttbxStatus.Items.Add("正在連接...");
- }
- catch
- {
- MessageBox.Show("連接失敗", "錯(cuò)誤", 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;
- // 讀取服務(wù)器返回的響應(yīng)連接信息
- str = GetResponse();
- if (CheckResponse(str) == false)
- {
- lsttbxStatus.Items.Add("服務(wù)器拒接了連接請(qǐng)求");
- return;
- }
- // 如果服務(wù)器接收請(qǐng)求
- // 向服務(wù)器發(fā)送憑證——用戶名和密碼
- // 向服務(wù)器發(fā)送用戶名,請(qǐng)求確認(rèn)
- lsttbxStatus.Items.Add("核實(shí)用戶名階段...");
- SendToServer("USER " + tbxUserMail.Text);
- str = GetResponse();
- if (CheckResponse(str) == false)
- {
- lsttbxStatus.Items.Add("用戶名錯(cuò)誤.");
- return;
- }
- // 用戶名審核通過后再發(fā)送密碼等待確認(rèn)
- // 向服務(wù)器發(fā)送密碼,請(qǐng)求確認(rèn)
- SendToServer("PASS "+txbPassword.Text);
- str = GetResponse();
- if (CheckResponse(str) == false)
- {
- lsttbxStatus.Items.Add("密碼錯(cuò)誤!");
- return;
- }
- lsttbxStatus.Items.Add("身份驗(yàn)證成功,可以開始會(huì)話");
- // 向服務(wù)器發(fā)送LIST 命令,請(qǐng)求獲得郵件列表和大小
- 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;
- // 登陸成功后實(shí)例化郵件發(fā)送對(duì)象,以便后面完成發(fā)送郵件的操作
- // 實(shí)例化郵件發(fā)送類(SmtpClient)對(duì)象
- if (smtpClient == null)
- {
- smtpClient = new SmtpClient();
- smtpClient.Host = tbxSmtpServer.Text;
- smtpClient.Port = 25;
- // 不使用默認(rèn)憑證,即需要認(rèn)證登陸
- smtpClient.UseDefaultCredentials = false;
- smtpClient.Credentials = new NetworkCredential(tbxUserMail.Text, txbPassword.Text);
- smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
- }
- // 登陸成功后,自動(dòng)接收新郵件
- // 開始接收郵件
- try
- {
- btnRefreshMailList.PerformClick();
- }
- catch
- {
- MessageBox.Show("讀取郵件列表失??!", "錯(cuò)誤", 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服務(wù)器交互事件
- // 獲取服務(wù)器響應(yīng)的信息
- private string GetResponse()
- {
- string str = null;
- try
- {
- str = streamReader.ReadLine();
- if (str == null)
- {
- lsttbxStatus.Items.Add("連接失敗——服務(wù)器沒有響應(yīng)");
- }
- else
- {
- lsttbxStatus.Items.Add("收到:[" + str + "]");
- if (str.StartsWith("-ERR"))
- {
- str = null;
- }
- }
- }
- catch(Exception err)
- {
- lsttbxStatus.Items.Add("連接失敗:[" + err.Message + "]");
- }
- return str;
- }
- // 檢查響應(yīng)信息
- private bool CheckResponse(string responseString)
- {
- if (responseString == null)
- {
- return false;
- }
- else
- {
- if (responseString.StartsWith("+OK"))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- // 向服務(wù)器發(fā)送命令
- private bool SendToServer(string str)
- {
- try
- {
- // 這里必須使用WriteLine方法的,因?yàn)镻OP3協(xié)議中定義的命令是以回車換行結(jié)束的
- // 如果客戶端發(fā)送的命令沒有以回車換行結(jié)束,POP3服務(wù)器就不能識(shí)別,也就不能響應(yīng)客戶端的請(qǐng)求了
- // 如果想用Write方法,則str輸入的參數(shù)字符中必須包含“\r\n”,也就是回車換行字符串。
- streamWriter.WriteLine(str);
- streamWriter.Flush();
- lsttbxStatus.Items.Add("發(fā)送:[" + str + "]");
- return true;
- }
- catch(Exception ex)
- {
- lsttbxStatus.Items.Add("發(fā)送失敗:[" + ex.Message + "]");
- return false;
- }
- }
- #endregion
3.3 程序運(yùn)行結(jié)果演示
首先輸入郵箱名和密碼登錄到POP3服務(wù)器來獲取郵件列表的演示:
然后在郵件列表中選中一個(gè)郵件進(jìn)行閱讀,然后進(jìn)行回復(fù)郵件的操作演示(郵件的發(fā)送都可以附加附件發(fā)送出去):
閱讀郵件的界面:
回復(fù)郵件的界面:
同時(shí)點(diǎn)擊發(fā)送按鈕后,就可以把郵件發(fā)送到sina的SMTP服務(wù)器上,再由新浪的SMTP服務(wù)器轉(zhuǎn)發(fā)到QQ的SMTP服務(wù)器,QQ的POP3服務(wù)器中QQ的SMTP服務(wù)器獲取收到的郵件,當(dāng)QQ用戶輸入正確的郵箱名和密碼后就可以從QQ的POP3服務(wù)器上獲取收到的郵件。
點(diǎn)擊發(fā)送按鈕后成功發(fā)送郵件的圖片:
四、總結(jié)
介紹到這里,本專題的內(nèi)容就已經(jīng)介紹完了,希望通過本專題可以讓大家明白郵件發(fā)送和接收的原理,并且可以自定義一個(gè)簡(jiǎn)單郵件收發(fā)器的功能的,在后面一專題將介紹FTP協(xié)議(文件傳輸協(xié)議),并實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文件上傳和下載的程序。
源代碼下載地址: http://files.cnblogs.com/zhili/MailSendAndReceive.zip
原文鏈接:http://www.cnblogs.com/zhili/archive/2012/09/24/MailSend_POP3_SMTP.html
【編輯推薦】
- C#網(wǎng)絡(luò)編程系列一:網(wǎng)絡(luò)協(xié)議簡(jiǎn)介
- C#網(wǎng)絡(luò)編程系列二:HTTP協(xié)議詳解
- C#網(wǎng)絡(luò)編程系列三:自定義Web服務(wù)器
- C#網(wǎng)絡(luò)編程系列四:自定義Web瀏覽器
- C#網(wǎng)絡(luò)編程系列五:TCP編程
- C#網(wǎng)絡(luò)編程系列六:UDP編程
- C#網(wǎng)絡(luò)編程系列七:UDP編程補(bǔ)充
- C#網(wǎng)絡(luò)編程系列八:P2P編程
- C#網(wǎng)絡(luò)編程系列九:類似QQ的即時(shí)通信程序