茫茫網海中的冷日
         
茫茫網海中的冷日
發生過的事,不可能遺忘,只是想不起來而已!
 恭喜您是本站第 1671498 位訪客!  登入  | 註冊
主選單

Google 自訂搜尋

Goole 廣告

隨機相片
IMG_60D_00003.jpg

授權條款

使用者登入
使用者名稱:

密碼:


忘了密碼?

現在就註冊!

屹立不倒的C : [分享]續傳軟體概念解說與實現

發表者 討論內容
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[分享]續傳軟體概念解說與實現
首先我們先認識一下,在 HTTP 的回應狀態碼裡面,有一個 206 Partial Content。
她的意思如下:
因為頻寬限制,Server 將較大的網頁或檔案分次傳送,由於僅能傳輸部分內容,因此以此訊息通知 Client 資料尚未傳完
簡單的說,會看到這個 Code 即表示『伺服器僅完成了部分用戶的GET請求』

換言之,我們就是碰上了所謂的『續傳軟體』啦!

而 flashget 這一類所謂「多線程下載」或叫「斷點續傳」軟體,就是每次請求部分資料而已!
但不知道大家是否會有如下幾個疑問:
1.第一個線程請求的一定是所有的資料(啟動時一定是單點開始),從伺服器回傳的數據中 Header content-length 顯示的是請求的文件大小,但是消息體實際沒有包含所有的數據。
這和協議不一致,http1.1中規定如果有content-length字段,這個字段的值就必須是消息體的字符數。
2.各個線程請求的數據都是從某個位置開始直到文件結束的數據,這樣各個線程得到的數據就有重疊部分。這些數據是怎麼合併的?

此時我們可以參閱後面兩篇資料,透過程式來說明!
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]點對點多線程斷點續傳的實現

點對點多線程斷點續傳的實現
作者: 趙明

下載配套源代碼(網絡傳聖源代碼)
下載地址二 http://h2osky.126.com

在如今的網絡應用中,文件的傳送是重要的功能之一,也是共享的基礎。一些重要的協議像HTTP,FTP等都支持文件的傳送。尤其是FTP,它的全稱就是「文件傳送協議」,當初的工程師設計這一協議就是為了解決網絡間的文件傳送問題,而且以其穩定,高速,簡單而一直保持著很大的生命力。作為一個程序員,使用這些現有的協議傳送文件相當簡單,不過,它們只適用於服務器模式中。這樣,當我們想在點與點之間傳送文件就不適用了或相當麻煩,有一種大刀小用的意味。筆者一直想尋求一種簡單有效,且具備多線程斷點續傳的方法來實現點與點之間的文件傳送問題,經過大量的翻閱資料與測試,終於實現了,現把它共享出來,與大家分享。
我寫了一個以此為基礎的實用程序(網絡傳聖,包含源代碼),可用了基於TCP/IP的電腦上,供大家學習。


(本文源代碼運行效果圖)


實現方法(VC++,基於TCP/IP協議)如下:
仍釆用服務器與客戶模式,需分別對其設計與編程。

服務器端較簡單,主要就是加入待傳文件,監聽客戶,和傳送文件。而那些斷點續傳的功能,以及文件的管理都放在客戶端上。

一、服務器端

首先介紹服務器端:
最開始我們要定義一個簡單的協議,也就是定義一個服務器端與客戶端聽得懂的語言。而為了把問題簡化,我就讓服務器只要聽懂兩句話,一就是客戶說「我要讀文件信息」,二就是「我準備好了,可以傳文件了」。
由於要實現多線程,必須把功能獨立出來,且包裝成線程,首先建一個監聽線程,主要負責接入客戶,並啟動另一個客戶線程。我用VC++實現如下:

DWORD WINAPI listenthread(LPVOID lpparam)
{
//由主函數傳來的套接字
  SOCKET pthis=(SOCKET)lpparam;
//開始監聽
int rc=listen(pthis,30);
//如果錯就顯示信息
if(rc<0){
CString aaa;
aaa="listen錯誤\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
aaa.ReleaseBuffer();
return 0;
}
//進入循環,並接收到來的套接字
while(1){
//新建一個套接字,用於客戶端
SOCKET s1;
s1=accept(pthis,NULL,NULL);
  //給主函數發有人聯入消息
CString aa;
aa="一人聯入!\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aa.GetBuffer(0),1);
aa.ReleaseBuffer();
DWORD dwthread;
//建立用戶線程
::CreateThread(NULL,0,clientthread,(LPVOID)s1,0,&dwthread);
}
return 0;
}
接著我們來看用戶線程:

先看文件消息類定義

struct fileinfo
{
int fileno;//文件號
int type;//客戶端想說什麼(前面那兩句話,用1,2表示)
long len;//文件長度
int seek;//文件開始位置,用於多線程
char name[100];//文件名
};
用戶線程函數:
DWORD WINAPI clientthread(LPVOID lpparam)
{
//文件消息
fileinfo* fiinfo;
//接收緩存
char* m_buf;
m_buf=new char[100];
//監聽函數傳來的用戶套接字
SOCKET pthis=(SOCKET)lpparam;
//讀傳來的信息
int aa=readn(pthis,m_buf,100);
//如果有錯就返回
if(aa<0){
closesocket (pthis);
return -1;
}
//把傳來的信息轉為定義的文件信息
fiinfo=(fileinfo*)m_buf;
CString aaa;
//檢驗客戶想說什麼
switch(fiinfo->type)
{
//我要讀文件信息
case 0:
//讀文件
aa=sendn(pthis,(char*)zmfile,1080);
//有錯
if(aa<0){
closesocket (pthis);
return -1;
}
//發消息給主函數
aaa="收到LIST命令\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
break;
//我準備好了,可以傳文件了
case 2:
//發文件消息給主函數
aaa.Format("%s 文件被請求!%s\n",zmfile[fiinfo->fileno].name,nameph[fiinfo->fileno]);
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
//讀文件,並傳送
readfile(pthis,fiinfo->seek,fiinfo->len,fiinfo->fileno);
//聽不懂你說什麼
default:
aaa="接收協議錯誤!\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
break;
}
return 0;
}
讀文件函數
void readfile(SOCKET  so,int seek,int len,int fino)
{
//文件名
CString myname;
myname.Format("%s",nameph[fino]);
CFile myFile;
//打開文件
myFile.Open(myname, CFile::modeRead | CFile::typeBinary|CFile::shareDenyNone);
//傳到指定位置 
myFile.Seek(seek,CFile::begin);
char m_buf[SIZE];
int len2;
int len1;
len1=len;
//開始接收,直到發完整個文件
while(len1>0){
len2=len>SIZE?SIZE:len;
myFile.Read(m_buf, len2);
int aa=sendn(so,m_buf,len2);
if(aa<0){
closesocket (so);
break;
}
len1=len1-aa;
len=len-aa;
}
myFile.Close();
}

服務器端最要的功能各技術就是這些,下面介紹客戶端。

二、客戶端

客戶端最重要,也最複雜,它負責線程的管理,進度的記錄等工作。

大概流程如下:
先連接服務器,接著發送命令1(給我文件信息),其中包括文件長度,名字等,然後根據長度決定分幾個線程下載,並初使化下載進程,接著發送命令2(可以給我傳文件了),並記錄文件進程。最後,收尾。
這其中有一個十分重要的類,就是cdownload類,定義如下:

class cdownload
{
public:
void createthread();//開線程
DWORD finish1();//完成線程
int sendlist();//發命令1
downinfo doinfo;//文件信息(與服務器定義一樣)
int startask(int n);開始傳文件n
long m_index;
BOOL good[BLACK];
int filerange[100];
CString fname;
CString fnametwo;
UINT threadfunc(long index);//下載進程
int sendrequest(int n);//發文件信息
cdownload(int thno1);
virtual ~cdownload();
};
下面先介紹sendrequest(int n),在開始前,向服務器發獲得文件消息命令,以便讓客戶端知道有哪些文件可傳
int cdownload::sendrequest(int n)
{
//建套接字
sockaddr_in local;
SOCKET m_socket;
int rc=0;
//初使化服務器地址
local.sin_family=AF_INET;
local.sin_port=htons(1028);
local.sin_addr.S_un.S_addr=inet_addr(ip);
m_socket=socket(AF_INET,SOCK_STREAM,0);
int ret;
//聯接服務器
ret=connect(m_socket,(LPSOCKADDR)&local,sizeof(local));
//有錯的話
if(ret<0){
AfxMessageBox("聯接錯誤");
closesocket(m_socket);
return -1;
}
//初使化命令
fileinfo fileinfo1;
fileinfo1.len=n;
fileinfo1.seek=50;
fileinfo1.type=1;
//發送命令
int aa=sendn(m_socket,(char*)&fileinfo1,100);
if(aa<0){
closesocket(m_socket);
return -1;
}
//接收服務器傳來的信息
aa=readn(m_socket,(char*)&fileinfo1,100);
if(aa<0){
closesocket(m_socket);
return -1;
}
//關閉
shutdown(m_socket,2);
closesocket(m_socket);
return 1;
}
有了文件消息後我們就可以下載文件了。在主函數中,用法如下:

//下載第clno個文件,並為它建一個新cdownload類
down[clno]=new cdownload(clno);
//開始下載,並初使化
type=down[clno]->startask(clno);
//建立各線程
createthread(clno);
下面介紹開始方法:
//開始方法
int cdownload::startask(int n)
{
//讀入文件長度
doinfo.filelen=zmfile[n].length;
//讀入名字
fname=zmfile[n].name;
CString tmep;
//初使化文件名
tmep.Format("\\temp\\%s",fname);
//給主函數發消息
CString aaa;
aaa="正在讀取 "+fname+" 信息,馬上開始下載。。。\n";
AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
aaa.ReleaseBuffer();
//如果文件長度小於0就返回
if(doinfo.filelen<=0) return -1;
//建一個以.down結尾的文件記錄文件信息
CString m_temp;
m_temp=fname+".down";
doinfo.name=m_temp;
FILE* fp=NULL;
CFile myfile;
//如果是第一次下載文件,初使化各記錄文件
if((fp=fopen(m_temp,"r"))==NULL){
filerange[0]=0;
//文件分塊
for(int i=0;i {
if(i>0)
filerange[i*2]=i*(doinfo.filelen/BLACK+1);
filerange[i*2+1]=doinfo.filelen/BLACK+1;
}
filerange[BLACK*2-1]=doinfo.filelen-filerange[BLACK*2-2];
myfile.Open(m_temp,CFile::modeCreate|CFile::modeWrite | CFile::typeBinary);
//寫入文件長度
myfile.Write(&doinfo.filelen,sizeof(int));
myfile.Close();
 
CString temp;
for(int ii=0;ii //初使化各進程記錄文件信息(以.downN結尾)
temp.Format(".down%d",ii);
m_temp=fname+temp;
myfile.Open(m_temp,CFile::modeCreate|CFile::modeWrite | CFile::typeBinary);
//寫入各進程文件信息
myfile.Write(&filerange[ii*2],sizeof(int));
myfile.Write(&filerange[ii*2+1],sizeof(int));
myfile.Close();
}
((CMainFrame*)::AfxGetMainWnd())->m_work.m_ListCtrl->AddItemtwo(n,2,0,0,0,doinfo.threadno);
}
else{
//如果文件已存在,說明是續傳,讀上次信息
CString temp;
 
m_temp=fname+".down0";
if((fp=fopen(m_temp,"r"))==NULL)
return 1;
else fclose(fp);
int bb;
bb=0;
//讀各進程記錄的信息
for(int ii=0;ii {
temp.Format(".down%d",ii);
m_temp=fname+temp;
 
myfile.Open(m_temp,CFile::modeRead | CFile::typeBinary);
myfile.Read(&filerange[ii*2],sizeof(int));
myfile.Read(&filerange[ii*2+1],sizeof(int));
myfile.Close();
bb = bb+filerange[ii*2+1];
CString temp;
}
if(bb==0) return 1;
doinfo.totle=doinfo.filelen-bb;
 
((CMainFrame*)::AfxGetMainWnd())->m_work.m_ListCtrl->AddItemtwo(n,2,doinfo.totle,1,0,doinfo.threadno);
}
  //建立下載結束進程timethread,以管現各進程結束時間。
DWORD dwthread;
::CreateThread(NULL,0,timethread,(LPVOID)this,0,&dwthread);
return 0;
}
下面介紹建立各進程函數,很簡單:
void CMainFrame::createthread(int threadno)
{
DWORD dwthread;
//建立BLACK個進程
for(int i=0;i {
m_thread[threadno][i]= ::CreateThread(NULL,0,downthread,(LPVOID)down[threadno],0,&dwthread);
}
}
downthread進程函數
DWORD WINAPI downthread(LPVOID lpparam)
{
cdownload* pthis=(cdownload*)lpparam;
//進程引索+1
InterlockedIncrement(&pthis->m_index);
//執行下載進程
pthis->threadfunc(pthis->m_index-1);
return 1;
}
下面介紹下載進程函數,最最核心的東西了
UINT cdownload::threadfunc(long index)
{
//初使化聯接
sockaddr_in local;
SOCKET m_socket;
int rc=0;
 
local.sin_family=AF_INET;
local.sin_port=htons(1028);
local.sin_addr.S_un.S_addr=inet_addr(ip);
m_socket=socket(AF_INET,SOCK_STREAM,0);
int ret;
//讀入緩存
char* m_buf=new char[SIZE];
int re,len2;
fileinfo fileinfo1;
//聯接
ret=connect(m_socket,(LPSOCKADDR)&local,sizeof(local));
//讀入各進程的下載信息
fileinfo1.len=filerange[index*2+1];
fileinfo1.seek=filerange[index*2];
fileinfo1.type=2;
fileinfo1.fileno=doinfo.threadno;
 
re=fileinfo1.len;
 
//打開文件 
CFile destFile;
FILE* fp=NULL;
//是第一次傳的話
if((fp=fopen(fname,"r"))==NULL)
destFile.Open(fname, CFile::modeCreate|CFile::modeWrite | CFile::typeBinary|CFile::shareDenyNone);
else
//如果文件存在,是續傳
destFile.Open(fname,CFile::modeWrite | CFile::typeBinary|CFile::shareDenyNone);
//文件指針移到指定位置
destFile.Seek(filerange[index*2],CFile::begin);
//發消息給服務器,可以傳文件了
sendn(m_socket,(char*)&fileinfo1,100);
CFile myfile;
CString temp;
temp.Format(".down%d",index);
m_temp=fname+temp;
  //當各段長度還不為0時
while(re>0){
len2=re>SIZE?SIZE:re;
 
//讀各段內容
int len1=readn(m_socket,m_buf,len2);
//有錯的話
if(len1<0){
closesocket(m_socket);
break;
}
 
//寫入文件
destFile.Write(m_buf, len1);
//更改記錄進度信息
filerange[index*2+1]-=len1;
filerange[index*2]+=len1;
//移動記錄文件指針到頭
myfile.Seek(0,CFile::begin);
//寫入記錄進度
myfile.Write(&filerange[index*2],sizeof(int));
myfile.Write(&filerange[index*2+1],sizeof(int));
//減去這次讀的長度
re=re-len1;
//加文件長度
doinfo.totle=doinfo.totle+len1;
};
//這塊下載完成,收尾
 
myfile.Close();
destFile.Close();
delete [] m_buf;
shutdown(m_socket,2);
 
 
if(re<=0) good[index]=TRUE;
return 1;
}

到這客戶端的主要模塊和機制已基本介紹完。希望好好體會一下這種多線程斷點續傳的方法。

作者信息:
姓名:趙明
email: papaya_zm@sina.comzmpapaya@hotmail.com
主頁: http://h2osky.126.com


原文出處:VC知识库文章 - 点对点多线程断点续传的实现
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]ftp協議實現多線程斷點續傳

ftp協議實現多線程斷點續傳

作者: 吳康彬

下載源代碼

  ftp下載的好處我在這裡就不多說了,許多工程會把ftp下載作為一個重要的功能來實現。微軟提供的WinInet類可以利用下面這些函數:


InternetOpen;
InternetConnect;
GetCurrentDirectory;
SetCurrentDirectory;
FtpGetFile;

  很容易實現ftp的下載,網上關於這方面的文章也很多。但是要實現ftp的多線程下載,利用這些函數就顯得有些牽強了。用socket根據ftp協議來開發將會變的十分靈活。下面我就逐步的講解整個開發的過程:開發環境 BCB(組件模式),VC 環境下請自行稍作改動。看了這篇文章後對於BCB開發人員來說,不僅可以對 FlashGet 等軟件的開發原理有一定的瞭解,特別是在開發組件方面也有很大的指導作用,請耐心的將它看完。很簡單!!

首先介紹一下部分ftp協議:


圖一 FTP服務示意圖

  用戶FTP和服務器FTP之間要傳送文件,需要有兩個連接:命令通道和數據連接,從名字上就可以看出命令通道是傳送命令的,數據通道是用於傳送文件。服務器與服務器之間的數據傳送在此就不多作解釋。
  主要用到的命令為:USER,PASS,TYPE,SIZE,REST,CWD,PWD,RETR,PASV,PORT,QUIT;

  • USER:參數是標記用戶的Telnet串。用戶標記是訪問服務器必須的,此命令通常是控制連接後第一個發出的命令,有些主機還會要求口令和帳戶。服務器可以在任何時間接收新的USER命令以改變訪問控制和(或)帳戶信息。這可以重新開始登錄過程,所以傳輸參數不變,在進行中的文件傳輸在過去的訪問控制參數下完成。
  • PASS:參數是標記用戶口令的Telnet串。此命令緊跟USER命令,在某些站點它是完成訪問控制不可缺少的一步。因此口令是個重要的東西,因此不能顯示出來,服務器方沒有辦法隱藏口令,所以這一任務得由用戶FTP進程完成。
  • TYPE:參數指定表示類型。有些類型需要第二個參數,第一個參數由單個Telnet字符定義,第二個參數是十進制整數指定字節大小,參數間以<SP>分隔。下面是格式:



    圖二 TYPE參數示意圖

    默認表示類型是ASCII非打印字符,如果參數未改變,以後只改變了第一個參數,則使用默認值。
  • SIZE:參數從FTP服務器上返回指定文件的大小。
  • REST:參數域代表服務器要重新開始的那一點,此命令並不傳送文件,而是略過指定點後的數據,此命令後應該跟其它要求文件傳輸的FTP命令。
  • CWD:此命令使用戶可以在不同的目錄或數據集下工作而不用改變它的登錄或帳戶信息。傳輸參數也不變。參數一般是目錄名或與系統相關的文件集合。
  • PWD:改變當前的工作目錄。
  • RETR:開始傳送指定的文件。(從REST參數指定的偏移量開始傳送)
  • PASV:此命令要求服務器DTP在指定的數據端口偵聽,進入被動接收請求的狀態,參數是主機和端口地址。
  • PORT:參數是要使用的數據連接端口,通常情況下對此不需要命令響應。如果使用此命令時,要發送32位的IP地址和16位的TCP端口號。上面的信息以8位為一組,逗號間隔十進制傳輸。
  • QUIT:退出登錄。

各個參數的具體用法舉例如下:



USER sandy \r\n //用戶名為sandy登錄

PASS sandy \r\n //密碼為sandy

TYPE I \r\n

SIZE sandy.txt \r\n //如果sandy.txt文件存在,則返回該文件的大小

REST 100 \r\n //重新指定文件傳送的偏移

CWD infor/ \r\n //獲取當前的工作目錄

PWD temp/ \r\n //改變當前的工作目錄

RETR \r\n //開始傳送文件

PASV \r\n //進入被動模式

PORT h1,h2,h3,h4,p1,p2 \r\n //進入主動模式,h1,h2,h3,h4為ip地址的4個部分。p1,p2是16進制的端口號。
下面介紹一下各個函數的使用順序和一些應注意的地方:


  使用這些命令的前提條件是客戶端和服務器端建立了連接。比如ftp服務器地址:192.168.1.81 ,端口:21。那麼利用Winsock的API函數建立socket連接,然後使用USER,PASS登陸FTP服務器.需要下載文件,要確保文件必須在當前工作目錄下,可以使用命令CWD和PWD。查看和更改當前的工作目錄。使用SIZE命令獲取文件的大小。我們想要多線程下載那麼就要求服務器支持該功能。一般我們都會在開頭先使用REST命令判斷該ftp站點是否支持多線程下載。PORT和PASV兩個命令是用來建立數據連接的。他們的主要區別是:PORT需要你指定一個ip地址和端口與服務器建立連接。PASV命令服務器會返回h1,h2,h3,h4,p1,p2樣式 的數據供客戶端連接。等數據連接建立後,就可以了使用REST,RETR進行多線程和斷點續傳文件下載了。
  上面講解了一點ftp下載的基本知識,下面主要介紹的是斷點續傳的文件保存技巧。
若要講斷點續傳的文件保存方式至少可以說出10種,但是各種方法都有利有弊,下面主要介紹一種我在工作中常常使用的一種文件保存方式:比如要下載一個364544字節的文件,文件名為:namelock.avi。因為要斷點續傳,所以 在下載的過程中必須得保存文件的大小,已經下載的文件的大小和各個線程的任務。有兩種方法:一、可以產生兩個文件:內容文件和配置文件。二、只需一個文件:把配置文件的數據加載到內容文件的末尾。這兩個都不失為好方法。我使用的是前一種,因為我水平有限,(對於臨界資源的訪問總是不能做到互坼,老出問題。)。這裡 的後綴名希望大家要把它放在心上,後綴名是個象徵性的東西。就拿我們funinhand(
廣州富年電子科技有限公司)公司來說,擁有自己的MPEG編碼、解碼技術,比如原來5m的一首mp3歌曲,通過編碼可以 轉換成500K左右的.fun文件(funinhand的前三個字)。再利用我們自己的解碼播放器邊下載邊解碼邊播放, 音質和mp3不相上下。真正實現了手機上的流媒體技術。受到國內外高科技大公司的信賴。(不好意思,這裡有點像做廣告了。)講這些的另外一個企圖是這樣的:內容文件所使用的後綴名是我女朋友的英文名(namelock)的前三個字母.nam 。配置文件使用的是我自己的英文名(sandy)的前三個字母.san 。所以說寫程序也可以很浪漫,因為這,女朋友又給了我的月生活零用錢增加了幾元,哈哈(大家也可以效仿)。言歸正傳,這兩個文件嚴格意義上來講是臨時文件,當文件下載完畢的時候,namelock.avi.nam內容文件應該改名為:namelock.avi。namelock.avi.san配置文件也應該及時的刪除。
  FTP多線程下載技術部分:前面我介紹了文件的保存技巧,主要也是為了多線程服務。現在有個namelock.avi文件需要下載。文件的大小為:364544字節。要用8個下載線程。 第一步:將namelock.avi文件分成8個子模塊。這裡要注意的地方是我所說的分成8個字模塊,並不是把文件的內容分別存放到8個不同的緩衝區裡。而是生成8個不同的文件偏移量。很多時候程序員為了偷懶往往容易一次性講文件讀入內存,這樣帶來的後果是不堪設想的。一個比較理想的方法是這樣的。

bool DealFile(string fileName) //隨便寫個函數說明

{

FILE *file;

DWORD fileSize ,pos;

int readLen ;
//MAX_BUFFER_LEN 在頭文件裡定義,這裡能夠保證數據不丟失,也不至於內存逸出

char *buffer = new char[MAX_BUFFER_LEN];
file = fopen(fileName.c_str(),"r+b");

  if(file == NULL) return false;

fseek(file,0,2);

fileSize = ftell(file); //取得文件的大小

fseek(file,0,0);

do{

readLen = fread(buffer,sizeof(char),MAX_BUFFER_LEN,file);

if(readLen > 0)

{

pos += readLen;

//對讀取的文件做處理

}

}while(pos < fileSize); //循環讀取文件

 delete[] buffer;

fclose(file); //釋放資源

return true;

}



  8個線程下載文件時,都要對內容文件和配置文件進行讀寫。這樣如果沒有處理好,很有可能會造成訪問文件失敗,我定義了一個全局變量FileLocked,如果FileLocked=true說明文件正在被某個線程訪問。所以使用Sleep(10)睡眠等待。當某個線程進入讀寫文件時必須設置FileLocked = true;訪問文件完畢必須將FileLocked = false;這樣就能很好的控制各個線程對文件的訪問了。(對臨界資源的訪問有API提供了很多很好的解決方法,請查閱)。
  8個下載線程同時下載文件時,完成部分下載是隨機的。那麼怎麼樣把隨機的文件數據按照偏移量正確的寫入文件呢?我是這樣實現的,當要下載文件namelock.avi時,首先查找文件namelock.avi.san配置文件是否存在。如果存在,說明上次已經下載過部分該文件,就可以斷點續傳了。如果沒有找到該文件,那麼生成和該文件的大小一樣大的文件,文件裡所有的數據都為0,(可以使用函數memset(buffer,10000,''0''))和一個配置文件。然後利用fseek函數將數據正確的覆蓋原先的0;接下來要介紹一寫配置文件的格式了。很簡單,配置文件的內容主要包括:文件在本地保存的絕對路徑、文件的大小、線程的個數、已經下載的文件大小,各個線程的任務(在原始文件起始位置和結束位置,中間使用''-''分開);如:


D:\mm\namelock.avi //文件保存在這裡
364544 //文件大小
5 //有5個線程在下載
0 //已經下載了0字節
0-72908 //線程1的下載任務
72908-145816 //線程2的下載任務
145816-218724 //線程3的下載任務
218724-291632 //線程4的下載任務
291632-364544 //線程5的下載任務



以上是開始下載時的各個線程的任務分配。


D:\mm\namelock.avi
364544
5
113868
72908-72908
113868-145816
145816-218724
218724-291632
291632-364544
以上是某一時刻各個線程的任務分配情況。

  各個線程任務分配是這樣實現的。在開始下載時,文件平均分成若干塊進行下載。如第一個線程一開始的任務是從文件的0位置開始下載一直到72908位置處。線程1每次下載一塊數據後就要調整任務,如第一次下載了20800字節的數據,那麼線程1的任務將改為:20800-72908。如此下去,直到任務為72908-72908時表示線程1完成了當前的下載任務。此時,線程1就分析各個線程的任務,找出任務最為繁忙的一個線程:如線程3:14816-218724。那麼線程1就自動去調整任務,拿50%的任務來再次下載。週而復始直到各個線程都完成任務。不過這裡有一點需要注意:為了避免重複下載部分數據,在調整任務的時候,起始的文件便移量必須加上接受緩衝器的字節數,因為如前面所舉的列子來看。線程1和線程3在平衡負載的時候,線程正在下載數據,如果所剩的數據比接受緩衝器的大小還小,線程1和線程3的部分下載數據將會重複。
  在調整任務和分析任務的時候,會發現一個問題。就是讀取文件數據太過頻繁。於是我用了一個數據結構。在下載文件的過程中始終打開配置文件,這樣速度提高了很多。在文件下載完畢後關閉文件。數據結構如下:

typedef struct FromToImpl{
DWORD from; //任務起始位置
DWORD to; //任務結束位置
}m_fromTo;
typedef struct InfroImpl{
String fileLoad; //文件保存位置
DWORD fileSize; //文件大小
int threadCnt; //下載線程數
DWORD alreadyDownloadCnt; //已經下載的文件大小
FromToImpl *fromToImpl; //各個線程的任務描述
}m_inforImpl;

具體實現的細節,請查看源程序。

如果有什麼疑問或建議請與我聯繫,E-mail: wukangbin@funinhand.com


原文出處: VC知识库文章 - ftp协议实现多线程断点续传
前一個主題 | 下一個主題 | 頁首 | | |



Powered by XOOPS 2.0 © 2001-2008 The XOOPS Project|