断点续传(客户端和服务器端实现)




所谓断点续传,也就是要从文件已经下载的地方开始继续下载。

客户端从服务器端下载文件,服务器端支持分段下载,这样子,还可以进行多线程分段下载(在此就不提供了)。

服务器端的支持:


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();
			}
		}
	}

}

关键点:
客户端


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));