我們知道從Delphi 3 開始,它自帶的控件中有基于Internet開發(fā)的控件。如果我們充分利用這些控件開發(fā)Internet程序則可以簡(jiǎn)化編程工作,提高效率。鑒于目前POP3客戶端的軟件的種類繁多,(如Outlook Express,Foxmail 以及Web 方式下的各免費(fèi)郵局),而服務(wù)器端(除Unix Email系統(tǒng))很少公開原代碼,下面我就向大家著重介紹一下利用 Delphi 4中Internet控件組的TClientSocket 和TServerSocket 控件來(lái)實(shí)現(xiàn) Email POP3服務(wù)器端。如果您理解了Email POP3服務(wù)器的構(gòu)造,相信也可以依葫蘆畫瓢寫出Email SMTP服務(wù)器程序。在此基礎(chǔ)上加入多線程技術(shù)使服務(wù)器能同時(shí)處理多個(gè)客戶的連接請(qǐng)求,您就可以輕松地實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Email服務(wù)器了。
一. 設(shè)計(jì)思路 Email 系統(tǒng)采用C/S 結(jié)構(gòu)。當(dāng)用戶想發(fā)送郵件時(shí)或收取郵件時(shí)在客戶機(jī)上運(yùn)行任意一個(gè)客戶端程序,如Foxmail。在菜單’工具->選項(xiàng)’的郵件服務(wù)器里填上運(yùn)行我們服務(wù)器程序的主機(jī)名。服務(wù)器主機(jī)24小時(shí)一直運(yùn)行我們的服務(wù)器端程序,SMTP和POP3服務(wù)器程序分別在25端口和110端口偵聽連接請(qǐng)求。當(dāng)用戶發(fā)信時(shí),首先客戶端會(huì)與服務(wù)器端建立Socket連接。然后開始一應(yīng)一答的Client/Server間的通信。發(fā)信和收信時(shí)建立連接后服務(wù)器端分別要發(fā)送一個(gè)’250 OK’ 和’+OK pop3 server is ready ’的應(yīng)答。客戶端收到此應(yīng)答后開始發(fā)送SMTP或POP3命令。POP3通信時(shí)一般最開始的命令是’user ‘和’pass’或’ apop’用以進(jìn)行身份驗(yàn)證。注意由于POP3會(huì)話有3個(gè)狀態(tài),某些命令只在某特定狀態(tài)下有效。當(dāng)用戶進(jìn)行完所有的操作后發(fā)送一個(gè)’quit’命令。服務(wù)器端收到此命令即終止此次socket連接并繼續(xù)偵聽其他的連接請(qǐng)求。注意:POP3通信時(shí)客戶端在Transaction狀態(tài)下’quit’則進(jìn)入update狀態(tài)。如果從Authorization狀態(tài)下’quit’則終止通信,而不進(jìn)入U(xiǎn)pdate狀態(tài)。如果客戶端不通過(guò)’quit’命令終止連接,POP3會(huì)話不會(huì)進(jìn)入U(xiǎn)pdate狀態(tài)。而只有在Update狀態(tài)下收到’quit’命令后服務(wù)器才會(huì)在斷連前把標(biāo)志為已刪的郵件進(jìn)行物理刪除。 二. 代碼實(shí)現(xiàn)(以POP3為例) 自定義TPOP類的描述:
SessionState = ( Init,Authorization, Transaction,Update); TPop=class (TComponent) public UserName:string;//Email帳戶名 PassWord:string; //Email口令 ReceText:Pchar; //server端收到的字符串 PopState:SessionState; //pop狀態(tài): init or authorization or transaction or update MsgCount:integer; //郵件總數(shù) SizeCount:integer; //郵件總大小 ReplyString:string;//服務(wù)器端發(fā)送的應(yīng)答信息 DeleIndex:byte;//用戶要?jiǎng)h的郵件序號(hào) ListIndex:byte;//list方法 的參數(shù): 用戶要列出的序號(hào)為listindex的郵件信息 RetrIndex:byte;//retr方法的參數(shù): 用戶要取序號(hào)為retrindex的郵件 TopIndex:byte; //top方法的參數(shù) QuitFlag:boolean;//用戶如果通過(guò)quit命斷連則此變量為true; 反之(此時(shí)要把f_dele都置回0) OldMsgCount:integer;//舊郵件數(shù):Last 命令返回 //郵件頭的各個(gè)域 HMsgId:string; HReplyTo:string; HDate:string; HFrom:string; HTo:string; HSubject:string; HMIME_Ver:real; HContent_Type:string; HContent_Transfer_Encoding:string; HText:string; //所有POP3服務(wù)器必須支持的命令 procedure user; function pass:boolean; procedure stat; procedure dele; procedure list; procedure retr; procedure noop; procedure rset; procedure aquit; procedure tquit; //擴(kuò)展的可選擇實(shí)現(xiàn)的POP3 命令 procedure top; procedure last; procedure apop; procedure uidl; end; 1. 建立連接 我們可以看到利用了Tclientsocket后客戶端請(qǐng)求建立連接只需下面的代碼。 with ClientSocket do begin Host := Server; Active := True; end; 服務(wù)器端利用TserverSocket,一直在偵聽110端口,若客戶端有連接請(qǐng)求,則ServerSocketAccept事件會(huì)被激活,建立起連接。
procedure TMyForm.ServerSocketAccept(Sender: TObject; Socket: TCustomWinSocket); begin Statusbar1.Panels[0].Text := '連接到 ' + Socket.RemoteAddress; //pop對(duì)象初始化 pop:=TPop.Create(nil); pop.PopState:=init; pop.LoginResult:=false; pop.QuitFlag:=false; ServerSocket.Socket.Connections[0] .sendtext('+OK ibonc pop3 server is ready'+crlf); end;
2. 通信 服務(wù)器端收到客戶端發(fā)來(lái)的信息,則會(huì)激活ServerSocketClientRead事件,通過(guò)ServerSocket的Socket.ReceiveText可以得到信息的內(nèi)容。
procedure TMyForm.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket); var temp_command :string; //存放接收到的命令行,并做去crlf的處理 begin temp_command:=Socket.ReceiveText; //to remove the crlf in command line temp_command:=trim(copy(temp_command,1, pos(crlf,temp_command)-1)); pop.ReceText:=pchar(temp_command); if pop.popstate=init then if strLIComp(pop.ReceText,'user ',5)=0 then pop.user else ServerSocket.Socket.Connections[0] .sendtext('-ERR user name please') else if pop.popstate=authorization then begin if strLIComp(pop.ReceText,'pass ',5)=0 then pop.pass else if strIComp(pop.ReceText,'quit')=0 then pop.aquit else ServerSocket.Socket.Connections[0] .sendtext('-ERR pass word please'); end else if pop.popstate=transaction then begin if strIComp(pop.ReceText,'stat')=0 then pop.stat else if strLIComp(pop.ReceText,'dele ',5)=0 then pop.dele else if strLIComp(pop.ReceText,'list',4)=0 then pop.list else if strLIComp(pop.ReceText,'retr ',5)=0 then pop.retr else if strIComp(pop.ReceText,'noop')=0 then pop.noop else if strIComp(pop.ReceText,'rset')=0 then pop.rset else if strIComp(pop.ReceText,'quit')=0 then pop.tquit else if strIComp(pop.ReceText,'last')=0 then pop.last else if strLIComp(pop.ReceText, 'apop ',5)=0 then pop.apop else if strLIComp(pop.ReceText, 'uidl ',5)=0 then pop.uidl else ServerSocket.socket.connections[0] .sendtext('-ERR no such command yet'+crlf); end end;
3. 關(guān)閉連接 procedure TMyForm.ServerSocket ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin ServerSocket.Active := False; //如果client端沒(méi)有通過(guò)quit 命令斷連, 則在斷連時(shí)要把那些f_dele置為0 if pop.QuitFlag=False then begin MyForm.query11.Close; MyForm.query11.Params[0].Asstring:=pop.UserName; MyForm.query11.prepare; MyForm.query11.execsql; end; end; 三. 結(jié)語(yǔ)
由于Email系統(tǒng)與數(shù)據(jù)庫(kù)表結(jié)構(gòu)的緊密聯(lián)系,筆者沒(méi)有寫出各POP3命令的具體實(shí)現(xiàn)。相信讀者在認(rèn)真閱讀了RFC1939之后不難寫出實(shí)現(xiàn)函數(shù)。現(xiàn)在就動(dòng)手為你的公司寫一個(gè)自己的Email服務(wù)器吧!
|