所谓断点续传,也就是要从文件已经下载的地方开始继续下载。
客户端从服务器端下载文件,服务器端支持分段下载,这样子,还可以进行多线程分段下载(在此就不提供了)。
服务器端的支持:
public class DownloadServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
OutputStream os = null;
FileInputStream is = null;
try {
File f = new File(
"/home/agilemobi/Desktop/laolian_client_for_android_v1.0.apk");
is = new FileInputStream(f);
long fileSize = f.length();
resp.setHeader("Accept-Ranges", "bytes");
// resp.setHeader("Content-Length", fileSize + "");
String range = req.getHeader("Range");
int status = HttpServletResponse.SC_OK; // 返回的状态码,默认200,首次下载
// 如果range下载区域为空,则首次下载,
if(range == null){
range = "bytes=0-";
} else {
// 通过下载区域下载,使用206状态码支持断点续传
status = HttpServletResponse.SC_PARTIAL_CONTENT;
}
long start = 0, end = 0;
if (null != range && range.startsWith("bytes=")) {
String[] values = range.split("=")[1].split("-");
start = Integer.parseInt(values[0]);
// 如果服务器端没有设置end结尾,默认取下载全部
if(values.length == 1){
end = fileSize;
} else {
end = Integer.parseInt(values[1]);
}
}
// 此次数据响应大小
long responseSize = 0;
if (end != 0 && end > start) {
responseSize = end - start + 1;
// 返回当前连接下载的数据大小,也就是此次数据传输大小
resp.addHeader("Content-Length", "" + (responseSize));
} else {
responseSize = Integer.MAX_VALUE;
}
byte[] buffer = new byte[4096];
// 设置响应状态码
resp.setStatus(status);
if(status == HttpServletResponse.SC_PARTIAL_CONTENT){
// 设置断点续传的Content-Range传输字节和总字节
resp.addHeader("Content-Range", "bytes " + start + "-" + (fileSize - 1) + "/" + fileSize);
}
// 设置响应客户端内容类型
resp.setContentType("application/x-download");
// 设置响应客户端头
resp.addHeader("Content-Disposition", "attachment;filename=laolian_client_for_android_v1.0.apk");
// 当前需要下载文件的大小
int needSize = (int)responseSize;
if(start != 0){
// 跳已经传输过的字节
is.skip(start);
}
os = resp.getOutputStream();
while (needSize > 0) {
int len = is.read(buffer);
if (needSize < buffer.length) {
os.write(buffer, 0, needSize);
} else {
os.write(buffer, 0, len);
// 如果读取文件大小小于缓冲字节大小,表示已写入完,直接跳出
if (len < buffer.length) {
break;
}
}
// 不断更新当前可下载文件大小
needSize -= buffer.length;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (is != null)
is.close();
if (os != null)
os.close();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
super.doPost(req, resp);
}
}
客户端:
public class TestConn {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String urlstr = "http://localhost:8090/mydownload/DownloadServlet";
URL url = new URL(urlstr);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
// connection.setRequestProperty("Accept-Encoding", "identity");
connection.connect();
int fileSize = connection.getContentLength();
System.out.println(connection.getResponseCode());
System.out.println(fileSize);
connection.disconnect();
download(0, 0, 20000, urlstr);
}
private static void download(int startPos, int compeleteSize, int endPos, String urlstr){
HttpURLConnection connection = null;
RandomAccessFile randomAccessFile = null;
InputStream is = null;
try {
URL url = new URL(urlstr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
// 设置范围,格式为Range:bytes x-y;
connection.setRequestProperty("Range", "bytes="
+ (startPos + compeleteSize) + "-" + endPos);
randomAccessFile = new RandomAccessFile("/home/agilemobi/Desktop/1.apk", "rwd");
randomAccessFile.seek(startPos + compeleteSize);
// 将要下载的文件写到保存在保存路径下的文件中
is = connection.getInputStream();
byte[] buffer = new byte[4096];
int length = -1;
while ((length = is.read(buffer)) != -1) {
randomAccessFile.write(buffer, 0, length);
compeleteSize += length;
System.out.println("compeleteSize:" + compeleteSize);
}
System.out.println("OVER:" + compeleteSize);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(is != null)
is.close();
if(randomAccessFile != null)
randomAccessFile.close();
if(connection != null)
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
关键点:
客户端
1.
connection.setRequestProperty("Range", "bytes="
+ (startPos + compeleteSize) + "-" + endPos);
2.
RandomAccessFile的使用
服务器端
1.对HTTP头Range信息的解析。
2.InputStream的skip()跳过多少字节流。
3.状态的设置。
4.Content-Length的设置,否则客户端getContentLength()返回为-1。
resp.addHeader("Content-Length", "" + (responseSize));