這兩個程序的操作都很簡單。這兩個程序叫做VirPro01a和VirPro01b,分別與上面討論的假定的情形中的程序A和程序B對應。
程序VirPro01a
VirPro01a程序被設計為把POP3電子郵件服務器作為公共的電子郵件服務器(秘密電子郵件帳號的服務器可以是任何類型的,例如,它可以是典型的WebMail服務器)。本程序在WinXP下使用SDK 1.4.2測試通過。
實例變量
VirPro01a類的開頭定義了一個實例變量列表:
class VirPro01a extends Frame{
String dataPath = "./Messages/";
int numberMsgs = 0;
int msgCounter = 0;
int msgNumber;
String uidl = "";//唯一的POP3消息ID
BufferedReader inputStream;
PrintWriter outputStream;
Socket socket;
String pathFileName;
dataPath變量包含對本地工作文件夾的指針,該文件夾是存儲等待病毒掃描並轉發到秘密電子郵件帳號的消息的地方。
你可能希望使用另一個不同的文件夾。如果需要這樣做,簡單地提供路徑和文件夾名稱(作為字符串)。你可以發現,我的工作文件夾叫做Messages,它是用包含程序的類文件的文件夾的相對路徑指定的。你也可以使用絕對路徑。
剩余的實例變量都是程序用於不同目的的簡單工作變量。
Main方法
下面的main方法確認正確的命令行參數數量,並使用這些參數來實例化VirPro01a類的一個對象。
public static void main(String[] args){
if(args.length != 3){
System.out.println("Usage: java VirPro01a "+ "pubServer userName password");
System.exit(0);
}// if結束
new VirPro01a(args[0],args[1],args[2]);
}// main結束
構造函數
它的構造函數如下:
VirPro01a(String server,String userName, String password){
int port = 110; //pop3郵件端口
try{
//得到套接字,連接到特定服務器的特定端口
socket = new Socket(server,port);
//從套接字得到輸入流
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//從套接字得到輸出流
outputStream = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
//連接後在命令行屏幕上顯示從服務器接收到的消息
String connectMsg = validateOneLine();
System.out.println("Connected to server "+ connectMsg);
//現在通訊進程處於AUTHORIZATION 狀態。向服務器發送用戶名和密碼。
//命令采用明文、大寫的方式發送。命令後面帶有參數。發送命令。
outputStream.println("USER " + userName);
//得到響應,並確認響應是+OK而不是-ERR。
String userResponse = validateOneLine();
//在命令行屏幕顯示響應
System.out.println("USER " + userResponse);
//向服務器發送密碼
outputStream.println("PASS " + password);
//驗證服務器的響應是+OK 。在過程中顯示響應。
System.out.println("PASS " + validateOneLine());
}catch(Exception e){e.printStackTrace();}
上面的代碼建立了與公共電子郵件服務器的通訊路徑。
WindowListener
下面的代碼使用匿名類實例化了並注冊了一個WindowListener對象,為頁面右上角的“Close”按鈕服務。
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
//結束與服務器的對話
outputStream.println("QUIT");
String quitResponse =validateOneLine();
//在命令行屏幕上顯示響應
System.out.println("QUIT " + quitResponse);
try{
socket.close();
}catch(Exception ex){ System.out.println("\n" + ex);}
System.exit(0);
}// windowClosing結束
}// WindowAdapter()結束
);// addWindowListener結束
Final型的本地變量
下面的代碼聲明並初始化了構造函數中的兩個本地變量:
final Button startButton =new Button("Start");
final TextArea textArea =new TextArea(20,50);
這兩個本地變量包含了圖1所示的按鈕和文本區域的指針。(請注意,這兩個本地變量必須使用final標記,因為它們能夠被匿名類中定義的代碼訪問。匿名類或本地類中的代碼不能訪問非final本地變量。)
注冊ActionListener
下面的代碼顯示了用於實例化和注冊按鈕的ActionListener對象的匿名類:
startButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
try{
//現在通訊進程處於TRANSACTION狀態。檢索並保存消息
if(numberMsgs == 0){
outputStream.println("STAT");
String stat = validateOneLine();
//得到消息的數量(字符串)
String numberMsgsStr =stat.substring(4,stat.indexOf(" ",5));
//把字符串轉換為整型
numberMsgs = Integer.parseInt(numberMsgsStr);
}// 如果numberMsgs == 0終止
//注意:Msg數量從1而不是0開始。檢索並保存每個消息。
//每個消息以新行的句點結束
msgNumber = msgCounter + 1;
if(msgNumber <= numberMsgs){
//處理下一個消息。得到並保存來自服務器的消息唯一標識,
//驗證響應
outputStream.println("UIDL " + msgNumber);
uidl = validateOneLine();
//打開輸出文件保存消息。使用UIDL作為文件名
pathFileName = dataPath + uidl;
DataOutputStream dataOut =new DataOutputStream(new FileOutputStream(pathFileName));
//發送RETR命令開始消息檢索進程
outputStream.println("RETR " + msgNumber);
//驗證響應
String retrResponse =validateOneLine();
//從服務器讀取消息的第一行
String msgLine =inputStream.readLine();
//繼續讀取消息直到遇到第一個“.”符號。它標識消息結束。
while(!(msgLine.equals("."))){
//把數據行寫入輸出文件並讀取下一行。
//在寫入輸出文件的時候插入新行的字符。
dataOut.writeBytes(msgLine + "\n");
msgLine = inputStream.readLine();
}// while結束
//關閉輸出文件。現在消息存儲在以服務器提供的
//唯一ID為文件名的本地文件中。
dataOut.close();
//顯示過程
textArea.append(msgNumber + "\n");
//增加消息計數器,為下一個消息作准備
msgCounter++;
//禁止用戶為每個新消息按下按鈕
Toolkit.getDefaultToolkit().getSystemEventQueue().
postEvent(new ActionEvent(startButton,ActionEvent. ACTION_PERFORMED,"Start/Next"));
}//如果msgNumber <= numberMsgs就終止
else{//msgNumber > numberMsgs
//沒有更多消息了。禁止 Start/Next 按鈕
startButton.setEnabled(false);
//提示用戶
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
}// else終止
}// try終止
catch(Exception ex){ ex.printStackTrace();}
}// actionPerformed終止
}// ActionListener終止
);// addActionListener終止
ActionListener對象的目的是下載公共電子郵件服務器上的所有消息,把每個單獨的消息放入不同的文件中,並把這些文件存儲在工作文件夾中。
配置GUI
下面的代碼配置了GUI,它多個組件放在頁面上,設置頁面的大小,並使頁面可視。
add(startButton);
add(textArea);
textArea.setText("");
setLayout(new FlowLayout());
setTitle("Copyright 2004, R.G.Baldwin");
setBounds(274,0,400,400);
//使GUI可視
setVisible(true);
}//構造函數結束
上面的代碼同時表明VirPro01a類的構造函數的結束。
VirPro01b程序
VirPro01b程序包含大量的與前面程序不同的信息。
前面提到,這是設計用於一起使用的兩個程序之一,它們與良好的病毒掃描程序一起防止病毒感染電子郵件收件箱。
這個程序需要在VirPro01a運行之後,病毒檢測程序已經確保了VirPro01a的輸出不包含病毒(或者包含病毒的文件從工作文件夾中刪除了),才能運行。
這個程序把電子郵件消息轉發到秘密的電子郵件帳號。在我的系統中,這個過程相對較慢,使用線纜調制解調器,大約四秒鐘轉發一條消息。
這個程序在WinXP下使用SDK 1.4.2測試通過。
forwardEmailMsg方法
我的解釋將從該程序中最復雜的部分開始。這個方法叫做forwardEmailMsg。其中的一部分復雜性來自它對於沒有正式說明文件的Sun提供的一個類(sun.net.smtp.SmtpClient)的大量依賴。這是發送電子郵件消息的一個非常有用的類,被包含在SDK 1.4.2中。但是,它好像沒有被包含在SDK 1.4.2文檔中,希望在這個類的文檔中找到有用的消息非常困難。
文檔在哪兒?
我能夠找到的最好的文檔是下面的鏈接http://swig.stanford.edu/pub/java/javadoc/sun/net/smtp/SmtpClient.html。上面的Web站點對於這個類的操作描述是:“這個類實現了SMTP客戶端。為了發送郵件,你需要建立一個新的SmtpClient,調用‘to’方法添加目的地,調用‘from’方法為發送者命名,調用startMessage返回寫入消息(帶有RFC733頭信息)的流,最後關閉SmtpClient。”除了這些簡短的描述之外,幾乎沒有其它有用的信息。但是,它已經提供了至關重要的信息,例如方法的名稱、參數類型、返回類型等等。
一些警告
如果你在Web上搜索這個類,你將找到大量的使用它的警告信息,其中主要的是Sun並不支持它。如果你在設計一個需要長期支持和維護的產品,支持信息可能是涉及要害的。但是,我編寫的這個程序只供自己使用以防護電子郵件病毒。因此,我不關心長期的支持和維護。這個類容易使用,因此它是個很好的選擇。
ReadLines方法
ForwardEmailMsg方法使用了另一個叫做readLines的方法。因此,在解釋forwardEmailMsg方法之前我將解釋readLines方法。ReadLines方法的開始如下:
private String readLines(String pathFileName,
String firstLine,String lastLine){
該方法的參數
該方法接收下面三個String(字符串)參數:
· pathFileName
· firstLine
· lastLine
設計該方法的主要目的是讓你能夠從文本文件中提取連續的文本行,從第一行開始的內容到希望得到的最後一行為止。
ReadLines方法從文本文件中讀取和保存文本行,它從firstLine定義的行開始,以lastLine定義的行結束。如果lastLine為null,數據就一直保存到文件結束;如果firstLine為null,數據保存為文件中開始的第一行。
來自文件中的文本行保存的方式為:把它們連接成一個String對象,在每一行的結束向字符串插入newline(新行標識)。文件的名稱和路徑由pathFileName指定。
該方法的剩余部分
ReadLines方法剩余的代碼如下:
StringBuffer strBuf = new StringBuffer();
try{
BufferedReader inDataMsg = new BufferedReader(new FileReader(pathFileName));
String data;
boolean isSave = false;
while((data = inDataMsg.readLine())!= null){
if(((firstLine == null) ||(data.startsWith(firstLine))) && (isSave == false)){
isSave = true;
}// if結束
if(isSave){ strBuf.append(data + "\n");
}// if結束
if((lastLine != null) && (data.startsWith(lastLine))){
break;//不需要讀取更多信息了
}// if結束
}// while循環結束
inDataMsg.close();//關閉文件
}catch(Exception e){e.printStackTrace();}
return new String(strBuf);
}//讀取數據行結束
消息的格式
為了使我設計的readLines更加通用,我計劃把它設計為從未處理的消息中提取文本行,因此看一個消息的例子對你來說可能是有益的。
下圖顯示了一個為了演示目的而發送給自己的未處理的簡單的電子郵件消息文本(請注意,我在文本中插入和大量的行分隔符,這樣才能顯示如下)。
Return-Path: <[email protected]>
Received: from ms-smtp-01-eri0.texas.rr.com
(ms-smtp-01.texas.rr.com [24.93.47.40])
by omnistarhost.com (8.11.6/8.11.6)
with ESMTP id i1G1PeX29829
for <[email protected]>; Sun,
15 Feb 2004 19:25:40 -0600
Received: from DickBaldwin.com
(cs24339-166.austin.rr.com [24.243.39.166])
by ms-smtp-01-eri0.texas.rr.com
(8.12.10/8.12.7) with ESMTP id i1G1JHLc003760
for <[email protected]>;
Sun, 15 Feb 2004 19:19:20 -0600 (CST)
Message-ID: <[email protected]>
Date: Sun, 15 Feb 2004 19:19:16 -0600
From: Richard Baldwin <[email protected]>
Reply-To: [email protected]
User-Agent: Mozilla/5.0 (Windows; U; Windows
NT 5.1; en-US; rv:1.4) Gecko/20030624
Netscape/7.1 (ax)
X-Accept-Language: en-us, en
MIME-Version: 1.0
To: [email protected]
Subject: A test msg to illustrate messagestructure
Content-Type: text/plain;
charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
X-MailScanner-Information: Please
contact the ISP for more information
X-MailScanner: Found to be clean
Status:
This is a test message.
--
Richard G. Baldwin (Dick Baldwin)
Home of Baldwin's on-line Java Tutorials
http://www.DickBaldwin.com
Professor of Computer Information Technology
Austin Community College
(512) 223-4758 or (512) 250-8682
mailto:[email protected]
http://www.austincc.edu/baldwin/
在圖1中我使用加亮顯示了兩行。
主題行和狀態行
在forwardEmailMsg方法的代碼中主題行將扮演重要的角色。在程序中,在消息被轉發到秘密的電子郵件帳號之前,消息編號被插入到主題行中。
狀態行之前的信息都被認為是消息頭部信息。狀態行後面的部分都被認為是消息主體。如果你在文本編輯器中閱讀未處理的電子郵件消息,你一般注意電子郵件主體中的消息。
返回到forwardEmailMsg方法
private boolean forwardEmailMsg(String recipient,
String smtpServer,String tag, String pathFileName){
ForwardEmailMsg方法輸入的參數是:
· recipient(接收者)——接收消息的人的電子郵件地址。在例子中它是秘密電子郵件帳號。它是程序啟動時的命令行參數。
· smtpServer(smtp服務器)——對你進行身份驗證以發送電子郵件消息的smtp服務器的標識。它也是程序啟動時的命令行參數之一。
· tag(標記)——它是消息被轉發到接收者之前插入主題開始部分的字符串。我使用的是來自公共電子郵件服務器的原始消息編號,你可以改變它。
· pathFileName(文件名和路徑)——將轉發到秘密電子郵件帳號的消息的標識。
本地變量
下面的方法聲明並初始化了方法後面將使用的本地變量:
StringBuffer message = new StringBuffer("No message found");
SmtpClient構造函數
前面提到的文檔提供了類的兩個重載的構造函數:
· 一個用於建立未初始化的SMTP客戶端。
· 另一個用於建立連接到特定主機的新SMTP客戶端。
try{
SmtpClient smtp =new SmtpClient(smtpServer);
上面的代碼使用了第二個構造函數,它實例化一個連接到用戶在命令行參數中提供的smtpServer的SmtpClient對象。
from()方法
盡管起面提到的文檔沒有提高該方法的描述,但是可以直觀地推導得出該方法需要接收發送消息的人的電子郵件地址。
smtp.from(recipient);
它需要一個可能存在的電子郵件地址
在例子中,實際上根本不關心電子郵件是誰發送的,也就是說它是一個可能存在的電子郵件地址。秘密電子郵件帳號的客戶端程序將從消息頭部的From:行中得到消息的發送者。
(但是有必要給from方法傳遞一個可能存在的電子郵件地址。否則它會產生一個異常。你可能非常奇怪,為什麼from方法不執行什麼操作卻存在。你傳遞到from方法的電子郵件地址將顯示在消息頭部的Return-Path中,圖3中有例子。)
由於我知道接收者的電子郵件地址是可能存在電子郵件地址,我就把接收者的電子郵件地址作為消息的發送者傳遞到from方法中。
to()方法
這個方法需要把預計的消息接收者的電子郵件地址作為參數。代碼把接收者的電子郵件地址傳遞給to()方法:
smtp.to(recipient);
StartMessage方法
這個方法得到並返回一個PrintStream對象用於建立消息:
PrintStream msg = smtp.startMessage();
上面的代碼調用startMessage方法得到PrintStream對象的指針,並把該指針保存在變量msg中。
得到字符串形式的全部的消息
下面的代碼調用readLines方法得到來自pathFileName指定的文件的全部的消息,並把它轉換成一個String對象:
message = new StringBuffer(readLines(pathFileName,null,null));
請注意,上面的代碼給readLines傳遞了兩個空(null)參數,指示它使用文件中的所有文本行構建並返回字符串。
把標記插入主題行
下面的代碼執行如下操作:
· 把輸入參數tag的值直接地插入主題行,在當前主題之前。
· 把消息從StringBuffer對象轉換成String對象,並調用println方法把它插入輸出流。
message = message.insert0(message.indexOf("Subject: ")+9,tag);
msg.println(new String(message));
發送消息
下面的代碼調用SmtpClient對象上的closeServer方法:
//關閉流並發送消息
smtp.closeServer();
return true;
}catch( Exception e ){
System.out.println("\n" + e);
System.out.println("Forwarding email");
Toolkit.getDefaultToolkit().beep();
try{
Thread.currentThread().sleep(300);
}catch(Exception ex){
System.out.println(ex);
}// catch結束
Toolkit.getDefaultToolkit().beep();
return false;
}// catch結束
}//end forwardEmailMsg
這段代碼會引起消息被發送到接收者,盡管前面提到的文檔中沒有明確地說明。
成功時返回true
如果上面的代碼返回true就表明調用的方法發送了消息,並且現在可以從服務器刪除該消息,從工作文件夾移動到文檔文件夾了。
異常
如果forwardEmailMsg方法中調用的任何SmtpClient方法產生了異常,它都會被代碼中的catch代碼塊捕捉到。
Catch代碼塊輸出一些診斷信息、提醒用戶並返回False。它表明調用方法並沒有轉發消息,不能從公共電子郵件服務器上刪除它,不能移動到文檔文件夾中。
重新運行VirPro01b來試圖發送消息可能有用,也可能沒有用,這依賴於異常的具體種類。如果引起異常的是嚴重的網絡擁塞而導致的超時,那麼重新運行程序發送消息是很好的選擇。對於其它一些更嚴重的問題,重新運行可能不會成功,應該在前面提到的文本編輯器中檢查消息。
MoveFile方法
在進入VirPro01b類的構造函數的細節前我還要討論另一個有用的方法。下面的代碼完整地顯示了moveFile方法:
private void moveFile(String pathFileName,String archivePath){
String fileName = pathFileName.substring(
pathFileName.lastIndexOf('/') + 1);
String archivePathFileName =archivePath + fileName;
boolean moved =new File(pathFileName).renameTo(
new File(archivePathFileName));
if(!moved)System.out.println("Unable to move " + new File(pathFileName) + "\nto " + new File(archivePathFileName));
}// moveFile方法結束
輸入參數
這個方法接收兩個輸入參數:
· PathFileName(文件名和路徑)——文件的名稱和當前位置。
· ArchivePath(文檔路徑)——文件的目的地。
這個方法用於把消息文件從工作文件夾移動到文檔文件夾。它把文件從pathFileName指定的當前位置移動到archivePath指定的新位置。
如果操作成功,File類的reName方法將返回true,否則返回false。例如,如果在目標文件夾中已經存在一個同名的文件,操作將返回false,並且上面的代碼將輸出一個消息表明移動操作沒有成功。
VirPro01b類
下面的代碼是VirPro01b的開始部分,包括一些實例變量的聲明和初始化。其中一些注釋表明了實例變量的使用方法,因此我沒有進一步解釋它們。
class VirPro01b extends Frame{
//下面是程序啟動時保密電子郵件提供的ID它是作為命令行參數提供的
String recipient;
//下面是存儲等待病毒掃描和轉發的消息文件的本地文件夾。
//你可以改變它。
String dataPath = "./Messages/";
//下面是存儲掃描後並轉發到秘密電子郵件帳號的消息的本地文件夾。
//從公共電子郵件服務器上刪除後它們被自動地移動到這個文件夾。
//你可能需要周期性地清除這個文件夾。
String archivePath = "./Archives/";
//下面是程序用於不同目的的工作變量。
BufferedReader inputStream;
PrintWriter outputStream;
Socket socket;
String pathFileName;
Vector msgToDelete = new Vector();
我要提醒你,你可以通過簡單地改變dataPath和archivePath的初始值來改變工作文件夾和文檔文件夾的位置和名稱。(在運行程序之前要確保新的文件夾已經建立了。)
Main方法
Main方法如下所示:
public static void main(String[] args){
if(args.length != 5){
System.out.println("Usage: java VirPro01b " + "pubServer userName password " + "secretServer smtpServer");
System.exit(0);
}// if結束
new VirPro01b(args[0],args[1],args[2], args[3],args[4]);
}// main結束
Main方法中的代碼確保了正確的命令行參數的數量,接著使用這些參數實例化VirPro01b了的一個對象。
VirPro01b類的構造函數
構造函數從把秘密電子郵件地址存儲在叫做recipient的實例變量開始,使它能夠被類中的其它方法訪問。其它的輸入參數只在構造函數內部使用,因此沒有必要把它們作為實例變量保存。
VirPro01b(final String server,final String userName,
final String password,String secretServer,
final String smtpServer){
recipient = secretServer;
但是你必須把它們聲明為final的,因為它們要被匿名類定義中的代碼訪問。
WindowListener對象
下面的代碼定義了一個匿名的類,並實例化了該類的一個匿名對象,它實現了WindowListener接口:
this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}// windowClosing結束
}// WindowAdapter()結束
);// addWindowListener結束
這個WindowListener對象注冊在頁面上,當用戶點擊“close”按鈕的時候,它引起程序終止。
GUI組件
下面的代碼實例化了用戶界面中的兩個按鈕和文本區域:
final Button startButton =new Button("Start");
final Button deleteButton = new Button("Delete Msg On Server");
final TextArea textArea =new TextArea(20,50);
同樣,這些指針也被聲明為final的,因為它們需要被匿名類的對象訪問。
“Start”按鈕上的ActionListener
當程序開始運行的時候,每個部分都被初始化好了,程序就等待用戶點擊“Start”按鈕了。當用戶點擊“Start”按鈕的時候,程序開始把電子郵件消息轉發到秘密的電子郵件帳號。
下面的代碼在“Start”按鈕上實例化並注冊了一個ActionListener用於處理消息的轉發。代碼顯示的是actionPerformed方法的前面一部分,該方法在用戶點擊“Start”按鈕的時候執行。
startButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
startButton.setEnabled(false);
File dataDir = new File(dataPath);
String[] dirList = dataDir.list();
上面的代碼立即使“Start”按鈕不能使用,以確保該按鈕只會被點擊一次。
目的
actionPerformed方法的目的是把工作文件夾中的所有消息文件轉換為電子郵件消息格式並把它們發送到秘密的電子郵件帳號。上面的代碼通過得到工作文件夾中的所有文件的列表開始這個過程。
(代碼假設工作文件夾中只包含消息文件。如果你在該文件夾中存儲了其它的文件,例如ReadMe.txt文件,你需要給上面的代碼添加過濾器以提取目錄列表中的消息文件。)
處理工作目錄中的所有文件
下面的代碼顯示了用於提取工作文件夾中的每個文件,把它轉換為電子郵件消息格式,並發送到秘密的電子郵件帳號的for循環的開始部分:
for(int msgCounter = 0;
msgCounter < dirList.length;msgCounter++){
String fileName =dirList[msgCounter];
pathFileName = dataPath + fileName;
得到消息的數量
我的代碼把每個轉發的消息的主題標記上來自公共電子郵件服務器的原始的消息編號(前面提到,你可以建立和使用其它的標記,唯一的要求是該標記必須是字符串型的)。
三個數字的字符串
下面的代碼得到原始的消息編號並把它格式化為三個數字的字符串:
String strMsgNumber =pathFileName.substring(pathFileName.indexOf(" "),pathFileName.lastIndexOf(" ")).trim();
int msgNumber = Integer.parseInt(strMsgNumber);
String msgNumberStr;
if(msgNumber < 10){
msgNumberStr = "00" + msgNumber;
}else if(msgNumber > 99){
msgNumberStr = "" + msgNumber;
}else{
msgNumberStr = "0" + msgNumber;
}// else結束
文件名中的信息
為了理解上面的代碼,我必須給出消息被寫入文件時,它的文件名的一些背景信息。下面是一個工作文件夾中的一個典型的文件名:
+OK 38 402fb6da00000098
這個文件名是如何構成的?
這個文件名是給服務器發送UIDL命令後,由從服務器上接收到的響應直接構成的。我相信這是所有POP3電子郵件服務器的標准響應信息。
前面三個字符是+OK,它表明命令被接收了(如果命令沒有被接收,響應將會以-ERR開始。你可以查看完整代碼中的validateOneLine方法找到更多的詳細信息)。
消息的編號
兩個空格之間的字符是公共電子郵件服務器在接收到命令時賦予消息的編號(據我所知,如果從服務器上刪除了編號較小的消息,消息的編號將會發生改變,換句話說,你每次訪問服務器下載消息時:
· 消息的編號從1開始。
· 消息是有序編號的。
· 順序的消息編號之間不會有空隙。
如果你回頭查看中的VirPro01a代碼,你會發現我下載了所有的消息而沒有刪除任何消息。如果程序要求刪除消息,我必須在刪除任何消息之前先下載所有的消息,以避免消息編號被重復。)
唯一標識符UIDL
文件名中第二個空格之後的長字符串是服務器給消息賦予的一個唯一的ID(同樣,據我所知,這個唯一的ID對於該服務器上的相同的電子郵件帳號的任何消息是永遠不會重復的,但是對於相同服務器上的不同的電子郵件帳號或不同的電子郵件服務器上的消息是可能重復的)。
PathFileName變量
上面的代碼中的pathFileName的值僅僅是帶有文件路徑的文件名。有了pathFileName之後,你就能夠理解上面的代碼如何提取消息編號,並把它轉換為包含消息編號的三個數字的字符串,例如001、063或169(如果某個時候在服務器上的消息數量多余999個,我就不得不擴展代碼以產生四個數字的消息編號字符串。這與幾年前的Y2K問題類似)。
把消息轉發到秘密的電子郵件帳號
下面的代碼調用forwardEmailMsg方法(前面已經討論過了)把消息文件中的信息格式化為電子郵件消息,並把它發送給秘密的電子郵件帳號:
boolean okToDelete =
forwardEmailMsg(recipient, smtpServer,
"{"+ msgNumberStr +"}",pathFileName);
回顧一下forwardEmailMsg方法,如果轉發操作成功,它就返回true,否則返回false。其返回值存儲在代碼的okToDelete變量中。
標記可刪除的消息
如果forwardEmailMsg方法返回true,下面的代碼就把標識消息文件的pathFileName添加到msgToDelete指向的Vector集合。該集合的內容用於以後從公共電子郵件服務器上刪除消息,還用於把消息文件從工作文件夾移動到文檔文件夾:
if(okToDelete){
textArea.append("Forwarded " +msgNumberStr + "\n");
msgToDelete.add(pathFileName);
}else{
textArea.append("Failed " +msgNumberStr + "\n");
}// else結束
}//目錄長度上的循環結束
不標記的消息
如果forwardEmailMsg返回false,消息文件的pathFileName就不會添加到集合中。其結果是該消息不會從電子郵件服務器上刪除,消息文件也不會移動到文檔文件夾中。
為用戶顯示信息
上面的代碼也在文本區域顯示信息,使用戶知道消息轉發到秘密的電子郵件帳號的嘗試是否成功。
循環結束
上面的代碼還表明了控制工作文件夾中的所有消息的處理過程的for循環的結束。
激活“Delete”按鈕
下面的代碼激活“Delete”按鈕,並在文本區域發布一個刪除消息:
deleteButton.setEnabled(true);
textArea.append("\nDo you want to "
+ "delete messages from server?\n");
激活“Delete”使得用戶能夠激活注冊在該按鈕上的ActionListener,用於從公共電子郵件服務器上刪除消息,並把消息文件從工作文件夾移動到文檔文件夾。
提醒用戶
下面的代碼發出三聲“嘟嘟”提醒用戶轉發過程完成了,可以決定是否刪除公共電子郵件服務器上的消息了:
try{
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
Thread.currentThread().sleep(300);
Toolkit.getDefaultToolkit().beep();
}catch(Exception ex){
ex.printStackTrace();
}// catch結束
}// actionPerformed結束
}// ActionListener結束
);// addActionListener結束
上面的代碼同時表明在“Start”按鈕上注冊的ActionListener實例的結束。
“Delete”按鈕上的ActionListener
下面的代碼顯示了實例化和注冊圖2中的“Delete”按鈕上ActionListener對象的代碼的開始部分:
deleteButton.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
deleteButton.setEnabled(false);
textArea.append("\n");
上面的代碼立即禁止了“Delete”按鈕,以確保它只會被激活一次。它還把文本區域中的選擇點(selection point)移動到文本區域的末尾。
連接到公共電子郵件服務器
下面的代碼得到公共電子郵件服務器的連接用於刪除服務器上的消息:
int port = 110; //pop3郵件端口
try{
//得到套接字,連接到特定服務器的特定端口
socket = new Socket(server,port);
//從套接字得到輸入流
inputStream = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
//從套接字得到輸出流
outputStream = new PrintWriter(new OutputStreamWriter(
socket.getOutputStream()),true);
//在連接後面的命令行屏幕上顯示從服務器接收到的消息
String connectMsg = validateOneLine();
System.out.println("Connected to server "+ connectMsg);
//現在通訊進程處於AUTHORIZATION 狀態。把用戶名和密碼
//發送給服務器。命令使用明文、大寫方式發送到服務器。
//某些命令後面需要跟著參數。發送命令。
outputStream.println("USER " + userName);
//得到響應,並確認響應是+OK而不是-ERR
String userResponse =validateOneLine();
//在命令行屏幕上顯示響應信息
System.out.println("USER "+ userResponse);
//向服務器發送密碼
outputStream.println("PASS "+ password);
//驗證服務器的響應是否是+OK。顯示響應結果
System.out.println("PASS "+ validateOneLine());
}catch(Exception ex){
ex.printStackTrace();
}//catch結束
從本質上說,上面的代碼與VirPro01a程序中的相同,我就不進一步討論了。
啟動消息刪除過程
下面的代碼啟動消息刪除過程:
· 從msgToDelete指向的Vector集合中提取消息標識信息。
· 從公共電子郵件服務器上刪除被標識的消息。
· 把對應的消息文件從公共文件夾移動到文檔文件夾。
for(int cnt = 0;cnt < msgToDelete.size();cnt++){
pathFileName =(String)msgToDelete.elementAt(cnt);
String strMsgNumber =pathFileName.substring(
pathFileName.indexOf(" "),
pathFileName.lastIndexOf(" ")).trim();
int msgNumber = Integer.parseInt(strMsgNumber);
消息編號是必須的
為了從服務器上刪除消息,該消息必須由服務器上的消息編號所標識。上面的代碼從存儲在Vector集合中的標識信息中提取消息編號。這段代碼對於msgToDelete指向的Vector集合中包含的每個消息標識都執行一次。
如何從服務器上刪除消息
從服務器上刪除消息是通過在TRANSACTION狀態時發送DELE命令標記供刪除的消息來完成的。該消息實際上是在客戶端向服務器發送QUIT命令,使服務器進入UPDATE狀態時才被真正地刪除了。如果程序在發送QUIT命令前過早地終止了,被標記的消息就不會從服務器上刪除。
標記供刪除的消息
下面的代碼中的通過執行語句(加亮行)標記供刪除的消息:
System.out.println("Deletion is temporarily disabled.");
//從服務器上刪除被臨時禁止了。在徹底了解自己在做什麼之前不要啟用它。
outputStream.println("DELE " + msgNumber);
//驗證並在GUI上顯示它
textArea.append("Msg: " + msgNumber + " "
+ validateOneLine()+"\n");
textArea.append("Marked:" + msgNumber + "\n");
注意
在你徹底地測試過這個程序並對它的行為感到滿意時才激活這個語句。如果激活了它,你可能由於刪除了某些消息,卻又沒有適當地把它們轉發到秘密的電子郵件帳號而造成某些電子郵件消息的丟失。
只要你不從服務器上刪除消息,你就可以使用正常的電子郵件客戶端程序閱讀它們。
移動消息文件
下面的代碼調用moveFile方法把消息文件從公共文件夾移動到文檔文件夾:
moveFile(pathFileName,archivePath);
}//結束msgToDelete.size()上的循環
上面的代碼還表明控制著msgToDelete所指向的Vector集合的內容所標識的所有消息的刪除和移動的循環的結束。
終止對話
下面的代碼公共發送QUIT命令終止了與公共電子郵件服務器的對話,它引起被標記的消息從服務器上被刪除:
outputStream.println("QUIT");
String quitResponse =validateOneLine();
System.out.println("QUIT " + quitResponse);
假如對QUIT命令的響應是+OK,服務器就進入UPDATE模式並刪除消息。如果響應是-ERR,服務器就不會進入UPDATE模式,並且消息不會被刪除。
關閉套接字並清除信息
下面的代碼關閉套接字並在圖2的文本區域顯示一些有用的消息:
try{
socket.close();
}catch(Exception ex){
ex.printStackTrace();
}// catch結束
textArea.append("\n\nMessages deleted from server.\n");
}//actionPerformed結束
}// ActionListener結束
);// addActionListener結束
上面的代碼同時表明注冊到Delete按鈕的ActionListener對象的實例化過程的結束。
配置GUI
下面的代碼配置了圖2中的GUI,它把多種組件放置在頁面上、設置了大小並使它可視。
add(startButton);
add(deleteButton);
deleteButton.setEnabled(false);
add(textArea);
textArea.setText("");
setLayout(new FlowLayout());
setTitle("Copyright 2004, R.G.Baldwin");
setBounds(274,0,400,400);
//使GUI可視
setVisible(true);
}//構造函數結束
}// VirPro01b類結束
上面的代碼表明構造函數的結束以及VirPro01b類的結束。