用 Java 实现断点续传 (HTTP)
断点续传是一种实现文件下载的机制,可以在中断下载后从上次下载的地方继续下载。在 HTTP 请求中实现断点续传与常规下载有所不同。
假设我们的目标是使用 Java 实现断点续传功能,以下是关键要点:
当浏览器请求从服务器下载文件时,通常会发起以下请求:
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 服务器发送一个带有起始位置的请求信息。例如,下面的请求表明从字节位置 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-`,该行告诉服务器从指定的字节位置开始传输文件。
服务器接收到这个请求后,返回带有范围的信息,如下所示:
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。
了解了上述原理后,我们可以开始编写 Java 代码实现断点续传。
在Java中实现断点续传功能并不复杂,下面将介绍其中的关键几点。
要实现断点续传,需要在请求中提交 RANGE 头部信息,指定从哪个字节位置开始下载文件。
使用Java的net包中的HttpURLConnection类可以方便地完成这个步骤。以下是示例代码:
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();
以上代码创建了一个HTTP连接,并设置了"User-Agent"和"RANGE"头部字段,以指定起始位置为2000070字节进行断点续传。
接下来是如何保存从输入流获取的字节流到文件中。在这里,我们使用Java的IO包中的RandomAccessFile类来实现保存文件的操作。
该类允许我们根据需要定位文件指针,并在指定位置开始写入字节流。以下是保存文件的示例代码:
RandomAccessFile 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); }
以上代码创建了一个RandomAccessFile对象,并使用`seek(nPos)`方法将文件指针定位到指定位置(例如2000070字节)。
然后,循环读取输入流中的字节流,并将其写入文件。
实现断点续传的过程可以总结为以下几个步骤:
1. 提交 RANGE 头部信息以确定下载起始位置。
2. 获取输入流,该流包含了从指定位置开始的字节流。
3. 创建用于保存文件的输出流(例如RandomAccessFile)。
4. 将输入流中的字节流读取到内存缓冲区。
5. 将内存缓冲区中的字节写入到文件中。
6. 重复以上两个步骤,直到完成文件的下载。
通过上述步骤,我们可以实现简单的断点续传功能。当然,在实际应用中可能需要更多的错误处理、线程控制和进度监测等功能来完善程序。
以上代码仅提供了基本的实现思路和方法,具体根据实际需求进行扩展和改进。
接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。
以下是提供有关断点续传内核的实现的整理完善的文章内容。
为了实现断点续传功能,我们使用了6个类,其中包括一个测试类。这些类分别是:
1. SiteFileFetch.java:负责整个文件的抓取,控制内部线程(FileSplitterFetch类)。
2. FileSplitterFetch.java:负责部分文件的抓取。
3. FileAccessI.java:负责文件的存储。
4. SiteInfoBean.java:存储要抓取的文件的信息,如文件保存的目录、文件名和抓取文件的URL等。
5. Utility.java:工具类,放置一些简单的方法。
6. TestMethod.java:测试类。
以下是源程序:
// SiteFileFetch.java package NetFox; import java.io.*; import java.net.*; 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 = 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() { 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 accessible!"); } 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(); } 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; } 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 表示访问错误 } String sHeader; for(int i=1;;i++) { sHeader=httpConnection.getHeaderFieldKey(i); if(sHeader!=null && sHeader.equals("Content-Length")) { nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader)); break; } else if (sHeader == null) { break; } } return nFileLength; } catch (Exception e) { e.printStackTrace(); return -1; // -1 表示错误 } } public void write_nPos() { try { output = new DataOutputStream(new FileOutputStream(tmpFile)); output.writeLong(nFileLength); for (int i = 0; i < nStartPos.length; i++) { output.writeLong(fileSplitterFetch[i].nStartPos); output.writeLong(fileSplitterFetch[i].nEndPos); } output.close(); } catch (Exception e) { e.printStackTrace(); } } public void read_nPos() { try { DataInputStream input = new DataInputStream(new FileInputStream(tmpFile)); nFileLength = input.readLong(); for (int i = 0; i < nStartPos.length; i++) { nStartPos[i] = input.readLong(); nEndPos[i] = input.readLong(); } input.close(); } catch (Exception e) { e.printStackTrace(); } } public void stopIt() { bStop = true; } public double getProcess() { double process = 0.0; long length = 0; for (int i = 0; i < nStartPos.length; i++) { length += fileSplitterFetch[i].length; } process = length * 1.0 / nFileLength * 100; return process; } private void processErrorCode(int nErrorCode) { System.err.println("Error Code : " + nErrorCode); } }
// FileSplitterFetch.java package NetFox; import java.io.*; import java.net.*; public class FileSplitterFetch extends Thread { static final int BUFFER_SIZE = 1024; URL url; // 文件路径 String sName; // 保存文件名 long nStartPos; // 文件分段的开始位置 long nEndPos; // 文件分段的结束位置 int nThreadID; // 线程编号 long length; boolean bDownOver = false; // 是否下载完成 public FileSplitterFetch(String sURL, String sName, long nStart, long nEnd, int id) throws IOException { this.url = new URL(sURL); this.sName = sName; this.nStartPos = nStart; this.nEndPos = nEnd; this.nThreadID = id; } public void run() { FileOutputStream output = null; InputStream input = null; try { HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection (); httpConnection.setRequestProperty("User-Agent", "NetFox"); String sProperty = "bytes=" + nStartPos + "-"; httpConnection.setRequestProperty("RANGE", sProperty); Utility.log(sProperty); input = httpConnection.getInputStream(); byte[] b = new byte[BUFFER_SIZE]; long nRead = 0; output = new FileOutputStream(sName + "_" + nThreadID); while (nStartPos < nEndPos) { nRead = input.read(b, 0, BUFFER_SIZE); if (nRead == -1) break; output.write(b, 0, (int) nRead); nStartPos += nRead; length += nRead; } bDownOver = true; Utility.log("Thread " + nThreadID + " is over!"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException e) { e.printStackTrace(); } } } }
// FileAccessI.java package NetFox; import java.io.*; public class FileAccessI implements Serializable { 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; } public synchronized int read(byte[] b, int nStart, int nLen) { int n = -1; try { oSavedFile.seek(nPos); n = oSavedFile.read(b, nStart, nLen); if (n > 0) { nPos += n; } } catch (IOException e) { e.printStackTrace(); } return n; } public void close() throws IOException { if (oSavedFile != null) oSavedFile.close(); } }
// SiteInfoBean.java package NetFox; public class SiteInfoBean { private String sSiteURL; // 文件下载地址 private String sFilePath; // 保存文件路径 private String sFileName; // 保存文件名 private int nSplitter; // 分割线程数 public SiteInfoBean(String sURL, String sPath, String sName, int nSpiltter) { this.sSiteURL = sURL; this.sFilePath = sPath; this.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 package NetFox; public class Utility { public static void log(String msg) { System.out.println(msg); } public static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
// TestMethod.java package NetFox; import java.io.IOException; public class TestMethod { public static void main(String[] args) throws IOException { SiteInfoBean bean = new SiteInfoBean("http://example.com/file.txt", "D:\\Downloads", "file.txt", 5); SiteFileFetch siteFileFetch = new SiteFileFetch(bean); siteFileFetch.start(); } }
以上是断点续传内核的实现代码。这些类配合使用,可以在下载大文件时实现断点续传功能。主要的类是SiteFileFetch,负责整个文件的抓取和控制内部线程(FileSplitterFetch类)。其他的类包括FileSplitterFetch、FileAccessI、SiteInfoBean、Utility,分别用于文件分段的抓取、文件的存储、文件信息的存储和一些工具方法。
使用上述代码,你可以创建一个SiteInfoBean对象,设置文件的URL、保存路径、文件名和分割线程数。然后创建SiteFileFetch对象并启动它,就可以开始断点续传下载了。
请注意,以上代码只是示例,需要根据实际情况进行适当的修改和扩展。
原来地址:用 Java 实现断点续传 (HTTP) | java教程-Java-Toy模板网