基于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

主页:http://www.cnblogs.com/shangbingbing

空间:http://shangbingbing.qzone.qq.com

时间: 2024-10-29 10:46:55

基于RMI服务传输大文件的完整解决方案的相关文章

OpenAdaptor1——基于webservice传输大文件

<span style="font-family:Arial, Helvetica, sans-serif;BACKGROUND-COLOR: rgb(255,255,255)">OpenAdaptor支持XFire和CXF开发的webservice,所以开发CXF webservice,基于myeclipse开发比较容易.参照网上webrvice传输大文件示例修改 http://blog.csdn.net/kongxx/article/details/7540930<

个人第一个开源分布式项目distributeTemplate的实现三 网络通讯netty传输大文件

今天 我将讲讲网络通讯,这里我初始版本 由于采用的事Netty框架  所以 这里讲网络Netty在我们这里是怎么使用的,下周开始添加rpc lucene内容了 实现之后的0.2 0.3版本,后面将会去掉netty依赖 采用原生的NIO2 (aio) 异步非阻塞方式 实现自己网络通讯,也就是说 这部分可能会实现一个简单的但是比netty精简高效的网络框架,后期做出来 可能会单独开一个分支开源出来,netty说白了 就是 事件驱动 以及 NIO 加一些协议 以及 异常 处理,废话不多说了. 我最近

TCP协议传输大文件读取时候的问题

TCP协议传输大文件读取时候的问题 大文件传不完的bug 我们在定义的时候定义服务端每次文件读取大小为10240, 客户端每次接受大小为10240 我们想当然的认为客户端每次读取大小就是10240而把客户端的读下来的文件想当然大小每一次都加上10240 而实际上服务端发送文件send每次发送不一定是一次性把10240的文件传送完,可能分了好几次进行发送至缓冲区这我们实际文件大小就不一定是10240 解决办法: 1.对于每次服务端所发送的文件内容及大小都发送给客户端,让客户端一一对应读取 2.实时

C# Socket传输大文件

1.基础类TransferFiles,client和server都需要 using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; using System.Windows.Forms; namespace Server { public class TransferFiles { public static int SendData(

远程传输大文件使用什么平台好呢?

远程传输大文件使用什么平台好呢?小文件倒是还可以通过QQ这样的方式进行传输,但是它对传输文件的大小有所限制,传输大文件就行不通了. 远程传输大文件使用什么平台好呢?传输大文件一个是要求传输稳定,不能说传了一点就断了,人又不可能一直盯着看是不是正常传输,这会造成传输时间的浪费.再一个要求文件传输的速度不能太慢,既然是大文件传输,速度很慢可能就要花好几个小时,甚至一整天,这会延误文件的传达. 我平时是使用网盘进行大文件的传输,网盘传输的速度比一般的通讯工具要更加快,也更加稳定,还没有文件的限制. 比

大文件上传解决方案

版权所有 2009-2018荆门泽优软件有限公司 保留所有权利 官方网站:http://www.ncmem.com/ 产品首页:http://www.ncmem.com/webapp/up6.2/index.asp 在线演示:http://www.ncmem.com/products/up6.2/index.htm 产品介绍:http://www.cnblogs.com/xproer/archive/2012/10/26/2741264.html 升级日志:http://www.cnblogs.

内网/外网大文件上传解决方案

最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格数据.上传影音文件等.如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成. 下面从文件上传方式入手,整理大文件上传的思路,并给出了相关实例代码,由于PHP内置了比较方便的文件拆分和拼接方法,因此服务端代码使用

前端大文件上传解决方案支持分片断点上传

最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表格数据.上传影音文件等.如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成. 下面从文件上传方式入手,整理大文件上传的思路,并给出了相关实例代码,由于PHP内置了比较方便的文件拆分和拼接方法,因此服务端代码使用

content_form.class.php文件不完整 解决方案

玩phpcms的从多少会遇到这个问题,根据错误提示我们可以发现是由于content_form.class.php文件不完整导致的,网上有好多文章说是把这个文件用本地的替换掉就可 以了,但是只要一更新缓存问题又会出现,不除根,果断废弃.这个问题,一般用ftp上传文件时,文件代码丢失,使得代码不完整造成的,只要找到丢失文件,再上传就好了.../phpcms/modules/content/fields/video/form.inc.php就是由于这个文件不完整从而导致的错误,它才是真正的元凶.我们只