斷點續傳教學例子

wyfem發表於2021-09-09
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>假設要下載的資源為,分段下載的思路如下:</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;
	}
}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2894/viewspace-2823981/,如需轉載,請註明出處,否則將追究法律責任。

相關文章