Http多線程下載與斷點續(xù)傳分析
找了很多天的工作,真是找得有點郁悶,也就惰了下來!發(fā)現(xiàn)現(xiàn)在的簡歷真是不值錢。上次面試的時候本來投的是“高級程序員”職位,筆試也筆試,面試也面了。本來還是信心滿滿的. 不過后來在談到薪水的時候,被貶到了初級程序員,給的高級程序員標(biāo)準(zhǔn)我還達不到,中級程序員的標(biāo)準(zhǔn)也需要一定條件--中級證書,郁悶的是我也沒有!最后被定位為初級程序員!還真是有點打擊人。
不過我到現(xiàn)在也不知道初中高級程序員的標(biāo)準(zhǔn)究竟在哪里,平時總是盡量讓自己提高,也很少去考濾到證書的重要性!總之最后就是泡湯了
好了,口水就吐到這里吧,轉(zhuǎn)入正題!投簡歷歸投簡歷,剩余時間總是喜歡做一點自己喜歡做的事。
有時候發(fā)現(xiàn)最快樂的事就是靜靜的聽著音樂,敲著代碼,寫一些東西與朋友一起分享,一起研究。
上次的 - “Mp3在線搜索工具”還有很多可以改進的地方,也得到一些朋友的建議,非常感謝。這個版本中加入了斷點續(xù)傳的功能,使用了XML文件保存任務(wù)列表及狀態(tài)信息,并且支持多線程分段下載, 提高下載速度,在這一個版本中,我把它叫做: JLoading 因為我還想不出一個更好聽一點或更酷一些的名字,

而且我還想讓他可以下載一些其它文件。程序不想做大,但想做得極致一些,比如從速度上。歡迎交流學(xué)習(xí), huliqing(huliqing@live.com)
Jloading完整程序下載
Jloading源碼下載(僅供學(xué)習(xí)研究使用,有任何問題請與本人聯(lián)系)
協(xié)議針對于Http,先談一下簡單原理。因為代碼太多,在這里只取重點分析。
如果你對http協(xié)議比較了解,那么你應(yīng)該已經(jīng)知道原理了,只要在請求頭中加入以下代碼就可以只請求部分?jǐn)?shù)據(jù): Content-Range: bytes 20000-40000/47000 ,
即從第20000字節(jié)請求到第40000個字節(jié),(文件長度是47000字節(jié)).知道了這一點之后,請求數(shù)據(jù)就非常容易了,
只要利用Java中的URL,先探測數(shù)據(jù)的長度,然后再將這個長度分片段進行多段程下載就可以了,以下是我的實現(xiàn)方式:
// 對文件進行分塊
try {
totalBytes = new URL(url).openConnection().getContentLength();
if (totalBytes == -1) state = STATE_NONE;
} catch (IOException ioe) {
return;
}
// 創(chuàng)建分塊,并創(chuàng)建相應(yīng)的負(fù)責(zé)下載的線程
long pieceSize = (long) Math.ceil((double) totalBytes / (double) threads);
long pStart = 0;
long pEnd = 0;
tasksAll.clear();
tasks.clear();
for (int i = 0; i < threads; i++) {
if (i == 0) {
pStart = pieceSize * i;
}
if (i == threads - 1) {
pEnd = totalBytes;
} else {
pEnd = pStart + pieceSize;
}
Piece piece = new Piece(pStart, pStart, pEnd);
tasksAll.add(piece);
tasks.add(piece);
pStart = pEnd + 1;
}
程序根據(jù)線程數(shù)目劃分成相應(yīng)的分片信息(Piece)并放入任務(wù)列表中,由線程池中的線程去負(fù)責(zé)下載,每個線程負(fù)責(zé)一個片段(Piece),當(dāng)線程下載完自己的分片之后并不立即消毀,而是到任務(wù)隊列中繼續(xù)獲取任務(wù),
若任務(wù)池為空,則將自己放入空閑池中并等待新任務(wù),其他線程在發(fā)現(xiàn)有空閑線程時,則將自己所負(fù)責(zé)的任務(wù)分片再進行切割,并放入到任務(wù)隊列中,同時喚醒空閑線程幫助下載,這樣不會出現(xiàn)懶惰線程,也可以實現(xiàn)動態(tài)增刪線程的功能,注意的是一些線程同步的問題。
public void run() {
while (!dl.isOk()) {
// 暫停任務(wù)
synchronized (this) {
if (dl.isPaused()) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
}
// 中斷停止
if (Thread.interrupted() || dl.isStopped()) {
return;
}
// 等待獲取任務(wù)
Piece piece;
synchronized (tasks) {
while (tasks.isEmpty()) {
if (dl.isOk()) return;
try {
tasks.wait();
//System.out.println(this.getName() + ":wait


");
} catch (InterruptedException ie) {
//System.out.println(this.getName() +
// ":InterruptedException:" + ie.getMessage());
}
}
piece = tasks.remove(0);
dl.removeFreeLoader(this);
//System.out.println(this.getName() + ":loading


");
}
try {
URL u = new URL(dl.getURL());
URLConnection uc = u.openConnection();
// 設(shè)置斷點續(xù)傳位置
uc.setAllowUserInteraction(true);
uc.setRequestProperty("Range", "bytes=" + piece.getPos() + "-" + piece.getEnd());
in = new BufferedInputStream(uc.getInputStream());
out = new RandomAccessFile(dl.getFileProcess(), "rw");
out.seek(piece.getPos()); // 設(shè)置指針位置
long start;
long end;
int len = 0;
while (piece.getPos() < piece.getEnd()) {
start = System.currentTimeMillis();
len = in.read(buff, 0, buff.length);
if (len == -1) break;
out.write(buff, 0, len);
end = System.currentTimeMillis();
timeUsed += end - start; // 累計時間使用
long newPos = piece.getPos() + len;
// 如果該區(qū)段已經(jīng)完成,如果該線程負(fù)責(zé)的區(qū)域已經(jīng)完成,或出界
if (newPos > piece.getEnd()) {
piece.setPos(piece.getEnd());
long offset = newPos - piece.getEnd();
long trueReads = (len - offset + 1);
dl.growReadBytes(trueReads); // 修正偏移量
dl.setOffsetTotal(dl.getOffsetTotal() + trueReads);
readBytes += trueReads;
//System.out.println(this.getName() + ":read=" + trueReads);
} else {
dl.growReadBytes(len);
piece.setPos(piece.getPos() + len);
readBytes += len;
//System.out.println(this.getName() + ":read=" + len);
}
// 如果存在空閑的任務(wù)線程,則切割出新的區(qū)域至任務(wù)隊列中。由空閑
// 的線程輔助下載
if (dl.isFreeLoader()) {
Piece newPiece = piece.cutPiece();
if (newPiece != null) {
synchronized (tasks) {
dl.addTask(newPiece);
dl.setRepairCount(dl.getRepairCount() + 1); // 增加切割次數(shù)
tasks.notifyAll(); // 喚醒等待任務(wù)中的空閑線程
}
}
}
// 暫停任務(wù)
synchronized (this) {
if (dl.isPaused()) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
}
// 中斷停止
if (Thread.interrupted() || dl.isStopped()) {
in.close();
out.close();
return;
}
//System.out.println(this.getName() + ":read:" + dl.getReadBytes());
}
out.close();
in.close();
dl.addFreeLoader(this);
//System.out.println("切割次數(shù):" + dl.getRepairCount());
if (dl.isOk()) dl.processWhenOk();
} catch (IOException e) {
//System.out.println(this.getName() + ":無法讀取數(shù)據(jù)");
}
}
}
這里使用了RandomAccessFile進行保存本地文件,使用RandomAccessFile可以快速移動指針。另外一個就是需要在運行過程中,記得定時保存分片信息,以免在程序意外崩潰的情況下無法正常保存狀態(tài)信息。
程序可能還存在一些不足并且還有很多可以改進的地方。
以下是狀態(tài)文件Config的XML結(jié)構(gòu),這里記錄了每個任務(wù)的運行狀態(tài),piece作為每個分段的狀態(tài),在程序重啟之后載入這個配置文件,并在下載完成之后刪除這一文件.另外主目錄還有一個task.xml文件,用于記錄所有下載任務(wù),及狀態(tài)文件的位置。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<file id="1207786769343_4046.6321122755676"
length="3895507"
name="讀你 36首經(jīng)典精選"
save="C:\Documents and Settings\huliqing.TBUY-HULIQING\桌面\dist\musics\讀你 36首經(jīng)典精選[1].mp3"
threads="12">
<urls>
<url src="http://zlq.zust.edu.cn/Uploadfiles/wlhx/20071224220653927.mp3"/>
</urls>
<pieces>
<piece end="324626" pos="20767" start="0"/>
<piece end="649253" pos="419439" start="324627"/>
<piece end="973880" pos="892414" start="649254"/>
<piece end="1298507" pos="1068702" start="973881"/>
<piece end="1623134" pos="1318124" start="1298508"/>
<piece end="1947761" pos="1706453" start="1623135"/>
<piece end="2272388" pos="1987815" start="1947762"/>
<piece end="2597015" pos="2535705" start="2272389"/>
<piece end="2921642" pos="2671690" start="2597016"/>
<piece end="3246269" pos="3176315" start="2921643"/>
<piece end="3570896" pos="3522551" start="3246270"/>
<piece end="3895507" pos="3678693" start="3570897"/>
</pieces>
- huliqing@huliqing.name
- http://www.huliqing.name