用C#.NET實(shí)現(xiàn)電子郵件客戶程序
周華清 戴晟輝(東華理工學(xué)院計(jì)算機(jī)與通信系 江西 撫州 344000)
【摘要】通過C#這種VisualSTudio.NET中新引入的面向?qū)ο笄翌愋桶踩木幊陶Z言,在.NET平臺(tái)上開發(fā)電子郵件客戶程序。通過套接字編程實(shí)現(xiàn)網(wǎng)絡(luò)通信連接,闡述SMTP(簡單郵件傳輸協(xié)議)和POP3(郵局協(xié)議)的工作原理,然后具體講解了根據(jù)SMTP協(xié)議開發(fā)電子郵件客戶端的郵件發(fā)送程序,根據(jù)POP3協(xié)議開發(fā)電子郵件客戶端的郵件接收程序。
【關(guān)鍵詞】套接字? 簡單郵件傳輸協(xié)議? 郵局協(xié)議
1 C#中套接字的編程
套接字是通信的基石,是支持TCP/IP協(xié)議網(wǎng)絡(luò)通信的基本操作單元。可以將套接字看做不同主機(jī)間的進(jìn)程進(jìn)行雙向通信的端點(diǎn),它構(gòu)成了單個(gè)主機(jī)內(nèi)及整個(gè)網(wǎng)絡(luò)間的編程界面。套接字存在于通信域中。通信域是為了處理一般的線程通過套接字通信而引進(jìn)的一種抽象概念。套接字通常和同一個(gè)域中的套接字交換數(shù)據(jù)(數(shù)據(jù)交換也可能穿越域的界限,但這時(shí)一定要執(zhí)行某種解釋程序)。各種進(jìn)程使用這個(gè)相同的域互相之間用Internet協(xié)議簇來進(jìn)行通信。
針對C#中Socket編程用Socket類來進(jìn)行,.NET 框架的Socket類是包含在System.Net.Sockets名字空間中的一個(gè)非常重要的類,其中為實(shí)現(xiàn)網(wǎng)絡(luò)編程提供了大量的方法。使用Socket類開發(fā)windows網(wǎng)絡(luò)應(yīng)用程序原來有規(guī)可尋,它們在大多數(shù)情況下遵循大致相同的步驟。
在使用之前,你需要首先創(chuàng)建Socket對象的實(shí)例,這可以通過Socket類的構(gòu)造方法來實(shí)現(xiàn):
public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType);
其中,addressFamily參數(shù)指定Socket使用的尋址方案;socketType參數(shù),指定Socket的類型;protocolType參數(shù),指定Socket使用的協(xié)議。
下面的示例語句創(chuàng)建一個(gè)Socket,它可用于在基于TCP/IP的網(wǎng)絡(luò)(如Internet)上通訊。
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
若要使用UDP而不是TCP,需要更改協(xié)議類型,如下面的示例所示:
Socket s=newSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
一旦創(chuàng)建Socket,在客戶端,將可以通過Connect方法連接到指定的服務(wù)器,并通過Send/SendTo方法向遠(yuǎn)程服務(wù)器發(fā)送數(shù)據(jù),而后可以通過Receive/ReceiveFrom方法從服務(wù)端接收數(shù)據(jù);而在服務(wù)器端,你需要使用Bind方法綁定所指定的接口使Socket與一個(gè)本地終結(jié)點(diǎn)相聯(lián),并通過Listen方法偵聽該接口上的請求,當(dāng)偵聽到用戶端的連接時(shí),調(diào)用Accept完成連接的操作,創(chuàng)建新的Socket以處理傳入的連接請求。使用完Socket后,記住使用Shutdown方法禁用Socket,并使用Close方法關(guān)閉Socket。
?2 SMTP和POP3協(xié)議的工作過程
在Internet上發(fā)送/接收電子郵件,要用到二個(gè)協(xié)議:SMTP(簡單郵件傳輸協(xié)議)和POP3(郵局協(xié)議)。下面從SMTP和POP3協(xié)議入手,說明如何發(fā)送和接收電子郵件。
如圖1,描述了從發(fā)件人使用主機(jī)(Client1)發(fā)送一封郵件從郵箱one@server1.com發(fā)往郵箱two@server2.com(其中域名為server1.com的郵件系統(tǒng)安裝在Server1上,域名為server2.com的郵件系統(tǒng)安裝在Server2上),收件人通過主機(jī)(Client2)接收到這封郵件的過程。在郵件傳送的各個(gè)階段要采用不同的網(wǎng)絡(luò)協(xié)議。???
(1)發(fā)件人采用Mail Client端軟件在Client 1處撰寫一封郵件發(fā)給收件人,首先是將該郵件從Client1發(fā)送到他本人的郵箱one@server1.com所在的服務(wù)器Server1上。這一步由Client1發(fā)往Server的過程采用的是SMTP協(xié)議。
?
????????????????????????? 圖1 電子郵件傳送過程示意圖
(2)郵件可以從發(fā)件人的郵件服務(wù)器Server1直接傳送到收件人郵箱two@server2.com所在的服務(wù)器Server2上,也可能需要經(jīng)過第三方的郵件服務(wù)器Server3做中轉(zhuǎn)后再送達(dá)接收方,這個(gè)過程稱為Relay。這一步在Mail Server之間進(jìn)行郵件傳送的過程采用的是SMTP協(xié)議。郵件發(fā)送到接收方的服務(wù)器Server2上后,由Server2負(fù)責(zé)將該郵件投遞到收件人的郵箱two@server2.com中,保存在Server2的磁盤陣列中。此時(shí),郵件傳送過程結(jié)束。
(3)收件人訪問他在Server2上的郵箱two@server2.com時(shí),采用Mail Client端軟件Client2將Server2郵箱中的郵件下載到本地硬盤上閱讀。這一步將郵件從Server下載到Client的過程采用的是POP3協(xié)議。
開發(fā)電子郵件客戶端實(shí)際上也就是利用套接字(Socket)編程進(jìn)行對話通訊,按照SMTP協(xié)議和POP3協(xié)議的規(guī)范完成郵件傳輸。
3 郵件發(fā)送模塊的實(shí)現(xiàn)
?SMTP協(xié)議是TCP/IP協(xié)議棧中的一個(gè)廣為使用的上層協(xié)議。SMTP定義了如何在兩個(gè)用戶間傳輸郵件,使用了spooling的概念,允許將郵件從一個(gè)本地應(yīng)用發(fā)送到一個(gè)SMTP應(yīng)用。SMTP應(yīng)用把郵件存儲(chǔ)到一個(gè)設(shè)備或內(nèi)存中,一旦郵件到達(dá)SMTP應(yīng)用,該郵件被放入到一個(gè)隊(duì)列中,一個(gè)服務(wù)器檢查是否有郵件到達(dá),然后試圖投遞到達(dá)的郵件。如果郵件的收方不存在,服務(wù)器然后還會(huì)再進(jìn)行嘗試。最終,如果郵件不能被投遞,則將該郵件丟棄或返回給郵件的發(fā)送者。這一概念被稱為端到端的投遞,并且它將郵件保存在隊(duì)列中,直到郵件被投遞出去。我們可以從兩個(gè)RFC中找到有關(guān)SMTP的討論。RFC822描述了報(bào)文的結(jié)構(gòu),其中還包括了信封。RFC821規(guī)定了在兩臺(tái)機(jī)器間控制郵件交換的協(xié)議。
以下模塊利用TcpClient類開發(fā)一個(gè)SMTP客戶端,以發(fā)送郵件。
3.1 發(fā)送SMTP命令
SMTP服務(wù)器一般都識(shí)別UTF8碼,所有發(fā)送命令都使用UTF8編碼,每個(gè)命令均以回車換行符結(jié)束。下列代碼實(shí)現(xiàn)命令的發(fā)送功能。
private void WriteToNetStream(ref NetworkStream NetStream.string Command)
{? string stringToSend = Command + "\r\n";
?Byte[] arrayToSend = System.Text.Encoding.UTF8.GetBytes
??????? (stringToSend.ToCharArray());
?? NetStream.Write(arrayToSend,0,arrayToSend.Length);}
3.2 答應(yīng)碼的接收
SMTP服務(wù)器對每一個(gè)都發(fā)出響應(yīng)。下列代碼實(shí)現(xiàn)接受答應(yīng)碼的功能。
private string ReadFromNetStream(ref NetworkStream NetStream)
{ byte[] bb=new byte[512];
? NetStream.Read(bb,0,bb.Length);
? string read=System.Text.Encoding.UTF8.GetString(bb);}
?3.3 應(yīng)答碼檢查
發(fā)送郵件客戶端必須要檢查應(yīng)答碼,以判斷服務(wù)器是否已經(jīng)執(zhí)行了命令。如果服務(wù)器沒有執(zhí)行,則重新發(fā)送命令或采用其他措施。下列代碼可以檢查服務(wù)器是否正確地執(zhí)行了命令。
private string checkForError(string strMessage,string check)
{? if (strMessage.Indexof(check) == -1)
????? { return "err";}
else{return "correct";}}
上述方法的第一個(gè)參數(shù)是服務(wù)器返回信息,第二個(gè)參數(shù)是要檢查的答應(yīng)碼。如果服務(wù)器返回的信息中不存在第二個(gè)參數(shù)的字符串,則返回“err”,表明服務(wù)器沒正確執(zhí)行命令。
?3.4 發(fā)送郵件
客戶端發(fā)出DATA命令,在服務(wù)器作出354應(yīng)答后,即可開始郵件內(nèi)容的發(fā)送。郵件以<CRLF>.<CRLF>結(jié)束。下列代碼實(shí)現(xiàn)了郵件發(fā)送功能。
private void sendMail(ref NetworkStream NetStream, string message)
{?? Byte[] attayToSend = System.Text.Encoding.UTF8.GetBytes
(message.ToCharArray());
?? NetStream.Write(arrayToSend,0,arrayToSend.Length);}
4 郵件接收模塊的實(shí)現(xiàn)
POP允許本地郵件UA(User Agent,用戶代理)連接SERVER并將郵件取回到用戶本地系統(tǒng),用戶也在本地機(jī)上閱讀和響應(yīng)消息。POP3UA通過TCP/IP與服務(wù)器連接(通常使用端口110)。輸入用戶名和口令經(jīng)過認(rèn)證后,UA可通過POP3命令取回或刪除郵件。POP3僅僅是接收協(xié)議。POP3UA使用SMTP向服務(wù)器發(fā)送郵件。
利用POP3協(xié)議開發(fā)一個(gè)郵件接收程序,使用USER、PASS、STAT、LIST、RETR、DELE、QUIT命令。下面開始開發(fā)過程。
?4.1 發(fā)送命令碼
?在此發(fā)送命令碼也使用的是ASCII碼。下列方法用于向服務(wù)器發(fā)送命令碼:
private void WriteToNetStream(ref NetworkStream NetStream.string Command)
{?? string stringToSend = Command + "\r\n";
??? Byte[]arrayToSend= System.Text.Encoding.ASCII.GetBytes
??????????? (stringToSend.ToCharArray());
??? NetStream.Write(arrayToSend,0,arrayToSend.Length);}
4.2 接收服務(wù)器應(yīng)答
一般而言,接收服務(wù)器應(yīng)答,既可使用ASCII碼,也可以使用UTF8碼,這里使用的是ASCII碼。下列方法用于接受服務(wù)器的應(yīng)答。
private string ReadFromNetStream(ref NetworkStream NetStream)
{? byte[] bb=new byte[1024];
?? NetStream.Read(bb,0,bb.Length);
?? string read=System.Text.Encoding.ASCII.GetString(bb);
?? return read;}
4.3 接受郵件
在此接收郵件用UTF8碼,當(dāng)遇到<CRKF>.<CRLF>,則結(jié)束讀取數(shù)據(jù)。下列代碼用于接收郵件:
private void ReadMail(ref NetworkStream NetStream,int number)
{? int k=0;
?? bool check=false;
?? byte[] bb=new byte[6400];
?? while(!check)
???? { k=NetStream.Read(bb,0,bb.Length);
?? string read=System.Text.Encoding.UTF8.GetString(bb,0,k);
?????? int x= read.IndexOf("\r\n.\r\n");
?????? if(x!=-1)
????????? { check=true;}
????????? richTextBox2.AppendText(read);
????????? WriteToNetStream(ref
?NetStream,"DELE "+number.ToString());
????????? string back=ReadFromNetStream(ref NetStream );
??????? richTextBox1.AppendText("DELE "+number.ToString()
+"命令應(yīng)答:"+back+"\r\n"); }}
5 結(jié)束語
把郵件發(fā)送模塊和郵件接收模塊合起來,就成為一個(gè)電子郵件的客戶端程序。在實(shí)際應(yīng)用中,在上面基礎(chǔ)上加以改進(jìn),如果再進(jìn)一步結(jié)合數(shù)據(jù)庫技術(shù),這可以開發(fā)出易用、可靠的電子郵件的客戶端程序。
(收稿日期:2004-02-25;電子郵件:zhouhuaqing@peoplemail.com.cn)
Programming e-mail client with c#.net
Abstract: Develop a program for e-mail client on the flat of .net with C#. C# is a new language of visual studio .net , is modern, object-oriented and is safe in type. Realize the network correspondence depending Socket programming. Expatiate the principle of SMTP(Simple Mail Transfer Protocol) and POP3(Post office Protocol), then explain developing a program to send e-mail according to the SMTP agreement development E-mail mail customer and carry to receive the procedure developing a program to receive e-mail according to the POP3 in detail.
Key Words: Socket; Simple Mail Transfer Protocol; Post Office Protocol
參 考 文 獻(xiàn)
[1]謝希仁. 計(jì)算機(jī)網(wǎng)絡(luò). 第2版. 北京:清華大學(xué)出版社,1999
[2]Charles Petzold. Windows程序設(shè)計(jì). 第5版. 北京:北京大學(xué)出版社,1999
[3]Anthony Jones 和 Jim Ohlund.? Windows網(wǎng)絡(luò)編程. 第2版. 北京:清華大學(xué)出版社,2002