服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java设计防病毒电子邮件程序之代码


  这两个程序的操作都很简单。这两个程序叫做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: <baldwin@dickbaldwin.com>
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 <baldwin@dickbaldwin.com>; 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 <baldwin@dickbaldwin.com>;
sun, 15 feb 2004 19:19:20 -0600 (cst)
message-id: <40301a94.6070504@dickbaldwin.com>
date: sun, 15 feb 2004 19:19:16 -0600
from: richard baldwin <baldwin@dickbaldwin.com>
reply-to: baldwin@dickbaldwin.com
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: baldwin@dickbaldwin.com
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:baldwin@dickbaldwin.com
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类的结束。

扫描关注微信公众号