支持网络文件断点续传的实现
断点续传的原理:一个文件下载了一部分后,由于服务器或客户端原因,当前下载进度中断,用户可继续重新建立网络连接继续下载未下完的部分
断点三个最主要的属性:   Code:连接返回响应状态,状态码206支持断点续传
                       
                        Range属性:下载区域,它接收是一个区间范围,比如:Range:bytes=0-10000
                        例如:下载8.zip文件要求从10086字节开始传,前面的字节不用传,那么RANGE: bytes=10086-
Content-Range:bytes start- (fileSize-1)/fileSize;注释:fileSize文件总大小,start续传区间,从哪个位置开始续传的位置
首次请求支持断点续传服务器:
请求:
GET /down.zip HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Connection: Keep-Alive
响应:
200
Content-Length=106786028
Accept-Ranges=bytes
Date=Mon, 30 Apr 2013 12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2013 12:56:11 GMT
第二次请求支持断点续传服务器:
请求:
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
响应:
206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2013 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2013 12:55:20 GMT
示例图:

代码示例:
支持断点续传后台服务:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
public class RetryBokenDownloadsServlet extends HttpServlet {
	
	public RetryBokenDownloadsServlet() {
		super();
	}
	public void init() throws ServletException {
		super.init();
	}
	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		    //定义一个任意访问文件位置的RandomAccessFiled对象
		    RandomAccessFile raFile = null;
		    //获取响应输出流对象
		    ServletOutputStream sos = null;
		
	 try {
			//下载网络杂志的相对路径
			String downloadFile = request.getParameter("downloadFile");
			//下载文件名
			String fileName = request.getParameter("fileName");
			//获取绝对路径下网络杂志文件大小
			int fileSize = getFileByte(FileUtil.SITE_PATH + downloadFile);
			//设置下载文件大小
			response.addHeader("content-file-length", "" + (fileSize));
			//定义一个任意访问文件位置的RandomAccessFiled对象
			raFile = new RandomAccessFile(FileUtil.SITE_PATH + downloadFile, "r");
	        //设置获取客户端请求RANGE,获取下载文件的字节开始位置和结束位置
			String range = request.getHeader("RANGE");
			int status =200; //返回的状态码,默认200,首次下载
			//如果range下载区域为空,则首次下载,
			if(range ==null){
				range="bytes=0-";
			}else{
				//非首次下载通过下载区域下载使用206状态码支持断点续传
				status =206;
			}
			int 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]);
				}
				
			}
			//此次数据响应大小
			int responseSize = 0;
			if (end != 0 && end > start) {
				responseSize = end - start + 1;
				//返回当前连接下载的数据大小,也就是此次数据传输大小
				response.addHeader("content-length", "" + (responseSize));
			} else {
				responseSize = Integer.MAX_VALUE;
			}
                       //设置缓存为要下载的文件大小一半,减少太频繁并发连接数超过了其承载量而出现 ClientAbortException:  java.io.IOException异常,在windows7本地调试不会,在window2003和linux会出现,待排查
                       //超过20M,缓存设置默认20M左右,如视频,防止内存溢出
                       int maxSize = 1024*1024*20;
                       byte[] buffer = null;
                       if (fileSize > maxSize){  
                             buffer = new byte[1024*1024*20];
                       }else{
                             buffer =  new byte[fileSize/2];
                       }
			//设置响应状态码
			response.setStatus(status);
			if(status == 206){
				//设置断点续传的Content-Range传输字节和总字节
				response.addHeader("Content-Range","bytes "+start+"-"+(fileSize-1)+"/"+fileSize);
			}
			//设置响应客户端内容类型
			response.setContentType("application/x-download");
			//设置响应客户端头
			response.addHeader("Content-Disposition", "attachment;filename="+ fileName);
			//response.addHeader("Content-Range","bytes ""1000-5000/5001" );
			//获取响应输出流对象
			sos = response.getOutputStream();
			//当前需要下载文件的大小
			int needSize = responseSize;
			//将下载网络杂志记录指针定位到start位置
			raFile.seek(start);
			while (needSize > 0) {
				int len = raFile.read(buffer);
				if (needSize < buffer.length) {
					sos.write(buffer, 0, needSize);
				} else {
					sos.write(buffer, 0, len);
					//如果读取文件大小小于缓冲字节大小,表示已写入完,直接跳出
					if (len < buffer.length) {
						break;
					}
				}
				//不断更新当前可下载文件大小
				needSize -= buffer.length;
			}
	     
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			//关闭流
			if(raFile != null){
			   raFile.close();
			}
			if(sos != null){
			   sos.close();
			}
		}
	
	}
	public void doPost(HttpServletRequest reuqest,
			HttpServletResponse response) throws ServletException, IOException {
		doGet(reuqest,response);
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
		out.println("<HTML>");
		out.println("</HTML>");
		out.flush();
		out.close();
	}
	/**
	 * 获取文件字节总大小
	 * @param filePath 文件绝对路径(硬盘路径)
	 * @return
	 * @throws IOException 
	 */
	private static int getFileByte(String filePath) throws IOException {
		File file = new File(filePath);
		FileInputStream fs = null;
		int fileSize = 0;
		try {
			fs = new FileInputStream(file);
            fileSize = fs.available();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			if(fs!=null){
				fs.close();
			}
		}
		return fileSize;
	}
	
	
	public void destroy() {
		super.destroy(); 
	}
	
}
测试类:
  /**
    * 断点续传下载
     * @param start 开始位置
     * @param end 结束位置
     * @throws MalformedURLException 
    * @throws FileNotFoundException
    */
    public static void retryBokenDownloads(int start, int end){  
          
        String testUrl= "xxxxxxx.zip";
        try { 
           File file = new File("E:/temp/8.zip");
           if(file.exists()){
              System.out.println("获取已下载的文件大小="+file.length());
           }else{
             System.out.println("获取已下载的文件大小0");
           }
            RandomAccessFile raFile = new RandomAccessFile("E:/temp/8.zip", "rw");  
            URL url = new URL(endpoint);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();   
            conn.setRequestProperty("Content-Type","text/html; charset=UTF-8");   
            conn.setRequestProperty("RANGE","bytes="+start+"-"+end);   
            conn.connect();
            System.out.println(conn.getResponseCode());    //获取服务端的返回的响应码,200-返回连接成功
              System.out.println(conn.getContentLength());   //获取服务端的内容长度
              System.out.println(conn.getContentType());    //获取服务端的内容类型
              System.out.println(conn.getHeaderField("content-file-length"));  //获取服务端的文件大小
              System.out.println(conn.getHeaderField("Content-Range"));        //获取服务端的文件大小
             //System.out.println(conn.getHeaderField("content-length"));      //连接服务端下载的大小
              InputStream ins = (InputStream)conn.getContent();  
            raFile.seek(start);  
              
            byte[] buffer = new byte[4096];  
            int len = -1;  
            while((len = ins.read(buffer))!=-1){  
                raFile.write(buffer,0,len);  
            }  
            raFile.close();  
            conn.disconnect();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }
}
posted on 2013-06-25 11:03 阳光总在风雨后001 阅读(1072) 评论(0) 收藏 举报
 
                     
                    
                 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号 
