手记

断点续传教学例子

2019-10-22 22:43:383784浏览

风落几番

7实战 · 15手记 · 6推荐
package net.url;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
 
/**
 * 断点续传java例子
 */
public class BreakpointResume {
	/**
	 * 断点续传时每段的字节数
	 */
	private static final long FRAGMENT_SIZE = 2048L;
	/**
	 * 本地文件存储目录
	 */
	private static final String LOCAL_PATH = "D:\\Demo";
 
	/**
	 * args的第一个参数指示Web资源的URL地址,协议类型限定为HTTP。required<br>
	 * 第二个参数指示本地文件的绝对路径 + 文件名。optional
	 * 
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		if (args.length != 1 && args.length != 2) {
			System.out.println("Usage[1]:java BreakpointResume url localFile");
			System.out.println("or Usage[2]:java BreakpointResume url");
			return;
		}
 
		String localFile = null;
		String url = args[0];
		if (args.length == 2) {
			localFile = args[1];
		}
		// 1. 校验URL和本地文件的格式
		String checkURLMsg = checkURLFormat(url);
		if (checkURLMsg != null) {
			System.err.println(checkURLMsg);
			return;
		}
		String checkLocalFileMsg = checkLocalFileFormat(localFile);
		if (checkLocalFileMsg != null) {
			System.err.println(checkLocalFileMsg);
			return;
		}
 
		// 2. 计算Range的范围
		long startRange, endRange;
		if (localFile == null) {
			startRange = 0L;
			endRange = FRAGMENT_SIZE;
		} else {
			startRange = new File(localFile).length();
			endRange = startRange + FRAGMENT_SIZE;
		}
		
		// 3. 访问网络资源然后分段下载
		downloadPartially(url, localFile, startRange, endRange);
	}
	
	/**
	 * 分段下载。<br>
	 * <p>假设要下载的资源为http://img.article.pchome.net/00/22/70/23/pic_lib/wm/03.jpg,分段下载的思路如下:</p>
	 * <p>1) 设置请求属性Range</p>
	 * <p>2) 连接远程Web资源</p>
	 * <p>3) 校验响应中的状态行,如果不是200或者206,就停止传输</p>
	 * <p>4) 查看.tmp文件(03.jpg.tmp)是否已经存在,如果不存在,就新建该文件</p>
	 * <p>5) 将响应内容写入.tmp文件</p>
	 * <p>6) 如果Web资源已经全部传输完了,将.tmp文件的后缀去掉,还原为文件本来的后缀和格式,然后结束while循环</p>
	 * <p>7) 如果Web资源没有传输完,计算下一次传输的Range的范围</p>
	 * 
	 * @param url
	 * @param localFile
	 * @param startRange
	 * @param endRange
	 * @throws MalformedURLException	应该不会抛出该异常,因为已经限定了只能用HTTP协议访问Web资源,并且进行了校验
	 */
	private static void downloadPartially(String url, String localFile, long startRange, long endRange) throws MalformedURLException {
		long startTime = System.currentTimeMillis();
		URL resource = new URL(url);
		
		// 加入num是用来模拟Web资源传输了一部分,然后第二次传输时,从上次结束的部分开始获取
		int num = 0;
		while (true) {
			if (++num == 10) {
//				break;
			}
			HttpURLConnection conn = null;
			InputStream in = null;
			RandomAccessFile raf = null;
			try {
				conn = (HttpURLConnection) resource.openConnection();
				// 1) 设置请求属性Range
				conn.setRequestProperty("Range", "bytes=" + startRange + "-" + (endRange == -1L ? "" : endRange));
				// 2) 连接远程Web资源
				conn.connect();
				// 3) 校验状态行,如果不是成功或者部分内容,就停止传输
				String statusLine = conn.getHeaderField(null);// 状态行
				System.out.println("statusLine=" + statusLine);
				if (!statusLine.contains("200") && !statusLine.contains("206")) {
					throw new Exception("获取Web资源[" + url + "]时,响应状态不是200或者206");
				}
 
				// 获取资源长度
				String cr = conn.getHeaderField("Content-Range");
				if (cr == null || "".equals(cr.trim())) {
					throw new Exception("获取Web资源[" + url + "]时,响应信息中Content-Range为null");
				}
				System.out.println("Content-Range=" + cr);
				cr = cr.replace("[", "").replace("]", "").replace("bytes", "").trim();
				// 解析响应消息头中Content-Range字段的值
				long resourceStartPos = Long.parseLong(cr.substring(0, cr.indexOf("-")));
				long resourceEndPos = Long.parseLong(cr.substring(cr.indexOf("-") + 1, cr.indexOf("/")));
				long resourceTotalLength = Long.parseLong(cr.substring(cr.indexOf("/") + 1));
				System.out.println("resourceStartPos=" + resourceStartPos
						+ ", resourceEndPos=" + resourceEndPos
						+ ", resourceTotalLength=" + resourceTotalLength);
				// 将相应内容读取到buf中
				byte[] buf = new byte[(int) (resourceEndPos - resourceStartPos)];
				in = conn.getInputStream();
				in.read(buf);
				
				// 4) 查看.tmp文件是否已经存在,如果不存在,就新建该文件
				if (localFile == null) {
					localFile = LOCAL_PATH + File.separator + url.substring(url.lastIndexOf("/") + 1) + ".tmp";
				}
				System.out.println("localFile=" + localFile);
				File f = new File(localFile);
				if (!f.exists()) {// .tmp文件不存在,使用OutputStream手动创建该文件
					OutputStream os = new FileOutputStream(f);
					try {os.close();} catch (Exception e) {}
				}
				
				TimeUnit.MILLISECONDS.sleep(10L);
				raf = new RandomAccessFile(f, "rwd");
				raf.seek(startRange);
				raf.write(buf);
				try {raf.close();} catch (Exception e) {}
 
				// 5) 如果Web资源已经全部传输完了,将.tmp文件的后缀去掉,还原为文件本来的后缀和格式,然后结束while循环
				// 从Web服务器返回的内容中的字节,是以0为索引开始计数的
				if (resourceEndPos == resourceTotalLength - 1) {
					f.renameTo(new File(LOCAL_PATH + File.separator + url.substring(url.lastIndexOf("/") + 1)));
					break;
				}
				
				// 6) 如果Web资源没有传输完,计算下一次传输的Range的范围
				// 如果剩下的要传输的内容不超过FRAGMENT_SIZE的1.5倍,就一起全部传输过来,减少HttpURLConnection连接带来的资源消耗
				if (resourceTotalLength - resourceEndPos <= FRAGMENT_SIZE * 3 / 2) {
					startRange = resourceEndPos;
					endRange = -1L;
				} else {
					startRange = resourceEndPos;
					endRange = resourceEndPos + FRAGMENT_SIZE;
				}
				System.out.println("next startRange=" + startRange + ", endRange=" + endRange);
			} catch (Exception e) {
				System.err.println(e.getMessage());
				return;
			} finally {
				if (in != null) {
					try {in.close();} catch (Exception e) {}
				}
				if (conn != null) {
					conn.disconnect();
				}
			}
		}
		long time = System.currentTimeMillis() - startTime;
		System.out.println("传输Web资源[" + url + "]共耗时" + time / 1000 + "s" + time % 1000 + "ms");
	}
 
	/**
	 * URL格式限定如下:
	 * <p>
	 * URL的长度至少是20
	 * </p>
	 * <p>
	 * URL的协议类型必须是HTTP
	 * </p>
	 * <p>
	 * URL中必须包含".",且"."后面的字符个数不能超过10
	 * </p>
	 * 
	 * @param url
	 * @return
	 */
	private static String checkURLFormat(String url) {
		if (url.length() < 20) {
			return "url的长度至少为20";
		}
 
		String protocol = url.substring(0, 7);
		if (!protocol.equalsIgnoreCase("http://")) {
			return "url必须以http://开头(不区分大小写)";
		}
 
		int dotIndex = url.lastIndexOf(".");
		if (dotIndex == -1) {
			return "url中必须有'.'";
		}
 
		String resourceSuffix = url.substring(dotIndex);
		if (resourceSuffix.length() > 10) {
			return "url格式不正确,资源名称的后缀('.'后面的字符)不能超过10个字符";
		}
		return null;
	}
 
	/**
	 * 本地文件格式限定如下:
	 * <p>
	 * 如果localFile不为null,那么该文件在本地必须存在并且是文件
	 * </p>
	 * <p>
	 * 如果localFile不为null,那么该文件的后缀必须是.tmp,说明该文件在之前没有传输完,本次继续传输
	 * </p>
	 * 
	 * @param localFile
	 * @return
	 */
	private static String checkLocalFileFormat(String localFile) {
		if (localFile == null) {
			return null;
		}
 
		String retMsg = null;
		try {
			File f = new File(localFile);
			if (!f.exists()) {
				retMsg = "本地文件[" + localFile + "]不存在";
			} else if (!f.isFile()) {
				retMsg = "本地文件[" + localFile + "]不是一个文件";
			} else if (!localFile.endsWith(".tmp")) {
				retMsg = "本地文件[" + localFile + "]应该以.tmp结尾";
			}
		} catch (Exception e) {
			retMsg = "在读取本地文件[" + localFile + "]时出错,请检查该文件";
		}
		return retMsg;
	}
}
8人推荐
随时随地看视频
慕课网APP