</pre>第一步:客户端代码<pre name="code" class="html">
public static void download(String dataDownloadUrl, String destFile)
throws Exception {
try {
long remoteFileSize = 0;
remoteFileSize = getRemoteFileSzie(dataDownloadUrl);
File f = new File(destFile);
if (f.exists()) { // 文件存在,则在已经下载的地方插入继续下载
if (f.length() < remoteFileSize) {
if (f.length() == 0) {
logger.info("文件不存在,开始下载...");
} else {
logger.info("文件续传...");
}
} else {
logger.info("文件已下载完成,无需重新下载!");
return;
}
} else {
// 建立文件
try {
FileUtil.createFile(f);
} catch (Exception e) {
throw new Exception("初次下载 远程建立文件目录有误!" + e);
}
}
// 文件总大小
long totalSize = remoteFileSize;
// 分成的块的大小
long blockSize = 1024 * 1024 *50;//50M
// 能够分成几块
long count = totalSize / blockSize;
logger.info("按 50M 下载 ,能够分成 ... " + count + "...块");
// 剩余不够一块的 大小为
long lastSize = totalSize - (blockSize * count);
if (lastSize != 0) {
count = count + 1;
}
logger.info("剩余不够一块的大小为...." + lastSize);
for (int ii = 1; ii <= count; ii++) {
logger.info("第 " + ii + " 次进行下载....");
// 当为最后一块的时候
if (ii == count) {
logger.info(".......进入到最后一块的下载......");
HttpURLConnection httpURLConnection = null;
URL url = null;
BufferedInputStream bis = null;
byte[] buf = new byte[10240];
int size = 0;
// 检查本地文件
RandomAccessFile rndFile = null;
// 下载文件
url = new URL(dataDownloadUrl);
httpURLConnection = (HttpURLConnection) url
.openConnection();
// 设置User-Agent
httpURLConnection.setRequestProperty("User-Agent", "Net");
// 设置续传开始
logger.info("本地文件大小: " + f.length());
httpURLConnection.setRequestProperty("Range", "bytes="+ blockSize * (ii - 1) + "-" + totalSize);
bis = new BufferedInputStream(
httpURLConnection.getInputStream());
logger.info("客户端获取到的真实文件大小 remoteFileSize......"+ remoteFileSize);
rndFile = new RandomAccessFile(destFile, "rw");
rndFile.seek(f.length());
int i = 0;
while ((size = bis.read(buf)) != -1) {
rndFile.write(buf, 0, size);
i++;
}
logger.info("最后一块下载循环次数i= " + i);
if (new File(destFile).length() == remoteFileSize) {
logger.info("最后一块下载完成...判断了本地和远程文件的大小....");
httpURLConnection.disconnect();
logger.info("成功断开链接.......");
}
} else {
HttpURLConnection httpURLConnection = null;
URL url = null;
BufferedInputStream bis = null;
byte[] buf = new byte[10240];
int size = 0;
// 检查本地文件
RandomAccessFile rndFile = null;
// 下载文件
url = new URL(dataDownloadUrl);
httpURLConnection = (HttpURLConnection) url
.openConnection();
// 设置User-Agent
httpURLConnection.setRequestProperty("User-Agent", "Net");
// 设置续传开始
logger.info("本地文件大小: " + f.length());
httpURLConnection.setRequestProperty("Range", "bytes="+ blockSize * (ii - 1) + "-" + blockSize * ii);
bis = new BufferedInputStream(
httpURLConnection.getInputStream());
logger.info("客户端获取到的真实文件大小 remoteFileSize......"+ remoteFileSize);
rndFile = new RandomAccessFile(destFile, "rw");
rndFile.seek(f.length());
int i = 0;
while ((size = bis.read(buf)) != -1) {
rndFile.write(buf, 0, size);
i++;
}
logger.info("第 " + ii + " 次下载循环次数...." + i);
httpURLConnection.disconnect();
logger.info("也得....断开链接.......");
}
}
logger.info("下载完成后 ,客户端文件大小....." + remoteFileSize);
} catch (Exception e) {
logger.error("从【" + dataDownloadUrl + "】地址下载数据文件失败!", e);
throw new Exception("从【" + dataDownloadUrl + "】地址下载数据文件失败!", e);
}
</pre><pre name="code" class="html">第二步:服务器代码:
<pre name="code" class="java"> String zipFilePath = "";
// File zipfile = new File(zipFilePath);
logger.info("......真实文件大小......"+zipfile.length());
long fileLength = zipfile.length(); // 记录文件大小
logger.info("真实文件大小......"+fileLength);
long pastLength = 0; // 记录已下载文件大小
int rangeSwitch = 0; // 0:从头开始的全文下载;1:从某字节开始的下载(bytes=27000-);2:从某字节开始到某字节结束的下载(bytes=27000-39000)
long toLength = 0; // 记录客户端需要下载的字节段的最后一个字节偏移量(比如bytes=27000-39000,则这个值是为39000)
long contentLength = 0; // 客户端请求的字节总量
String rangeBytes = ""; // 记录客户端传来的形如“bytes=27000-”或者“bytes=27000-39000”的内容
RandomAccessFile raf = null; // 负责读取数据
OutputStream os = null; // 写出数据
OutputStream out = null; // 缓冲
byte b[] = new byte[1024]; // 暂存容器
if (request.getHeader("Range") != null) { // 客户端请求的下载的文件块的开始字节
response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);
rangeBytes = request.getHeader("Range").replaceAll(
"bytes=", "");
logger.info("range Bytes=== "+rangeBytes);
if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {// bytes=969998336-
rangeSwitch = 1;
rangeBytes = rangeBytes.substring(0,
rangeBytes.indexOf('-'));
pastLength = Long.parseLong(rangeBytes.trim());
contentLength = fileLength - pastLength; // 客户端请求的是
// 969998336
// 之后的字节
} else { // bytes=1275856879-1275877358
rangeSwitch = 2;
String temp0 = rangeBytes.substring(0,
rangeBytes.indexOf('-'));
String temp2 = rangeBytes.substring(
rangeBytes.indexOf('-') + 1,
rangeBytes.length());
pastLength = Long.parseLong(temp0.trim()); // bytes=1275856879-1275877358,从第
// 1275856879
// 个字节开始下载
toLength = Long.parseLong(temp2); // bytes=1275856879-1275877358,到第
// 1275877358 个字节结束
contentLength = toLength - pastLength; // 客户端请求的是
// 1275856879-1275877358
// 之间的字节
}
} else { // 从开始进行下载
contentLength = fileLength; // 客户端要求全文下载
}
logger.info("偏移量大小为......"+pastLength);
/**
* 如果设设置了Content -Length,则客户端会自动进行多线程下载。如果不希望支持多线程,则不要设置这个参数。
* 响应的格式是: Content - Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节]
* ServletActionContext
* .getResponse().setHeader("Content- Length", new
* Long(file.length() - p).toString());
*/
response.reset(); // 告诉客户端允许断点续传多线程连接下载,响应的格式是:Accept-Ranges:
// bytes
response.setHeader("Accept-Ranges", "bytes");// 如果是第一次下,还没有断点续传,状态是默认的
// 200,无需显式设置;响应的格式是:HTTP/1.1
// 200 OK
if (pastLength != 0) {
// 不是从最开始下载,
// 响应的格式是:
// Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
logger.info("----------------------------不是从开始进行下载!服务器即将开始断点续传...");
switch (rangeSwitch) {
case 1: { // 针对 bytes=27000- 的请求
String contentRange = new StringBuffer("bytes ")
.append(new Long(pastLength).toString())
.append("-")
.append(new Long(fileLength - 1).toString())
.append("/")
.append(new Long(fileLength).toString())
.toString();
response.setHeader("Content-Range", contentRange);
break;
}
case 2: { // 针对 bytes=27000-39000 的请求
String contentRange = rangeBytes + "/"
+ new Long(fileLength).toString();
response.setHeader("Content-Range", contentRange);
break;
}
default: {
break;
}
}
} else {
// 是从开始下载
logger.info("----------------------------是从开始进行下载!");
}
// request.setAttribute("org.apache.tomcat.sendfile.filename",
// zipFilePath); //真实文件
// request.setAttribute("org.apache.tomcat.sendfile.start",
// 0L);
response.addHeader("Content-Disposition","attachment; filename=export.zip");
// response.setContentType(CommonUtil.setContentType(zipfile.getName())); // set the
// MIME
// type.
response.addHeader("Content-Length",
String.valueOf(contentLength));
response.addHeader("abcc", fileLength + "");
// request.setAttribute("org.apache.tomcat.sendfile.end",
// fileLength);
os = response.getOutputStream();
out = new BufferedOutputStream(os);
raf = new RandomAccessFile(zipfile, "r");
try {
switch (rangeSwitch) {
case 0: { // 普通下载,或者从头开始的下载
// 同1
}
case 1: { // 针对 bytes=27000- 的请求
raf.seek(pastLength); // 形如 bytes=969998336-
// 的客户端请求,跳过 969998336 个字节
int n = 0;
while ((n = raf.read(b, 0, 1024)) != -1) {
out.write(b, 0, n);
}
break;
}
case 2: { // 针对 bytes=27000-39000 的请求
raf.seek(pastLength); // 形如
// bytes=1275856879-1275877358
// 的客户端请求,找到第 1275856879 个字节
int n = 0;
long readLength = 0; // 记录已读字节数
while (readLength <= contentLength - 1024) {// 大部分字节在这里读取
n = raf.read(b, 0, 1024);
readLength += 1024;
out.write(b, 0, n);
}
if (readLength <= contentLength) { // 余下的不足 1024
// 个字节在这里读取
n = raf.read(b, 0,
(int) (contentLength - readLength));
out.write(b, 0, n);
}
break;
}
default: {
break;
}
}
out.flush();
logger.info("------------------------------下载结束");
} catch (IOException ie) {
/**
* 在写数据的时候, 对于 ClientAbortException 之类的异常,
* 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时, 抛出这个异常,这个是正常的。
* 尤其是对于迅雷这种吸血的客户端软件, 明明已经有一个线程在读取
* bytes=1275856879-1275877358,
* 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段,
* 直到有一个线程读取完毕,迅雷会 KILL 掉其他正在下载同一字节段的线程, 强行中止字节读出,造成服务器抛
* ClientAbortException。 所以,我们忽略这种异常
*/
// ignore
logger.info("#提醒# 向客户端传输时出现IO异常,但此异常是允许的,有可能客户端取消了下载,导致此异常,不用关心!");
}finally{
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
System.out.println(e);
}
}
}
服务器端的代码 是在网上搜罗到的。望原创者见谅。原相关文章请见下面链接。
其实分块下载和 断点下载的原理相似,都是依靠请求头中的 "Range", "bytes=" ,具体请看代码。(代码写的烂....其实是自己留着以后备用 )