|         out=null;       }       return out;    }    /**     * 将字节流中的信息发送给服务器     * 接收信息的是servlet:BusServer     * 返回信息为结果对象的输入流(注意并非结果对象本身)     */    public ObjectInputStream executeMethod(byte buf[])                                throws TunnelException{       ObjectInputStream in=null;       try{         //设置连接属性:可以输入和输出,不使用缓存         //在连接中使用jsessionid可以维持该会话          URL url=new URL("http://127.0.0.1:8080/"+                   "rmibs/BusServer;jsessionid="+jsessionid);         URLConnection con=url.openConnection();         con.setUseCaches(false);         con.setDoOutput(true);         con.setDoInput(true);         con.setRequestProperty("Content-type",                                "application/octet-stream");         con.setRequestProperty("Content-length",""+buf.length);         //取得输出流         DataOutputStream out=new DataOutputStream(                                      con.getOutputStream());         out.write(buf);         //发送         //服务器根据客户端的方法编号,session编号和传入参数(可以是对象)         //调用相应的方法,先给客户端发送  flag=方法编号         //然后发送  结果对象                  out.flush();         out.close();         //从输入流中取得结果并返回         in=new ObjectInputStream(con.getInputStream());         //先读报头信息         int flag=in.readInt();         //出错         if(flag==-1){           String msg=in.readUTF();           throw new TunnelException(msg);         }       }       catch(IOException ex)       {         ex.printStackTrace();         throw new TunnelException(ex.getMessage());       }       return in;    }    public String getWelcome()    {       return welcome;    }    public String getUserName()    {       return userName;    }    public void setUserName(String name)    {       userName=name;    }    public String getUserPass()    {       return userPass;    }    public void setUserPass(String pass)    {       userPass=pass;    } } 第五步:定义自己的网络异常类TunnelException.java(客户端和服务器端共用)。 (同C/S) 第六步:编写商业对象代理类BusObjectProxy.java,实际也是网络数据流的解调制类(客户端)。 该类的构造函数就是初始化:包括接收客户端的用户名、密码登录信息,然后调用父类的initialize()函数与服务器建立连接,向服务器发送9999报头和用户名、密码,请求服务器通过登录验证。其他的方法就是实现接口BusInterface中定义的所有业务逻辑。当然这个实现只是象征性地。在大型RMI程序设计中,需要在此为每个握手协议添加相应的代码。 package com.ql.rmibs;   import java.io.*; import java.util.Vector; /**  * 客户端代理,主要用来传递applet参数,  * 并返回当地对象  */ public class BusObjectProxy         extends TunnelClient implements BusInterface{     public BusObjectProxy(String name,String pass) throws TunnelException,IOException{     setUserName(name);     setUserPass(pass);     initialize();   }      /**     *实现接口中定义的方法     *代理负责将入口参数打包发送到服务器     *并返回从服务器获得的结果     *方法1-- 功能:获取指定目录的所有文件名和目录名     *           编号:1     *           入口参数:路径名,例如"d:\\s"     *           返回值:对象FileAndDirectory     */    public FileAndDirectory getAllFileName(String path){       FileAndDirectory fd=null;       try{         ByteArrayOutputStream baos=new ByteArrayOutputStream();           //告诉服务器调用远程方法的编号1,并随后发送参数         ObjectOutputStream out=inputHeader(baos,1);         out.writeObject(path);           /*如果调用指定的方法:MethodNumber,附带的参数为一对象:MyObj则这样写:           ObjectOutputStream out=inputHeader(baos,MethodNumber);           out.writeObject(MyObj); */           //先执行输出,然后获得输入in         ObjectInputStream in=executeMethod(baos.toByteArray());         //从输入流中取得执行的结果         fd=(FileAndDirectory)in.readObject();         out.close();         in.close();       }       catch(Exception ex){         ex.printStackTrace();       }       return fd;    }   } 第七步:编写服务器端负责调制的类的网络层封装部分TunnelServer.java(服务器)。 主要功能:由于网络部分由HTTP协议代劳,因此,该部分代码相对简单,主要完成了B/S中服务器端对信息处理的公共部分。包括信息的接收、分发,客户端SESSION的管理,另外,实现了两个握手协议(9999和0),初始化整个对话,包括验证客户登录信息、为客户对话绑定商业对象实例。实现的核心代码如下: package com.ql.rmibs; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.http.HttpSessionBindingListener; import java.io.*; import java.util.*; import java.net.*;   //服务器端抽象类 public abstract class TunnelServer extends HttpServlet{    //服务器端对象实例化后,存放在一个Hashtable的结构中    //此Hashtable又存放在一个session中    //HASHTABLENAME用来标识放在session中的哈希表    static final String HASHTABLENAME="BusObjectTable";      public void service(HttpServletRequest req,                 HttpServletResponse res)                 throws ServletException,java.io.IOException{      //怎样保证获取正确的Session?      //答案:将第一次获取到的sessionid加入到URL中即可。      //方法:http://11.11.11.11/servlet/BusServer;jsessionid=取客户端的输入的对象流      ObjectInputStream in=new ObjectInputStream(req.getInputStream());        //参数true的作用是如果没有则创建新的对象      HttpSession session=req.getSession(true);      Hashtable busobjectTable=(Hashtable)                  session.getAttribute(HASHTABLENAME);        if (busobjectTable==null)      {        busobjectTable=new java.util.Hashtable();        session.setAttribute(HASHTABLENAME,busobjectTable);      }        //设置输出流      res.setContentType("application/octet-stream");        ByteArrayOutputStream byteOut=new ByteArrayOutputStream();      ObjectOutputStream out=new ObjectOutputStream(byteOut);        int flag=in.readInt();  //方法编号        Object serverbusObject;  //商业对象实例      int objectCode;          //商业对象编号        switch(flag){         case 9999: //验证权限,获取session           try{             String userName=(String)in.readObject();             String userPass=(String)in.readObject();             if(correctLogin(userName,userPass)){                String welcome="you are welcome!";                String SessionID=session.getId();                out.writeInt(flag);                out.writeUTF(SessionID);                out.writeUTF(welcome);             }else{                out.writeInt(-1);                out.writeUTF("用户名和密码错误!");             }           }catch(Exception ex){             ex.printStackTrace();           }         break;         case 0:  //初始化          //取得服务器端对象的实例          serverbusObject=getRealObject();          out.writeInt(flag);          objectCode=serverbusObject.hashCode();          busobjectTable.put(new Integer(objectCode),serverbusObject);          out.writeInt(objectCode);         break;         default: //不为0表示对一个方法的调用           objectCode=in.readInt();           serverbusObject=busobjectTable.get(new Integer(objectCode));           if(serverbusObject==null)           {              out.writeInt(-1);              out.writeUTF("对象"+Integer.toString(objectCode)+"已经不可用");           }           else           {             try             {                out.writeInt(flag);                out.flush();                executeMethod(serverbusObject,flag,in,out);             }             catch(Exception ex)             {                byteOut=new ByteArrayOutputStream();                out=new ObjectOutputStream(byteOut);                out.writeInt(-1);                out.writeUTF(ex.getMessage());             }           }      }      //数据传给字节流      out.flush();      byte[] buf=byteOut.toByteArray();      res.setContentLength(buf.length);      ServletOutputStream servletOut=res.getOutputStream();      servletOut.write(buf);      servletOut.flush();      servletOut.close();    }    //模拟系统登录过程    private boolean correctLogin(String name,String pass)    {       if(name.equals("qixiaorui") && pass.equals("20000203"))       {          return true;       }       else       {          return false;       }    }    public void init(ServletConfig cfg)    throws ServletException    {      super.init(cfg);    }    public void destroy()    {      super.destroy();    }    //两个必须在子类中实现的方法    public abstract Object getRealObject() throws ServletException;    public abstract void executeMethod(Object busObject,           int flag,ObjectInputStream in,           ObjectOutputStream out) throws Exception; } 第八步:编写服务器调制类BusServer.java(服务器)。 通过继承TunnelServer类,获取服务器端程序共有的功能,同时独立地实现了在网络层抽象类中定义的几个方法。把共用的和特有的方法分开,其主要目的是为了保证程序结构简单明了,程序设计者可以集中力量进行业务逻辑的描述与实现,而无须关心数据在网络上被如何封装、如何分发。 该类主要实现了抽象类TunnelServer中没有被实现的两个主要方法:即获取本地商业对象的实例以及根据不同报头执行不同的本地商业对象的相应方法。核心代码如下: package com.ql.rmibs; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.Vector; |