基于RMI服务传输大文件的完整解决方案
基于RMI服务传输大文件,分为上传和下载两种操作,需要注意的技术点主要有三方面,第一,RMI服务中传输的数据必须是可序列化的。第二,在传输大文件的过程中应该有进度提醒机制,对于大文件传输来说,这点很重要,因为大文件的传输时间周期往往比较长,必须实时告知用户大文件的传输进度。第三,针对大文件的读取方式,如果采用一次性将大文件读取到byte[]中是不现实的,因为这将受制于JVM的可用内存,会导致内存溢出的问题。
笔者实验的基于RMI服务传输大文件的解决方案,主要就是围绕以上三方面进行逐步解决的。下面将分别就上传大文件和下载大文件进行阐述。
1. 基于RMI服务上传大文件
1.1. 设计思路
上传大文件分为两个阶段,分别为“CS握手阶段”和“文件内容上传更新阶段”。
CS握手阶段,又可称之为客户端与服务端之间的基本信息交换阶段。客户端要确定待上传的本地文件和要上传到服务端的目标路径。读取本地文件,获取文件长度,将本地文件长度、本地文件路径和服务端目标路径等信息通过接口方法传递到服务端,然后由服务端生成具有唯一性的FileKey信息,并返回到客户端,此FileKey作为客户端与服务端之间数据传输通道的唯一标示。这些信息数据类型均为Java基本数据类型,因此都是可序列化的。此接口方法就称之为“握手接口”,通过CS握手,服务端能确定三件事:哪个文件要传输过来、这个文件的尺寸、这个文件将存放在哪里,而这三件刚好是完成文件上传的基础。
然后就是“文件内容上传更新阶段”。客户端打开文件后,每次读取一定量的文件内容,譬如每次读取(1024 * 1024 * 2)byte的数据,并将此批数据通过“上传更新”接口方法传输到服务端,由服务端写入输出流中,同时检查文件内容是否已经传输完毕,如果已经传输完毕,则执行持久化flush操作在服务端生成文件;客户端每次传输一批数据后,就更新“已传输数据总量”标示值,并与文件总长度进行比对计算,从而得到文件上传进度,实时告知用户。综上所述,客户端循环读取本地文件内容并传输到服务端,直到文件内容读取上传完毕为止。
1.2. 功能设计
1.2.1 文件上传相关状态信息的管理
大文件上传的过程中,在服务端,最重要的是文件上传过程相关状态信息的精确管理,譬如,文件总长度、已上传字节总数、文件存储路径等等。而且要保证在整个上传过程中数据的实时更新和绝对不能丢失,并且在文件上传完毕后及时清除这些信息,以避免服务端累计过多失效的状态数据。
鉴于此,我们设计了一个类RFileUploadTransfer来实现上述功能。代码如下:
/**
* Description: 文件上传过程相关状态信息封装类。<br>
* Copyright: Copyright (c) 2016<br>
* Company: 河南电力科学研究院智能电网所<br>
* @author shangbingbing 2016-01-01编写
* @version 1.0
*/
public class RFileUploadTransfer implements Serializable {
private static final long serialVersionUID = 1L;
private String fileKey;
//客户端文件路径
private String srcFilePath;
//服务端上传目标文件路径
private String destFilePath;
//文件尺寸
private int fileLength = 0;
//已传输字节总数
private int transferByteCount = 0;
//文件是否已经完整写入服务端磁盘中
private boolean isSaveFile = false;
private OutputStream out = null; public RFileUploadTransfer(String srcFilePath, int srcFileLength, String destFilePath) {
this.fileKey = UUID.randomUUID().toString();
this.srcFilePath = srcFilePath;
this.fileLength = srcFileLength;
this.destFilePath = destFilePath; File localFile = new File(this.destFilePath);
if(localFile.getParentFile().exists() == false) {
localFile.getParentFile().mkdirs();
}
try {
this.out = new FileOutputStream(localFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} public String getFileKey() {
return fileKey;
}
public String getSrcFilePath() {
return srcFilePath;
}
public String getDestFilePath() {
return destFilePath;
}
public boolean isSaveFile() {
return isSaveFile;
} public void addContentBytes(byte[] bytes) {
try {
if(bytes == null || bytes.length == 0) {
return;
}
if(this.transferByteCount + bytes.length > this.fileLength) {
//如果之前已经传输的数据长度+本批数据长度>文件长度的话,说明这批数据是最后一批数据了;
//由于本批数据中可能会存在有空字节,所以需要筛选出来。
byte[] contents = new byte[this.fileLength - this.transferByteCount];
for(int i=0;i<contents.length;i++) {
contents[i] = bytes[i];
}
this.transferByteCount = this.fileLength;
this.out.write(contents);
} else {
//说明本批数据并非最后一批数据,文件还没有传输完。
this.transferByteCount += bytes.length;
this.out.write(bytes);
}
if(this.transferByteCount >= this.fileLength) {
this.out.flush();
this.isSaveFile = true;
if(this.out != null) {
try {
this.out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
然后,在RMI服务接口方法实现类中构建一个线程安全的集合,用来存储管理各个大文件的传输过程,代码如下:
/**
* 上传文件状态监视器
*/
private Hashtable<String,RFileUploadTransfer> uploadFileStatusMonitor = new Hashtable<String,RFileUploadTransfer>();
1.2.2 CS握手接口设计
CS握手接口名称为startUploadFile,主要功能就是传输交换文件基本信息,构建文件上传过程状态控制对象。其在接口实现类中的代码如下所示:
@Override
public String startUploadFile(String localFilePath, int localFileLength, String remoteFilePath) throws RemoteException {
RFileUploadTransfer fileTransfer = new RFileUploadTransfer(localFilePath,localFileLength,remoteFilePath);
if(this.uploadFileStatusMonitor.containsKey(fileTransfer.getFileKey())) {
this.uploadFileStatusMonitor.remove(fileTransfer.getFileKey());
}
this.uploadFileStatusMonitor.put(fileTransfer.getFileKey(), fileTransfer);
return fileTransfer.getFileKey();
}
1.2.3 文件内容上传更新接口设计
文件内容上传更新接口名称为updateUploadProgress,主要功能是接收客户端传输过来的文件内容byte[]信息。其在接口实现类中的代码如下所示:
@Override
public boolean updateUploadProgress(String fileKey, byte[] contents) throws RemoteException {
if(this.uploadFileStatusMonitor.containsKey(fileKey)) {
RFileUploadTransfer fileTransfer = this.uploadFileStatusMonitor.get(fileKey);
fileTransfer.addContentBytes(contents);
if(fileTransfer.isSaveFile()) {
this.uploadFileStatusMonitor.remove(fileKey);
}
}
return true;
}
1.2.4 客户端设计
客户端的主要功能是打开本地文件,按批读取文件内容byte[]信息,调用RMI接口方法进行传输,同时进行传输进度的提醒。下面是笔者本人采用swing开发的测试代码,采用JProgressBar进行进度的实时提醒。
progressBar.setMinimum(0);
progressBar.setMaximum(100);
InputStream is = null;
try {
File srcFile = new File(localFilePath);
int fileSize = (int)srcFile.length();
String fileKey = getFileManageService().startUploadFile(localFilePath, fileSize, remoteFilePath); byte[] buffer = new byte[1024 * 1024 * 2];
int offset = 0;
int numRead = 0;
is = new FileInputStream(srcFile);
while(-1 != (numRead=is.read(buffer))) {
offset += numRead;
getFileManageService().updateUploadProgress(fileKey, buffer);
double finishPercent = (offset * 1.0 / fileSize) * 100;
progressBar.setValue((int)finishPercent);
}
if(offset != fileSize) {
throw new IOException("不能完整地读取文件 " + localFilePath);
} else {
progressBar.setValue(100);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
if(is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
实例界面截图如下所示:

2. 基于RMI服务下载大文件
2.1. 设计思路
下载大文件分为两个阶段,分别为“CS握手阶段”和“文件内容下载更新阶段”。
CS握手阶段,又可称之为客户端与服务端之间的基本信息交换阶段。服务端读取待下载的文件,获取文件长度,生成具有唯一性的FileKey信息,并将文件长度、FileKey信息传输到客户端,此FileKey作为客户端与服务端之间数据传输通道的唯一标示。这些信息数据类型均为Java基本数据类型,因此都是可序列化的。此接口方法就称之为“握手接口”,通过CS握手,客户端能确定两件事:哪个文件要下载、这个文件的尺寸,而这两件刚好是完成文件下载的基础。
然后就是“文件内容下载更新阶段”。服务端打开文件后,每次读取一定量的文件内容,譬如每次读取(1024 * 1024 * 2)byte的数据,并将此批数据通过“下载更新”接口方法传输到客户端,由客户端写入输出流中,同时检查文件内容是否已经传输完毕,如果已经传输完毕,则执行持久化flush操作在客户端生成文件;客户端每接收一批数据后,就更新“已下载数据总量”标示值,并与文件总长度进行比对计算,从而得到文件下载进度,实时告知用户。综上所述,客户端循环获取服务端传输过来的文件内容,直到服务端文件内容读取传输完毕为止。
2.2. 功能设计
2.2.1 文件下载相关状态信息的管理
大文件下载的过程中,在服务端,最重要的是文件下载过程相关状态信息的精确管理,譬如,文件总长度、已下载字节总数等等。而且要保证在整个下载过程中数据的实时更新和绝对不能丢失,并且在文件下载完毕后及时清除这些信息,以避免服务端累计过多失效的状态数据。
鉴于此,我们设计了一个类RFileDownloadTransfer来实现上述功能。代码如下:
/**
* Description: 文件下载过程相关状态信息封装类。<br>
* Copyright: Copyright (c) 2016<br>
* Company: 河南电力科学研究院智能电网所<br>
* @author shangbingbing 2016-01-01编写
* @version 1.0
*/
public class RFileDownloadTransfer implements Serializable {
private static final long serialVersionUID = 1L;
private String fileKey;
//服务端待下载文件路径
private String srcFilePath;
//待下载文件尺寸
private int fileLength = 0;
private InputStream inputStream = null;
//已经传输文件内容字节总数
private int transferByteCount = 0;
//服务端待下载文件是否已经读取完毕
private boolean isReadFinish = false; public RFileDownloadTransfer(String srcFilePath) {
this.fileKey = UUID.randomUUID().toString();
this.srcFilePath = srcFilePath;
File srcFile = new File(srcFilePath);
this.fileLength = (int)srcFile.length();
try {
this.inputStream = new FileInputStream(srcFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public String getFileKey() {
return fileKey;
}
public String getSrcFilePath() {
return srcFilePath;
}
public int getFileLength() {
return fileLength;
}
public boolean isReadFinish() {
return isReadFinish;
}
public byte[] readBytes() {
try {
if(this.inputStream == null) {
return null;
}
byte[] buffer = new byte[1024 * 1024 * 5];
this.inputStream.read(buffer);
this.transferByteCount += buffer.length;
if(this.transferByteCount >= this.fileLength) {
this.isReadFinish = true;
this.transferByteCount = this.fileLength;
}
return buffer;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
} public void closeInputStream() {
if(this.inputStream != null) {
try {
this.inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后,在RMI服务接口方法实现类中构建一个线程安全的集合,用来存储管理各个大文件的传输过程,代码如下:
/**
* 下载文件状态监视器
*/
private Hashtable<String,RFileDownloadTransfer> downloadFileStatusMonitor = new Hashtable<String,RFileDownloadTransfer>();
2.2.2 CS握接口设计
CS握手接口名称为startDownloadFile,主要功能就是传输交换文件基本信息,构建文件下载过程状态控制对象。其在接口实现类中的代码如下所示:
@Override
public Map<String, String> startDownloadFile(String srcFilePath) throws RemoteException {
RFileDownloadTransfer fileTransfer = new RFileDownloadTransfer(srcFilePath);
if(this.downloadFileStatusMonitor.containsKey(fileTransfer.getFileKey())) {
this.downloadFileStatusMonitor.remove(fileTransfer.getFileKey());
}
this.downloadFileStatusMonitor.put(fileTransfer.getFileKey(), fileTransfer);
Map<String,String> fileInfoList = new HashMap<String,String>();
fileInfoList.put("fileLength", String.valueOf(fileTransfer.getFileLength()));
fileInfoList.put("fileKey", fileTransfer.getFileKey());
return fileInfoList;
}
2.2.3 文件内容下载更新接口设计
文件内容下载更新接口名称为updateDownloadProgress,主要功能是接收客户端传输过来的文件内容byte[]信息。其在接口实现类中的代码如下所示:
@Override
public byte[] updateDownloadProgress(String fileKey) throws RemoteException {
if(this.downloadFileStatusMonitor.containsKey(fileKey)) {
RFileDownloadTransfer fileTransfer = this.downloadFileStatusMonitor.get(fileKey);
byte[] bytes = fileTransfer.readBytes();
if(fileTransfer.isReadFinish()) {
fileTransfer.closeInputStream();
this.downloadFileStatusMonitor.remove(fileKey);
}
return bytes;
}
return null;
}
2.2.4 客户端设计
客户端的主要功能是创建磁盘文件,逐批次获取文件内容byte[]信息,并将其写入输出流中,同时进行传输进度的提醒,并最终生成完整的文件。下面是笔者本人采用swing开发的测试代码,采用JProgressBar进行进度的实时提醒。
Map<String,String> fileInfoList = getFileManageService().startDownloadFile(remoteFilePath);
int fileLength = Integer.valueOf(fileInfoList.get("fileLength"));
String fileKey = fileInfoList.get("fileKey");
int transferByteCount = 0; progressBar.setMinimum(0);
progressBar.setMaximum(100); OutputStream out = new FileOutputStream(localFilePath);
while(true) {
if(transferByteCount >= fileLength) {
break;
} byte[] bytes = getFileManageService().updateDownloadProgress(fileKey);
if(bytes == null) {
break;
} if(transferByteCount + bytes.length > fileLength) {
//如果之前已经传输的数据长度+本批数据长度>文件长度的话,说明这批数据是最后一批数据了;
//那么本批数据中将会有空字节,需要筛选出来。
byte[] contents = new byte[fileLength - transferByteCount];
for(int i=0;i<contents.length;i++) {
contents[i] = bytes[i];
}
transferByteCount = fileLength;
out.write(contents);
} else {
//说明本批数据并非最后一批数据,文件还没有传输完。
transferByteCount += bytes.length;
out.write(bytes);
} double dblFinishPercent = (transferByteCount * 1.0 / fileLength) * 100;
int finishPercent = (int)dblFinishPercent;
if(finishPercent > 100) {
finishPercent = 100;
}
progressBar.setValue(finishPercent);
} if(transferByteCount != fileLength) {
LogInfoUtil.printLog("不能完整地读取文件 " + remoteFilePath);
} else {
progressBar.setValue(100);
out.flush();
if(out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实例界面截图如下所示:

【完】
作者:商兵兵
单位:河南省电力科学研究院智能电网所
QQ:52190634
基于RMI服务传输大文件的完整解决方案的更多相关文章
- C# 的 WCF文章 消息契约(Message Contract)在流(Stream )传输大文件中的应用
我也遇到同样问题,所以抄下做MARK http://www.cnblogs.com/lmjq/archive/2011/07/19/2110319.html 刚做完一个binding为netTcpBi ...
- WCF 用netTcpbinding,basicHttpBinding 传输大文件
问题:WCF如何传输大文件 方案:主要有几种绑定方式netTcpbinding,basicHttpBinding,wsHttpbinding,设置相关的传输max消息选项,服务端和客户端都要设置,tr ...
- TCP协议传输大文件读取时候的问题
TCP协议传输大文件读取时候的问题 大文件传不完的bug 我们在定义的时候定义服务端每次文件读取大小为10240, 客户端每次接受大小为10240 我们想当然的认为客户端每次读取大小就是10240而把 ...
- linux传输大文件
http://dreamway.blog.51cto.com/1281816/1151886 linux传输大文件
- 使用QQ传输大文件
现在在公网上能传输大文件并且稳定支持断点续传的软件非常少了,可以使用qq来做这件事. qq传输单个文件有时候提示不能超过4g有时候提示不能超过60g,没搞明白具体怎么样. 可以使用qq的传输文件夹功能 ...
- java大文件上传解决方案
最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...
- 基于TCP协议的大文件传输(粘包问题处理)
基于TCP的大文件上传服务端实现 # 服务端 # -*- coding: utf-8 -*- from socket import * import json, struct server = soc ...
- HTML5 大文件断点续传完整思路整理
需求: 支持大文件批量上传(20G)和下载,同时需要保证上传期间用户电脑不出现卡死等体验: 内网百兆网络上传速度为12MB/S 服务器内存占用低 支持文件夹上传,文件夹中的文件数量达到1万个以上,且包 ...
- Socket传输大文件(发送与接收)
下载 Client using System; using System.Collections.Generic; using System.Linq; using System.Text; usin ...
随机推荐
- 关于JAVA日志
虽然工作有一年了,做了好几个项目,但是对于日志这一块还只局限于拷贝配置文件,对于实现细节和灵活使用都还不会,主要的原因还是以前一直没有重视,在经历了好几个项目以后越发的觉得日志的重要性了.所以这几天都 ...
- 第 29 章 CSS3 弹性伸缩布局[下]
学习要点: 1.新版本 主讲教师:李炎恢 本章主要探讨 HTML5 中 CSS3 提供的用来实现未来响应式弹性伸缩布局方案,这里做一个初步的了解. 一.新版本 新版本的 Flexbox 模型是 201 ...
- Scalaz(50)- scalaz-stream: 安全的无穷运算-running infinite stream freely
scalaz-stream支持无穷数据流(infinite stream),这本身是它强大的功能之一,试想有多少系统需要通过无穷运算才能得以实现.这是因为外界的输入是不可预料的,对于系统本身就是无穷的 ...
- socket.io,远程控制你的幻灯片
原文:http://www.cnblogs.com/xiezhengcai/p/3964455.html 中秋休息了几天,今天又开始捣鼓socket.io了.今天的任务是通过socket.io控制你的 ...
- apache配置运行zendframework 2
其实ZF不用安装,只需引入就行,将ZF的library引入到项目的vendor\ZF2 就可以在ZF中开发了 在php版本5.4以上, 1 确保开启 extension=php_pdo.dllext ...
- Git分支(本地)
1.Git保存的不是文件的差异或者变化量,而只是一系列文件快照(File Snapshot): 2.暂存操作会对每一个文件计算校验和(SHA-1哈希字符串),然后把当前版本的文件快照保存到Git仓 ...
- 【转】PHP调试开发工具你认识多少?
来源:PHP100中文网(http://www.php100.com/html/itnews/PHPxinwen/2009/0902/3257.html) PHP现在已经是使用最为广泛的开源服务器端脚 ...
- Struts2与Struts的区别
Struts2与Struts的区别 从Struts2的发展过程来看,Struts2继承了Struts与WebWork的血脉,Struts2取两者之精华,形成新德框架,但是struts2还是更多的继承了 ...
- WPF实现炫酷Loading控件
Win8系统的Loading效果还是很不错的,网上也有人用CSS3等技术实现,研究了一下,并打算用WPF自定义一个Loading控件实现类似的效果,并可以让用户对Loading的颗粒(Particle ...
- 高性能的JavaScript库---Lodash
上周在仿做Nodejs社区的时候,遇到了lodash这个javascript库,很惭愧,那也是我第一次听说lodash.人嘛,对于新鲜的事物总是会或多或少感到些好奇的,于是就毫不犹豫地去lodash官 ...