实现单线程的断点下载
程序员文章站
2022-04-11 09:08:11
...
/** * 实现单线程的断点下载 */ public class HttpDownloadSingle implements Runnable { // 响应状态码 private String responseCode; // 响应头信息 private Map<String, String> headers = new HashMap<String, String>(); // 下载事件处理类 private DownloadEvent event; // 下载文件的url private String url; // 文件保存在本地的路径 private String outFilePath; // 缓冲区大小 private int bufferSize; public HttpDownloadSingle(DownloadEvent event, String url, String outFilePath, int bufferSize) { this.event = event; this.url = url; this.outFilePath = outFilePath; this.bufferSize = bufferSize; } @Override public void run() { try { download(); } catch (IOException e) { throw new RuntimeException(e); } } public void download() throws IOException { // 下载的文件保存在本地的路径 File outFile = new File(outFilePath); long finishedSize = 0; if (outFile.exists()) // 如果文件已经存在,记录文件大小,即已下载大小 finishedSize = outFile.length(); URL download_url = new URL(url); Socket socket = new Socket(); // 获取主机地址 String host = download_url.getHost(); // 获取端口 如果url地址没有指定端口 会返回-1 给定一个默认端口号80 int port = download_url.getPort() == -1 ? 80 : download_url.getPort(); // 资料路径 String resourcePath = download_url.getPath(); event.state("connecting " + host + ":" + port); // 设置read超时 socket.setSoTimeout(5000); // 连接服务器 并且设置连接超时 socket.connect(new InetSocketAddress(host, port), 3000); event.state("connect successfully"); try { // 生成一个request消息,用于查看所下载文件大小和服务器是否支持断点下载 generateHttpRequest(socket, host, resourcePath, finishedSize); InputStream socketIn = socket.getInputStream(); // 解析响应头信息 analyseResponseHeader(socketIn, event); // 获取要下载文件的大小 long contentLength = getFileLength(); // 如果已下载的大小大于等于总的文件大小 直接返回 if (contentLength <= finishedSize) return; // 如果已下载大小不等于0 且状态码是200(表示服务器不支持断点下载 // Accept-Ranges: bytes也可以用来判断服务器是否支持断点下载) 也直接返回 if (finishedSize > 0 && "200".equals(responseCode)) return; //206表示支持断点下载 其他状态码就抛出一个运行时异常 if (responseCode.charAt(0) != '2') throw new RuntimeException("Unsupported response status code"); byte[] buf = new byte[bufferSize]; int n; FileOutputStream fos = new FileOutputStream(outFile, true); try { while (-1 != (n = socketIn.read(buf))) { fos.write(buf, 0, n); finishedSize += n; //计算当前已下载百分比 event.percent(finishedSize * 100 / contentLength); } } finally { if (fos != null) { try { fos.close(); } catch (Exception e) { } } } } finally { socket.close(); } } private String getHeader(String name) { return headers.get(name); } private int getIntHeader(String name) { return Integer.parseInt(getHeader(name).trim()); } private long getFileLength() { long len = -1; try { len = getIntHeader("Content-Length"); String[] parts = getHeader("Content-Range").split("/"); if (parts.length > 1) len = Integer.parseInt(parts[1].trim()); else len = -1; } catch (Exception e) { } return len; } private void analyseResponseHeader(InputStream socketIn, DownloadEvent event) throws IOException { String line = ""; while (true) { int bt = socketIn.read(); if (bt == '\r') { bt = socketIn.read(); if (bt == '\n') { //如果读到空行,就结束解析 if ("".equals(line)) break; //解析状态行和头信息 addHeader2Map(line); event.viewHeader(line); line = ""; } } else line += (char) bt; } } private void addHeader2Map(String line) { int index = line.indexOf(":"); if (index > 0) headers.put(line.substring(0, index).trim(), line.substring(index + 1)); else analyseResponseLine(line); } private void analyseResponseLine(String line) { String[] parts = line.split(" +"); if (parts.length > 1) //获取状态码 responseCode = parts[1].trim(); } private void generateHttpRequest(Socket socket, String host, String path, long startPos) throws IOException { PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println("GET " + path + " HTTP/1.1"); writer.println("Host: " + host); writer.println("User-Agent: java"); if (startPos > 0) //如果已下载长度不为0 就通过Range头把已下载长度也一起发送给服务器 writer.println("Range: bytes=" + startPos + "-"); writer.println("Connection: close"); writer.println(); writer.flush(); } public interface DownloadEvent { void state(String message); void viewHeader(String header); void percent(long newValue); } }
class DownloadProgress implements DownloadEvent { long oldValue = -1; @Override public void percent(long newValue) { if (newValue > oldValue) { System.out.print("[" + newValue + "%]"); oldValue = newValue; } } @Override public void state(String message) { System.out.println(message); } @Override public void viewHeader(String header) { System.out.println(header); } } /* * 6688768 bytes 87494656 bytes 121552303 bytes */ public class Main { public static void main(String[] args) throws Exception { if (args.length < 1) { System.out.println("Usage: java class DownloadFileName"); return; } FileInputStream fis = new FileInputStream(args[0]); BufferedReader fileReader = new BufferedReader(new InputStreamReader(fis)); String s = ""; String[] ss; while ((s = fileReader.readLine()) != null) { ss = s.split("[ ]+"); if (ss.length > 2) { System.out.println("---------------------------"); System.out.println("downloading:" + ss[0]); System.out.println("output file path:" + ss[1]); System.out.println("buffer size:" + ss[2]); System.out.println("---------------------------"); HttpDownloadSingle httpDownload = new HttpDownloadSingle(new DownloadProgress(), ss[0], ss[1], Integer .parseInt(ss[2])); new Thread(httpDownload).start(); } } fileReader.close(); } }
运行结果
--------------------------- downloading:http://localhost:8080/test_web/xx.exe output file path:src/xx.exe buffer size:4096 --------------------------- connecting localhost:8080 connect successfully HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"607195608-1397026780797" Last-Modified: Wed, 09 Apr 2014 06:59:40 GMT Content-Type: application/octet-stream Content-Length: 607195608 Date: Wed, 16 Apr 2014 01:42:20 GMT Connection: close [0%][1%][2%][3%][4%][5%][6%][7%][8%][9%][10%][11%][12%][13%][14%][15%][16%][17%][18%]