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

Google 自訂搜尋

Goole 廣告

隨機相片
IMG_60D_00266.jpg

授權條款

使用者登入
使用者名稱:

密碼:


忘了密碼?

現在就註冊!

對這文章發表回應

發表限制: 非會員 可以發表

發表者: 冷日 發表時間: 2009/5/21 0:45:00

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

下載配套源代碼(網絡傳聖源代碼)
下載地址二 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知识库文章 - 点对点多线程断点续传的实现
內容圖示
url email imgsrc image code quote
樣本
bold italic underline linethrough   












 [詳情...]
validation picture

注意事項:
預覽不需輸入認證碼,僅真正發送文章時才會檢查驗證碼。
認證碼有效期10分鐘,若輸入資料超過10分鐘,請您備份內容後,重新整理本頁並貼回您的內容,再輸入驗證碼送出。

選項

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