博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用Java实现断点续传(HTTP)
阅读量:2453 次
发布时间:2019-05-10

本文共 12194 字,大约阅读时间需要 40 分钟。

 

本文介绍了一种利用 Java 来实现断点续传的方法。

其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。

打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:
假设服务器域名为wwww.sjtu.edu.cn,文件名为down.zip。
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 2001 12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web服务器的时候要多加一条信息--从哪里开始。

下面是用自己编的一个"浏览器"来传递请求信息给Web服务器,要求从2000070字节开始。
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

仔细看一下就会发现多了一行RANGE: bytes=2000070-

这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2001 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

和前面服务器返回的信息比较一下,就会发现增加了一行:

Content-Range=bytes 2000070-106786027/106786028
返回的代码也改为206了,而不再是200了。

知道了以上原理,就可以进行断点续传的编程了。

  1. (1)用什么方法实现提交RANGE: bytes=2000070-。
    当然用最原始的Socket是肯定能完成的,不过那样太费事了,其实Java的net包中提供了这种功能。代码如下:
    URL url = new URL("http://www.sjtu.edu.cn/down.zip");
    HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();
    //设置User-Agent
    httpConnection.setRequestProperty("User-Agent","NetFox");
    //设置断点续传的开始位置
    httpConnection.setRequestProperty("RANGE","bytes=2000070");
    //获得输入流
    InputStream input = httpConnection.getInputStream();

    从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。大家看,其实断点续传用Java实现起来还是很简单的吧。接下来要做的事就是怎么保存获得的流到文件中去了。

  2. 保存文件采用的方法。
    我采用的是IO包中的RandAccessFile类。
    操作相当简单,假设从2000070处开始保存文件,代码如下:
    RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");
    long nPos = 2000070;
    //定位文件指针到nPos位置
    oSavedFile.seek(nPos);
    byte[] b = new byte[1024];
    int nRead;
    //从输入流中读入字节流,然后写到文件中
    while((nRead=input.read(b,0,1024)) > 0)
    {
    oSavedFile.write(b,0,nRead);
    }

怎么样,也很简单吧。接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。

 

 

主要用了6个类,包括一个测试类。

SiteFileFetch.java负责整个文件的抓取,控制内部线程(FileSplitterFetch类)。
FileSplitterFetch.java负责部分文件的抓取。
FileAccess.java负责文件的存储。
SiteInfoBean.java要抓取的文件的信息,如文件保存的目录,名字,抓取文件的URL等。
Utility.java工具类,放一些简单的方法。
TestMethod.java测试类。

下面是源程序:

/* **SiteFileFetch.java */import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.net.HttpURLConnection;import java.net.URL;public class SiteFileFetch extends Thread {	SiteInfoBean siteInfoBean = null; // 文件信息Bean	long[] nStartPos; // 开始位置	long[] nEndPos; // 结束位置	FileSplitterFetch[] fileSplitterFetch; // 子线程对象	long nFileLength; // 文件长度	boolean bFirst = true; // 是否第一次取文件	boolean bStop = false; // 停止标志	File tmpFile; // 文件下载的临时信息	DataOutputStream output; // 输出到文件的输出流	public SiteFileFetch(SiteInfoBean bean) throws IOException {		siteInfoBean = bean;		// tmpFile = File.createTempFile ("zhong","1111",new		// File(bean.getSFilePath()));		tmpFile = new File(bean.getSFilePath() + File.separator				+ bean.getSFileName() + ".info");		if (tmpFile.exists()) {			bFirst = false;			read_nPos();		} else {			nStartPos = new long[bean.getNSplitter()];			nEndPos = new long[bean.getNSplitter()];		}	}	public void run() {		// 获得文件长度		// 分割文件		// 实例FileSplitterFetch		// 启动FileSplitterFetch线程		// 等待子线程返回		try {			if (bFirst) {				nFileLength = getFileSize();				if (nFileLength == -1) {					System.err.println("File Length is not known!");				} else if (nFileLength == -2) {					System.err.println("File is not access!");				} else {					for (int i = 0; i < nStartPos.length; i++) {						nStartPos[i] = (long) (i * (nFileLength / nStartPos.length));					}					for (int i = 0; i < nEndPos.length - 1; i++) {						nEndPos[i] = nStartPos[i + 1];					}					nEndPos[nEndPos.length - 1] = nFileLength;				}			}			// 启动子线程			fileSplitterFetch = new FileSplitterFetch[nStartPos.length];			for (int i = 0; i < nStartPos.length; i++) {				fileSplitterFetch[i] = new FileSplitterFetch(siteInfoBean						.getSSiteURL(), siteInfoBean.getSFilePath()						+ File.separator + siteInfoBean.getSFileName(),						nStartPos[i], nEndPos[i], i);				Utility.log("Thread " + i + " , nStartPos = " + nStartPos[i]						+ ", nEndPos = " + nEndPos[i]);				fileSplitterFetch[i].start();			}			// fileSplitterFetch[nPos.length-1] = new			// FileSplitterFetch(siteInfoBean			// .getSSiteURL(),siteInfoBean.getSFilePath() + File.separator +			// siteInfoBean			// .getSFileName(),nPos[nPos.length-1],nFileLength,nPos.length-1);			// Utility.log("Thread " + (nPos.length-1) + " , nStartPos = " +			// nPos[nPos.length-1] + ",nEndPos = "+ nFileLength);			// fileSplitterFetch[nPos.length-1].start();			// 等待子线程结束			// int count = 0;			// 是否结束while循环			boolean breakWhile = false;			while (!bStop) {				write_nPos();				Utility.sleep(500);				breakWhile = true;				for (int i = 0; i < nStartPos.length; i++) {					if (!fileSplitterFetch[i].bDownOver) {						breakWhile = false;						break;					}				}				if (breakWhile)					break;				// count++;				// if(count>4)				// siteStop();			}			System.err.println("文件下载结束!");		} catch (Exception e) {			e.printStackTrace();		}	}	// 获得文件长度	public long getFileSize() {		int nFileLength = -1;		try {			URL url = new URL(siteInfoBean.getSSiteURL());			HttpURLConnection httpConnection = (HttpURLConnection) url					.openConnection();			httpConnection.setRequestProperty("User-Agent", "NetFox");			int responseCode = httpConnection.getResponseCode();			if (responseCode >= 400) {				processErrorCode(responseCode);				return -2; // -2 represent access is error			}			String sHeader;			for (int i = 1;; i++) {				// DataInputStream in = new				// DataInputStream(httpConnection.getInputStream ());				// Utility.log(in.readLine());				sHeader = httpConnection.getHeaderFieldKey(i);				if (sHeader != null) {					if (sHeader.equals("Content-Length")) {						nFileLength = Integer.parseInt(httpConnection								.getHeaderField(sHeader));						break;					}				} else					break;			}		} catch (IOException e) {			e.printStackTrace();		} catch (Exception e) {			e.printStackTrace();		}		Utility.log(nFileLength);		return nFileLength;	}	// 保存下载信息(文件指针位置)	private void write_nPos() {		try {			output = new DataOutputStream(new FileOutputStream(tmpFile));			output.writeInt(nStartPos.length);			for (int i = 0; i < nStartPos.length; i++) {				// output.writeLong(nPos[i]);				output.writeLong(fileSplitterFetch[i].nStartPos);				output.writeLong(fileSplitterFetch[i].nEndPos);			}			output.close();		} catch (IOException e) {			e.printStackTrace();		} catch (Exception e) {			e.printStackTrace();		}	}	// 读取保存的下载信息(文件指针位置)	private void read_nPos() {		try {			DataInputStream input = new DataInputStream(new FileInputStream(					tmpFile));			int nCount = input.readInt();			nStartPos = new long[nCount];			nEndPos = new long[nCount];			for (int i = 0; i < nStartPos.length; i++) {				nStartPos[i] = input.readLong();				nEndPos[i] = input.readLong();			}			input.close();		} catch (IOException e) {			e.printStackTrace();		} catch (Exception e) {			e.printStackTrace();		}	}	private void processErrorCode(int nErrorCode) {		System.err.println("Error Code : " + nErrorCode);	}	// 停止文件下载	public void siteStop() {		bStop = true;		for (int i = 0; i < nStartPos.length; i++)			fileSplitterFetch[i].splitterStop();	}}

 

/* **FileSplitterFetch.java */import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.io.Serializable;import java.net.HttpURLConnection;import java.net.URL;public class FileSplitterFetch extends Thread {	String sURL; // File URL	long nStartPos; // File Snippet Start Position	long nEndPos; // File Snippet End Position	int nThreadID; // Thread's ID	boolean bDownOver = false; // Downing is over	boolean bStop = false; // Stop identical	FileAccessI fileAccessI = null; // File Access interface	public FileSplitterFetch(String sURL, String sName, long nStart, long nEnd,			int id) throws IOException {		this.sURL = sURL;		this.nStartPos = nStart;		this.nEndPos = nEnd;		nThreadID = id;		fileAccessI = new FileAccessI(sName, nStartPos);	}	public void run() {		while (nStartPos < nEndPos && !bStop) {			try {				URL url = new URL(sURL);				HttpURLConnection httpConnection = (HttpURLConnection) url						.openConnection();				httpConnection.setRequestProperty("User-Agent", "NetFox");				String sProperty = "bytes=" + nStartPos + "-";				httpConnection.setRequestProperty("RANGE", sProperty);				Utility.log(sProperty);				InputStream input = httpConnection.getInputStream();				// logResponseHead(httpConnection);				byte[] b = new byte[1024];				int nRead;				while ((nRead = input.read(b, 0, 1024)) > 0						&& nStartPos < nEndPos && !bStop) {					nStartPos += fileAccessI.write(b, 0, nRead);					// if(nThreadID == 1)					// Utility.log("nStartPos = " + nStartPos + ", nEndPos = " +					// nEndPos);				}				Utility.log("Thread " + nThreadID + " is over!");				bDownOver = true;				// nPos = fileAccessI.write (b,0,nRead);			} catch (Exception e) {				e.printStackTrace();			}		}	}	// 打印回应的头信息	public void logResponseHead(HttpURLConnection con) {		for (int i = 1;; i++) {			String header = con.getHeaderFieldKey(i);			if (header != null)				//responseHeaders.put(header,httpConnection.getHeaderField(header				// ));				Utility.log(header + " : " + con.getHeaderField(header));			else				break;		}	}	public void splitterStop() {		bStop = true;	}}

 

/* **FileAccess.java */import java.io.*;public class FileAccessI implements Serializable {	/**  */	private static final long serialVersionUID = 265981599353224785L;	RandomAccessFile oSavedFile;	long nPos;	public FileAccessI() throws IOException {		this("", 0);	}	public FileAccessI(String sName, long nPos) throws IOException {		oSavedFile = new RandomAccessFile(sName, "rw");		this.nPos = nPos;		oSavedFile.seek(nPos);	}	public synchronized int write(byte[] b, int nStart, int nLen) {		int n = -1;		try {			oSavedFile.write(b, nStart, nLen);			n = nLen;		} catch (IOException e) {			e.printStackTrace();		}		return n;	}}

 

/* **SiteInfoBean.java */public class SiteInfoBean {	private String sSiteURL; // Site's URL	private String sFilePath; // Saved File's Path	private String sFileName; // Saved File's Name	private int nSplitter; // Count of Splited Downloading File	public SiteInfoBean() {		// default value of nSplitter is 5		this("", "", "", 5);	}	public SiteInfoBean(String sURL, String sPath, String sName, int nSpiltter) {		sSiteURL = sURL;		sFilePath = sPath;		sFileName = sName;		this.nSplitter = nSpiltter;	}	public String getSSiteURL() {		return sSiteURL;	}	public void setSSiteURL(String value) {		sSiteURL = value;	}	public String getSFilePath() {		return sFilePath;	}	public void setSFilePath(String value) {		sFilePath = value;	}	public String getSFileName() {		return sFileName;	}	public void setSFileName(String value) {		sFileName = value;	}	public int getNSplitter() {		return nSplitter;	}	public void setNSplitter(int nCount) {		nSplitter = nCount;	}}

 

/* **Utility.java */public class Utility {	public Utility() {	}	public static void sleep(int nSecond) {		try {			Thread.sleep(nSecond);		} catch (Exception e) {			e.printStackTrace();		}	}	public static void log(String sMsg) {		System.err.println(sMsg);	}	public static void log(int sMsg) {		System.err.println(sMsg);	}}

 

 

/* **TestMethod.java */public class TestMethod {	public TestMethod() { // /xx/weblogic60b2_win.exe		try {			SiteInfoBean bean = new SiteInfoBean(					"http://localhost/xx/weblogic60b2_win.exe", "L:\\temp",					"weblogic60b2_win.exe", 5);			// SiteInfoBean bean = new			// SiteInfoBean("http://localhost:8080/down.zip"			// ,"L:\\temp","weblogic60b2_win.exe",5);			SiteFileFetch fileFetch = new SiteFileFetch(bean);			fileFetch.start();		} catch (Exception e) {			e.printStackTrace();		}	}	public static void main(String[] args) {		new TestMethod();	}}

 

转载地址:http://qrbhb.baihongyu.com/

你可能感兴趣的文章
raspberry 防火墙_用于数据科学,Linux防火墙,Raspberry Pi NAS,openmediavault,Syncthing,微服务等的Python库
查看>>
Python编程,日志聚合工具,开源经济学,Prometheus监控,Kubernetes等
查看>>
raspberry nas_使用Raspberry Pi NAS托管您自己的云
查看>>
流行的编程语言,Audiophile Linux发行版,GNU,Bash,Raspberry PI,DevOps,GIMP等
查看>>
为什么优酷站点限制不了_什么是站点可靠性工程师,为什么要考虑这个职业道路
查看>>
少儿编程几种语言_您使用了几种编程语言?
查看>>
编程道场_编码道场教给我有关敏捷的知识
查看>>
视频分辨率和音频质量_高分辨率音频的声音是否比CD质量好?
查看>>
哪种编程语言好找工作_哪种编程语言可以工作? 为了周末?
查看>>
onenote组织知识体系_如何提升组织的安全专业知识
查看>>
python scikit_如何将Scikit学习Python库用于数据科学项目
查看>>
重启openstack服务_如何“ Kubernetize” OpenStack服务
查看>>
python编写函数_如何用Python编写自己喜欢的R函数
查看>>
Linux用户:您上一次使用Windows已有多长时间了?
查看>>
30个Linux安装案例,Make和Makefile说明,Linux工具,容器安全性,DevOps技巧等
查看>>
构建rpm包_如何构建rpm包
查看>>
linux 窗口管理器_您最喜欢的Linux窗口管理器是什么?
查看>>
makefile 无法工作_什么是Makefile,它如何工作?
查看>>
什么是ci/cd_什么是CI / CD?
查看>>
bash awk_Bash指南,Linux终端技巧,DevOps错误,Python,awk,NASA等
查看>>