1. 程式人生 > >[C# 網路程式設計系列]專題十:實現簡單的郵件收發器

[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個常用的命令碼

名稱  

解釋

HELO或EHLO

傳送連線到伺服器的命令,EHLO主要用於與ESMTP伺服器建立連線時傳送的命令

MAIL FROM

指定發件人的郵件地址

Rcpt to

指定收件人的郵件地址

Data

指定郵件正文內容,郵件內容以單獨一行   ”.” 表示接觸

Quit

關閉與伺服器的連線,然後退出

  電子郵件由信封、首部、正文和結束符號4部分組成,下面就具體介紹下這4個部分的內容:

  1. 信封

  信封包括髮信人的郵件地址和接收人的郵件地址,具體對應兩條SMTP命令——Mail from: [email protected](發信人的地址)和Rcpt to:  [email protected]

  2. 首部

  首部中常用的命令有:

  •   Subject:<郵件主題>——表示郵件的主題
  •   Date:<時間>——表示發郵件的時間
  •   reply-to:<郵件地址>——表示郵件的回覆地址
  •   Content-Type:<郵件型別>——表示郵件包含文字、HTML超文字和附件的型別。
  •       X-Priority:<郵件優先順序>——表示郵件傳送的優先順序,優先順序為3表示為普通郵件;如 X-Priority:3

  3. 正文

  正文當然指的就是郵件的內容了, 用Data命令指定,首部以一個空行結束,下面就是正文部分

  4. 結束符號

  郵件以“."結束,

  接收方收到SMTP命令之後,會給出一個響應碼,每個命令都只有一個響應碼,SMTP響應碼也是由3位數字組成,後面附加一些文字資訊,響應資訊的格式為:

     響應碼<空格>文字資訊<回車換行>

  客戶端發出一條命令後,伺服器端返回一個響應,傳送者在傳送下一條命令前必須等待伺服器的響應,成功接收到響應碼後才繼續傳送命令。

    附:SMTP常用的響應碼:

響應碼

解釋

響應碼

解釋

211

系統狀態或系統幫助響應

421

服務未就緒,關閉了傳輸通道

214

幫助資訊

501

引數格式錯誤

220

服務就緒

502

命令不可實現

221

服務關閉傳輸通道

535

使用者驗證失敗

235

使用者驗證成功

553

郵箱名不可用,要求的操作未執行

334

等待使用者輸入驗證

554

操作失敗

354

開始郵件輸入

3.1.2 郵件的傳送過程

  第一步:客戶端與伺服器建立連線(該步中客戶端首先發送EHLO local 連線命令,伺服器如果返回“220”響應碼錶示伺服器準備就緒了,客戶端再繼續傳送“Auto login”命令,請求登入,伺服器收到命令後返回“334”響應碼,表示要輸入使用者名稱,之後客戶端傳送使用者名稱命令,等到響應後再發送密碼命令,具體在程式的實現中也會有註釋。)

  第二步:客戶端傳送郵件的信封

  第三步:開始傳送郵件資料,(包括郵件首部,正文和結束符號,注:結束符號要單獨佔一行,表示郵件傳送結束)

  第四步: 客戶端與伺服器斷開連線。

3.1.3 傳送功能的實現程式碼

相信有了上面的理論解釋郵件傳送的過程後,實現郵件傳送的功能並不難的,並且.net類庫中SMTPClient類幫我們封裝了SMTP協議,使得我們實現郵件傳送功能就不要記住那些具體的命令了, 只需要使用該類中提供的方法來完成郵件的傳送(當然你也可以通過傳送命令的方式實現,SMTPClient類的方法也是幫我們完成傳送命令功能而已的),下面是郵件傳送功能的核心程式碼:

 #region 郵件傳送功能程式碼
         // 新增附件
         private void btnAddFile_Click(object sender, EventArgs e)
         {
             OpenFileDialog openFileDialog = new OpenFileDialog();
             openFileDialog.CheckFileExists = true;
             // 只接受有效的檔名
             openFileDialog.ValidateNames = true;
             // 允許一次選擇多個檔案作為附件
             openFileDialog.Multiselect = true;
             openFileDialog.Filter = "所有檔案(*.*)|*.*";
             if (openFileDialog.ShowDialog() != DialogResult.OK)
             {
                 return;
             }
             if (openFileDialog.FileNames.Length > 0)
             {
                 // 因為這裡允許選擇多個檔案,所以這裡用AddRange而沒有用Add方法
                 cmbAttachment.Items.AddRange(openFileDialog.FileNames);
             }
         }
 
         // 刪除附件
         private void btnDeleteFile_Click(object sender, EventArgs e)
         {
             int index = cmbAttachment.SelectedIndex;
             if (index == -1)
             {
                 MessageBox.Show("請選擇要刪除的附件!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                 return;
             }
             else
             {
                 cmbAttachment.Items.RemoveAt(index);
             }
         }
 
         // 傳送郵件
         private void btnSend_Click(object sender, EventArgs e)
         {
             this.Cursor = Cursors.WaitCursor;
             // 例項化一個傳送的郵件
             // 相當於與現實生活中先寫信,程式中把信(郵件)抽象為郵件類了
             MailMessage mailMessage = new MailMessage();
             // 指明郵件傳送的地址,主題,內容等資訊
             // 發信人的地址為登入收發器的地址,這個收發器相當於我們平時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;
             // 設定郵件正文不是Html格式的內容
             mailMessage.IsBodyHtml = false;
             // 設定郵件的優先順序為普通優先順序
             mailMessage.Priority = MailPriority.Normal;
             //mailMessage.ReplyTo = new MailAddress(tbxUserMail.Text);
 
             // 封裝傳送的附件
             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標頭資訊
                     // 對於ContentDisposition具體類的解釋大家可以參考MSDN
                     // 這裡我就不重複貼出來了,給個地址: 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);
                     // 把附件物件加入到郵件附件集合中
                     mailMessage.Attachments.Add(attachment);
                 }
             }
 
             // 傳送寫好的郵件
             try
             {
                 // SmtpClient類用於將郵件傳送到SMTP伺服器
                 // 該類封裝了SMTP協議的實現,
                 // 通過該類可以簡化傳送郵件的過程,只需要呼叫該類的Send方法就可以傳送郵件到SMTP伺服器了。
                 smtpClient.Send(mailMessage);
                 MessageBox.Show("郵件傳送成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
 
             }
             catch(SmtpException smtpError)
             {
                 MessageBox.Show("郵件傳送失敗:[" + smtpError.StatusCode + "];[" 
                     + smtpError.Message+"];\r\n["+smtpError.StackTrace+"]."
                     ,"錯誤",MessageBoxButtons.RetryCancel,MessageBoxIcon.Error);
             }
             finally
             {
                 mailMessage.Dispose();
                 this.Cursor = Cursors.Default;
             }
         }
 
         #endregion

3.2 郵件接收功能的實現

3.2.1 POP3協議

   前面介紹了郵件的傳送,當然接收者需要登入郵箱來檢視收到的郵件了,此時就必有有一個協議去讀取伺服器上郵件,POP3就是這樣的一個協議。還有兩外一種協議也是用來接收郵件的——IMAP協議,它與POP3協議區別有:1. IMAP使用的埠號是143而POP3郵件伺服器通過監聽110埠來提供POP3服務;

                       2 . IMAP 允許客戶端在郵件伺服器上建立資料夾來保持郵件,而不用把郵件下載到本地。而POP3需要把郵件下載到本地。

  和SMTP協議一樣,客戶端要通過POP3協議從POP3伺服器上獲取郵件,也需要先與POP3伺服器建立TCP連線,等待伺服器向客戶端傳送確認資訊表明連線成功時,客戶端才可以繼續傳送命令給伺服器來獲取郵件,在POP3協議中,規定的命令也是幾十條的,每條命令由命令和引數兩部分組成都是以回車換行結束,並且命令和引數之間由空格分隔,命令通常也是由3-4個字母組成,引數最多可以為40個字元的長度,而伺服器的響應資訊是由一個狀態碼和可能附加資訊的字元組成,所有的響應資訊也是以回車換行結束的。狀態碼和其他協議定義的狀態碼有點不一樣,POP3伺服器響應的狀態碼有兩種——“+OK”(確定)和"-ERR"(失敗)。這樣客戶端可以通過檢查響應的狀態碼所包含的字元來判斷伺服器是否響應客戶端傳送的命令,即響應資訊中包含“+OK”表示成功響應,包含“-ERR”表示伺服器未響應。同時在程式的實現中大家可以通過Debug來檢視響應訊息的組成,這樣可以加深理解。

3.2.2 郵件接收的過程

 客戶端從伺服器接收郵件的過程主要經歷3個狀態:授權狀態、操作狀態和更新狀態

(1)授權狀態——客戶端傳送與POP3伺服器的TCP連線請求,伺服器接收後傳送一個響應確認資訊之後,此時客戶端需要傳送正確的使用者名稱和密碼進行確認,因為在郵件伺服器上有很多使用者郵箱,只有提供正確的使用者名稱和密碼才有許可權訪問自己的郵箱,就像現實生活中我們郵箱的鑰匙一樣的。

   傳送使用者名稱命令: USER [email protected]

 傳送密碼命令: 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協議(檔案傳輸協議),並實現一個簡單的檔案上傳和下載的程式。

原始碼下載地址: 郵件收發器


相關推薦

[C# 網路程式設計系列]專題實現簡單郵件收發

引言: 在我們的平常工作中,郵件的傳送和接收應該是我們經常要使用到的功能的。因此知道電子郵件的應用程式的原理也是非常有必要的,在這一個專題中將介紹電子郵件應用程式的原理、電子郵件應用程式中涉及的協議和實現一個簡答的電子郵件收發器程式。 一、郵件應用程式基本知識 1

詳解C# 網路程式設計系列實現類似QQ的即時通訊程式

https://www.jb51.net/article/101289.htm   引言: 前面專題中介紹了UDP、TCP和P2P程式設計,並且通過一些小的示例來讓大家更好的理解它們的工作原理以及怎樣.Net類庫去實現它們的。為了讓大家更好的理解我們平常中常見的軟體QQ的工作原理,所以在本專題

C#網路程式設計系列文章(一)之Socket實現非同步TCP伺服器

原創性宣告 文章系列目錄 程式碼下載地址 開篇 本人因為對於網路程式設計的喜愛,經常性的使用c#編寫各類伺服器(e.g TCP伺服器,UDP伺服器),但是基本上都是搞著玩,網上也有很多講c#網路程式設計的文章,當然我也參考了很多作者寫的文章。看了這篇

C#網路程式設計系列文章(八)之UdpClient實現同步UDP伺服器

原創性宣告 本文作者:小竹zz 本文地址http://blog.csdn.net/zhujunxxxxx/article/details/44258719 轉載請註明出處 文章系列目錄 本文介紹 UdpClient 類在同步阻塞模式中為傳送和接收無連線的 U

[C# 基礎知識系列] 專題全面解析擴充套件方法

引言:    C# 3中所有特性的提出都是更好地為Linq服務的, 充分理解這些基礎特性後。對於更深層次地去理解Linq的架構方面會更加簡單,從而就可以自己去實現一個簡單的ORM框架的,對於Linq的學習在下一個專題中將會簡單和大家介紹下,這個專題還是先來介紹服務於Li

C#網路程式設計系列文章(二)之Socket實現同步TCP伺服器

原創性宣告 本文作者:小竹zz  本文地址http://blog.csdn.net/zhujunxxxxx/article/details/44258719 轉載請註明出處 文章系列目錄 本文介紹 在上一篇部落格中我說了,我將會介紹c#中使用Socke

C#網路程式設計系列文章(三)之TcpListener實現非同步TCP伺服器

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.Net; namespace NetFrame.Net.TC

C#網路程式設計系列文章(五)之Socket實現非同步UDP伺服器

原創性宣告 本文作者:小竹zz 本文地址http://blog.csdn.net/zhujunxxxxx/article/details/44258719 轉載請註明出處 文章系列目錄 本文介紹 在.Net中,System.Net.Sockets 名稱

Java併發程式設計系列synchronized(1)

在多執行緒併發訪問資源(這類資源稱為臨街資源)的時候,由於割裂來了原子操作,所以會導致資料不一致的情況。為了避免這種情況,需要使用同步機制,同步機制能夠保證多執行緒併發訪問資料的時候不會出現資料不一致的情況。 一種同步機制是使用synchronized關鍵字,

網路程式設計系列一 radius客戶端(802.1x客戶端)

學習802.1x客戶端的開發必須要了解下下面的內容: 1)802.1x客戶端開發一般是因為準入專案,這個准入系統的大環境 2) 802.1x協議的工作過程  和 EAP協議所屬的層次,以及層次之間的關係 3) 802.1x協議的具體詳細的結構可以看這個連結 然後的開

[C#基礎知識系列]專題:全面解析可空型別

引言:   C# 2.0 中還引入了可空型別,可空型別也是值型別,只是可空型別是包括null的值型別的,下面就介紹下C#2.0中對可空型別的支援具體有哪些內容(最近一直都在思考如何來分享這篇文章的,因為剛開始覺得可空型別使用過程中比較簡單,覺得沒有講的必要,但是考慮到這

[C#基礎知識系列]專題二:迭代

引言:    在C# 1.0中我們經常使用foreach來遍歷一個集合中的元素,然而一個型別要能夠使用foreach關鍵字來對其進行遍歷必須實現IEnumerable或IEnumerable<T>介面,(之所以來必須要實現IEnumerable這個介面,是因

網路程式設計系列二 codeblocks匯入makefile工程

最近需要用codeblocks開發一個客戶端程式,需要用到一個開源專案, 在codeblocks上加入Makefile工程。 1)“File -> New -> Project”,選擇“Empty Project”並建立。 2)選中 Project,右鍵,選擇“

Linux網路程式設計(一)一個簡單的socket程式

伺服器: /* *tcp_server.c */ #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include

linux 網路程式設計:使用兩執行緒實現socket同時收發資料

    工作中最近有使用到socket 向客戶端同時傳送和接收資料,因為是嵌入式linux裝置,且要求只能同時一個客戶端連線該埠。考慮到節省系統資源,只建立了兩個執行緒分別實現服務端的收發資料。下面直接上程式碼,該程式碼為在PC機上程式,已作詳細註釋。 server.c

WSWP(用python寫網路爬蟲)筆記 一實現簡單爬蟲

wswp中的程式碼是通過python2的語法來寫的,在學習的過程中個人比較喜歡python3,因此準備將wswp的示例程式碼用python3重寫一遍,以加深映像。 開始嘗試構建爬蟲 識別網站所用技術和網站所有者 構建網站所使用的技術型別的識別和尋找

[C# 網絡編程系列]專題UDP編程補充——UDP廣播程序的實現

tca ssa 程序 als learn ans targe focus ase 上次因為時間的關系,所以把上一個專題遺留下的一個問題在本專題中和大家分享下,本專題主要介紹下如何實現UDP廣播的程序,下面就直接介紹實現過程和代碼以及運行的結果。 一、程序實現 UDP廣播程序

linux下C/C++網路程式設計基本socket實現tcp和udp的例子

簡單的linux下socket程式設計,分別基於TCP和UDP協議實現的簡單程式 linux下socket程式設計可以概括為以下幾個函式的運用: socket() bind() listen

Boost.Asio C++ 網路程式設計基於TCP的非同步服務端

       這個流程圖是相當複雜的:從Boost.Asio出來你可以看到4個箭頭指向on_accept,on_read,on_write和on_check_ping。這也就意味著你永遠不知道哪個非同步呼叫是下一個完成的呼叫,但是你可以確定的是它是這4個操作中的一個。基於TC

[C# 網絡編程系列]專題UDP編程

單播 using 功能 .get 掩碼 ati multi 內容 協議 紹了TCP編程的一些知識,UDP與TCP地位相當的另一個傳輸層協議,它也是當下流行的很多主流網絡應用(例如QQ、MSN和Skype等一些即時通信軟件傳輸層都是應用UDP協議的)底層的傳輸基礎,所以在本專