本博客介绍如何进行文件的分块上传。本文侧重介绍客户端,服务器端请参考博客《Java 文件分块上传服务器端源代码》。建议读者朋友在阅读本文代码前先了解一下 MIME 协议。
所谓分块上传并非把大文件进行物理分块,然后挨个上传,而是依次读取大文件的一部分文件流进行上传。分块,倒不如说分流比较切实。本文通过一个项目中的示例,说明使用 Apache 的 HttpComponents/HttpClient 对大文件进行分块上传的过程。示例使用的版本是 HttpComponents Client 4.2.1。
本文仅以一小 demo 功能性地解释 HttpComponents/HttpClient 分块上传,没有考虑 I/O 关闭、多线程等资源因素,读者可以根据自己的项目酌情处理。
本文核心思想及流程:以 100 MB 大小为例,大于 100 MB 的进行分块上传,否则整块上传。对于大于 100 MB 的文件,又以 100 MB 为单位进行分割,保证每次以不大于 100 MB 的大小进行上传。比如 304 MB 的一个文件会分为 100 MB、100 MB、100 MB、4 MB 等四块依次上传。第一次读取 0 字节开始的 100 MB 个字节,上传;第二次读取第 100 MB 字节开始的 100 MB 个字节,上传;第三次读取第 200 MB 字节开始的 100 MB 个字节,上传;第四次读取最后剩下的 4 MB 个字节进行上传。
自定义的 ContentBody 源码如下,其中定义了流的读取和输出:
package com.defonds.rtupload.common.util.block; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import org.apache.http.entity.mime.content.AbstractContentBody; import com.defonds.rtupload.GlobalConstant; public class BlockStreamBody extends AbstractContentBody { //给MultipartEntity看的2个参数 private long blockSize = 0;//本次分块上传的大小 private String fileName = null;//上传文件名 //writeTo需要的3个参数 private int blockNumber = 0, blockIndex = 0;//blockNumber分块数;blockIndex当前第几块 private File targetFile = null;//要上传的文件 private BlockStreamBody(String mimeType) { super(mimeType); // TODO Auto-generated constructor stub } /** * 自定义的ContentBody构造子 * @param blockNumber分块数 * @param blockIndex当前第几块 * @param targetFile要上传的文件 */ public BlockStreamBody(int blockNumber, int blockIndex, File targetFile) { this("application/octet-stream"); this.blockNumber = blockNumber;//blockNumber初始化 this.blockIndex = blockIndex;//blockIndex初始化 this.targetFile = targetFile;//targetFile初始化 this.fileName = targetFile.getName();//fileName初始化 //blockSize初始化 if (blockIndex < blockNumber) {//不是最后一块,那就是固定大小了 this.blockSize = GlobalConstant.CLOUD_API_LOGON_SIZE; } else {//最后一块 this.blockSize = targetFile.length() - GlobalConstant.CLOUD_API_LOGON_SIZE * (blockNumber - 1); } } @Override public void writeTo(OutputStream out) throws IOException { byte b[] = new byte[1024];//暂存容器 RandomAccessFile raf = new RandomAccessFile(targetFile, "r");//负责读取数据 if (blockIndex == 1) {//第一块 int n = 0; long readLength = 0;//记录已读字节数 while (readLength <= blockSize - 1024) {//大部分字节在这里读取 n = raf.read(b, 0, 1024); readLength += 1024; out.write(b, 0, n); } if (readLength <= blockSize) {//余下的不足 1024 个字节在这里读取 n = raf.read(b, 0, (int)(blockSize - readLength)); out.write(b, 0, n); } } else if (blockIndex < blockNumber) {//既不是第一块,也不是最后一块 raf.seek(GlobalConstant.CLOUD_API_LOGON_SIZE * (blockIndex - 1));//跳过前[块数*固定大小 ]个字节 int n = 0; long readLength = 0;//记录已读字节数 while (readLength <= blockSize - 1024) {//大部分字节在这里读取 n = raf.read(b, 0, 1024); readLength += 1024; out.write(b, 0, n); } if (readLength <= blockSize) {//余下的不足 1024 个字节在这里读取 n = raf.read(b, 0, (int)(blockSize - readLength)); out.write(b, 0, n); } } else {//最后一块 raf.seek(GlobalConstant.CLOUD_API_LOGON_SIZE * (blockIndex - 1));//跳过前[块数*固定大小 ]个字节 int n = 0; while ((n = raf.read(b, 0, 1024)) != -1) { out.write(b, 0, n); } } //TODO 最后不要忘掉关闭out/raf } @Override public String getCharset() { // TODO Auto-generated method stub return null; } @Override public String getTransferEncoding() { // TODO Auto-generated method stub return "binary"; } @Override public String getFilename() { // TODO Auto-generated method stub return fileName; } @Override public long getContentLength() { // TODO Auto-generated method stub return blockSize; } }
在自定义的 HttpComponents/HttpClient 工具类 HttpClient4Util 里进行分块上传的封装:
public static String restPost(String serverURL, File targetFile,Map<String, String> mediaInfoMap){ String content =""; try { DefaultHttpClient httpClient = new DefaultHttpClient(); HttpPost post = new HttpPost(serverURL +"?"); httpClient.getParams().setParameter("http.socket.timeout",60*60*1000); MultipartEntity mpEntity = new MultipartEntity(); List<String> keys = new ArrayList<String>(mediaInfoMap.keySet()); Collections.sort(keys, String.CASE_INSENSITIVE_ORDER); for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) { String key = iterator.next(); if (StringUtils.isNotBlank(mediaInfoMap.get(key))) { mpEntity.addPart(key, new StringBody(mediaInfoMap.get(key))); } } if(targetFile!=null&&targetFile.exists()){ ContentBody contentBody = new FileBody(targetFile); mpEntity.addPart("file", contentBody); } post.setEntity(mpEntity); HttpResponse response = httpClient.execute(post); content = EntityUtils.toString(response.getEntity()); httpClient.getConnectionManager().shutdown(); } catch (Exception e) { e.printStackTrace(); } System.out.println("=====RequestUrl==========================\n" +getRequestUrlStrRest(serverURL, mediaInfoMap).replaceAll("&fmt=json", "")); System.out.println("=====content==========================\n"+content); return content.trim(); }
其中 "file" 是分块上传服务器对分块文件参数定义的名字。细心的读者会发现,整块文件上传直接使用 Apache 官方的 InputStreamBody,而分块才使用自定义的 BlockStreamBody。
最后调用 HttpClient4Util 进行上传:
public static Map<String, String> uploadToDrive( Map<String, String> params, String domain) { File targetFile = new File(params.get("filePath")); long targetFileSize = targetFile.length(); int mBlockNumber = 0; if (targetFileSize < GlobalConstant.CLOUD_API_LOGON_SIZE) { mBlockNumber = 1; } else { mBlockNumber = (int) (targetFileSize / GlobalConstant.CLOUD_API_LOGON_SIZE); long someExtra = targetFileSize % GlobalConstant.CLOUD_API_LOGON_SIZE; if (someExtra > 0) { mBlockNumber++; } } params.put("blockNumber", Integer.toString(mBlockNumber)); if (domain != null) { LOG.debug("Drive---domain=" + domain); LOG.debug("drive---url=" + "http://" + domain + "/sync" + GlobalConstant.CLOUD_API_PRE_UPLOAD_PATH); } else { LOG.debug("Drive---domain=null"); } String responseBodyStr = HttpClient4Util.getRest("http://" + domain + "/sync" + GlobalConstant.CLOUD_API_PRE_UPLOAD_PATH, params); ObjectMapper mapper = new ObjectMapper(); DrivePreInfo result; try { result = mapper.readValue(responseBodyStr, ArcDrivePreInfo.class); } catch (IOException e) { LOG.error("Drive.preUploadToArcDrive error.", e); throw new RtuploadException(GlobalConstant.ERROR_CODE_13001);// TODO } // JSONObject jsonObject = JSONObject.fromObject(responseBodyStr); if (Integer.valueOf(result.getRc()) == 0) { int uuid = result.getUuid(); String upsServerUrl = result.getUploadServerUrl().replace("https", "http"); if (uuid != -1) { upsServerUrl = upsServerUrl + GlobalConstant.CLOUD_API_UPLOAD_PATH; params.put("uuid", String.valueOf(uuid)); for (int i = 1; i <= mBlockNumber; i++) { params.put("blockIndex", "" + i); HttpClient4Util.restPostBlock(upsServerUrl, targetFile, params);// } } } else { throw new RtuploadException(GlobalConstant.ERROR_CODE_13001);// TODO } return null; }
其中 params 这个 Map 里封装的是服务器分块上传所需要的一些参数,而上传块数也在这里进行确定。
本文中的示例经本人测试能够上传大文件成功,诸如 *.mp4 的文件上传成功没有出现任何问题。如果读者朋友测试时遇到问题无法上传成功,请在博客后跟帖留言,大家共同交流下。本文示例肯定还存在很多不足之处,如果读者朋友发现还请留言指出,笔者先行谢过了。
相关推荐
在本压缩包中,我们关注的是一个Java实现的客户端源代码,用于与这种分布式文件系统进行交互。在Java中,开发这样的客户端通常涉及以下关键技术点: 1. **Hadoop HDFS**: Hadoop是一个开源框架,用于处理和存储大量...
【标题】"TrebleShot局域网文件共享客户端源代码(含windows和android客户端)" 提供的是一个跨平台的文件共享解决方案,它包括了Windows和Android两个操作系统版本的客户端源码。这个项目的核心目标是实现局域网内的...
在这个示例中,我们将探讨如何使用Java的Socket实现文件传输,包括服务器端和客户端的实现细节。 首先,我们从服务器端开始。服务器端的核心是监听特定端口(在这个例子中是8821)上的连接请求,并在接收到连接后...
在这个"Socket编程文件上传源代码案例"中,我们将深入探讨如何利用Socket和IO流技术来实现在TCP网络上的文件上传功能。 首先,我们需要理解Socket的概念。Socket是操作系统提供的一种进程间通信(IPC)机制,它为...
在“JAVA文件传输(论文+源代码).zip”中,我们可能找到一篇关于Java文件传输技术的学术论文以及相关的源代码实现。通过分析这个压缩包内的内容,我们可以深入理解Java文件传输的原理、设计模式以及实际应用。 首先...
标题提到的"java web 大文件上传源代码"提供了一个解决方案,它可能包含了分块上传、断点续传等优化策略,使得大文件上传变得可行且高效。 首先,大文件上传通常涉及的技术包括Servlet、Multipart解析器、以及可能...
在Android平台上,开发...以上是基于给定的"android手机客户端上传文件到客户端(web应用)_源代码代码"项目所涵盖的主要知识点。通过深入理解和实践这些内容,开发者可以构建出可靠的文件上传功能,满足实际应用的需求。
综上所述,此Java Web项目为大文件上传提供了一整套解决方案,涵盖了从客户端到服务器端的整个流程,包括文件分块、断点续传、安全控制以及与数据库的交互。通过深入理解这些知识点,开发者可以构建出高效、稳定且...
而"SocketClient"文件则包含客户端的源代码,负责文件分块、发送数据和关闭连接。 在实际应用中,我们还需要考虑以下几点优化: - **错误处理**:包括网络中断、文件损坏等情况,需要有合适的错误恢复机制。 - **...
本资源"文件上传与下载源代码"提供了在MyEclipse环境下实现这一功能的具体示例。MyEclipse是一款强大的Java集成开发环境,常用于构建Web应用。 1. **文件上传**: 文件上传是用户通过网页将本地文件传输到服务器的...
本项目提供了完整的源代码和相关的论文资料,对于深入理解Java技术栈和提升实际编程能力非常有帮助。 1. **Java网络编程**:在文件传输中,Java的Socket编程是基础。Socket提供了客户端与服务器之间的通信接口,...
本资源“JAVA文件传输(论文+源代码)”是一个典型的Java项目实例,主要关注文件传输这一核心功能。这个项目不仅提供了完整的源代码,还有配套的论文,为学习者提供了深入理解文件传输机制和Java编程实践的机会。 ...
这个"JAVA文件传输(LW+源代码).rar"压缩包可能包含了轻量级(Lightweight)的Java文件传输解决方案以及相关的源代码,使得学习者或开发者能够理解和应用这些代码来构建自己的文件传输系统。 在Java中,文件传输主要...
10. **论文与源代码**:`JAVA文件传输(论文+源代码)`可能包含了一份详细的技术报告或论文,解释了项目的实现原理和技术细节,以及源代码,供学习者理解和参考。 通过这个项目,开发者或学习者不仅可以了解文件传输...
8. **源代码分析**:压缩包内的源代码可能包含客户端和服务器端的实现,展示了如何使用Java进行文件上传和下载。通过阅读和理解这些代码,可以深入学习文件传输的具体实现。 9. **异常处理**:在文件传输过程中,...