RMI大文件传输的一种思路

文件传输通常利用的是TCP协议,采用C/S的方式进行的,优点是效率高,传输过程可控制,但如果传输过程涉及一些可能会有变动的业务时,就可能要同时改动服务端与客户端。
如果直接用RMI进行文件传输,通常的方法是把文件弄成byte[],再传输,优点是简单,同时业务的变动可以只在服务端进行,可缺点也很明显,对于一个大文件,传输时就太占用内存了。

在我的毕业设计《邮件系统》中,我想利用RMI把核心部分作为服务,而web界面就可以不用关心业务逻辑了,附件传输就遇到了上面的问题,想了一下,有了一个思路可以结合上面两者的优点,就是仍然利用TCP socket进行传输,但是利用RMI代码动态下载的能力,将客户端部分的实例化放在服务端。具体方法如下。
首先是开放给客户端的接口
public interface UpLoadService extends Remote{
public UpLoader getUpLoader(String name) throws RemoteException;
}


public interface UpLoader {
public void upLoad(InputStream output);
}

UpLoadService是RMI提供的服务,UpLoader是服务端实例化后传给客户端使用的。
接口都是在服务端实现的。
然后就是接受文件的服务端FileServer
public class FileServer {
private int port;
private volatile boolean isRunning = false;
private ExecutorService exec = Executors.newCachedThreadPool();
private Map<String, String> map;

public FileServer(int port) {
  this.port = port;
  map = Collections.synchronizedMap(new HashMap<String, String>());// 使map支持同步
}

public String requested(String fileName) {
  String key = RandomUtils.randomString(16);
  map.put(key, fileName);

//每当有一个文件上传请求,将随机生成一个key,并与文件名建立映射,如果有更复杂的业务或安全要求,可以修改
//UpLoader将凭借这个key与服务端连接,确保正确性与安全性
  return key;
}

public synchronized void service() {
  if (!isRunning) {
   new Thread() {
    public void run() {
     try {
      ServerSocket server = new ServerSocket(port);
      System.out.println("文件传输服务已启动...");
      isRunning = true;
      while (isRunning) {
       Socket socket = server.accept();
       synchronized (this) {
        exec.execute(new FileTransferSession(socket));
       }
      }
     } catch (IOException e) {
      e.printStackTrace();
     }
    }
   }.start();
  } else {
   System.out.println("文件传输服务已启动");
  }
}

public synchronized void close() {
  if (!isRunning) {
   new Thread() {
    public void run() {
     System.out.println("正在结束文件传输服务...");
     exec.shutdown();
     isRunning = false;
     try {
      while (!exec.isTerminated()) {
       sleep(100);
      }
      System.out.println("已结束文件传输服务");
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }.start();
  } else {
   System.out.println("文件传输服务未启动");
  }
}

public int getPort(){
  return port;
}

class FileTransferSession implements Runnable {
  private Socket socket;

  public FileTransferSession(Socket socket) {
   this.socket = socket;
  }

  public void run() {
   InputStream is = null;
   BufferedReader reader = null;
   try {
    is = socket.getInputStream();
    reader = new BufferedReader(new InputStreamReader(is));
    String key = reader.readLine();
    String name = map.get(key);
    if (name != null) {//验证key的正确性
     PrintWriter writer = new PrintWriter(
       new OutputStreamWriter(socket.getOutputStream()));
     writer.println("OK");//确认
     writer.flush();
     FileOutputStream fileOutput = new FileOutputStream(name);
     byte[] buffer = new byte[1024];
     int count = 0;

      //开始接受文件
     for (int i = 0; count != -1; i++) {
      count = is.read(buffer);
      if (count != -1)
       fileOutput.write(buffer, 0, count);
     }
     fileOutput.close();
    }
    is.close();
    socket.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
}
}


这里的关键在那个map,它记录每个上传请求生成的key,UpLoader的实现会发送自己的key到服务端,从而实现了简单认证。下面是UpLoader的实现UpLoaderImpl。
public class UpLoaderImpl implements UpLoader, Serializable {
private static final long serialVersionUID = 1393382791756158190L;
private String ip;
private int port;
private String key;

public UpLoaderImpl(String ip, int port,String key) {
  this.ip = ip;
  this.port = port;
  this.key = key;
}

public void upLoad(InputStream input) {
  try {
   Socket s = new Socket(ip, port);
   OutputStream os = s.getOutputStream();
   PrintWriter writer = new PrintWriter(new OutputStreamWriter(os));
   BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
   byte[] buffer = new byte[1024];
   writer.println(key);//发送认证key
   writer.flush();
   if(reader.readLine().equals("OK")){//确认
    int count = 0;
    for (int i = 0; count != -1; i++) {
     count = input.read(buffer);
     if (count != -1)
      os.write(buffer, 0, count);//发送
    }
   }
   writer.close();
   reader.close();
   os.close();
   s.close();
  } catch (UnknownHostException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
}

}

UpLoadService的实现
public class UpLoadServiceImpl extends UnicastRemoteObject implements
  UpLoadService {

private static final long serialVersionUID = 8108929499018468087L;

private FileServer server;

public UpLoadServiceImpl(FileServer server) throws RemoteException {
  super();
  this.server = server;
}

public UpLoader getUpLoader(String name) throws RemoteException {
  String key = server.requested(name);
  return new UpLoaderImpl("127.0.0.1",server.getPort(),key);
}

}

接口的实现全是在服务端实现的,客户端只用知道接口的定义就可使用接口提供的服务,虽然还是使用socket进行的文件传输,但遇到业务逻辑的改变时,只要接口的定义不变,客户端完全不用作任何改变。
上面的代码看起来挺麻烦的,可兼顾了RMI,和Socket传送文件的优点,不知道还有没有更好的思路。

完整下载:http://download.csdn.net/source/1229822
两个Eclipse工程,分别作为服务端与客户端,不依赖其他http服务可直接可运行。

猜你喜欢

转载自blog.csdn.net/chch87/article/details/4095287