• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            牽著老婆滿街逛

            嚴以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            點對點多線程斷點續傳的實現

            作者:趙明

            下載配套源代碼(網絡傳圣源代碼)
            下載地址二 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<BLACK;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<BLACK;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<BLACK;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<BLACK;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.com 或 zmpapaya@hotmail.com
            主頁: http://h2osky.126.com

            posted on 2006-06-20 18:56 楊粼波 閱讀(366) 評論(0)  編輯 收藏 引用 所屬分類: 文章收藏

            久久久久免费看成人影片| 久久男人中文字幕资源站| 久久精品国产99久久无毒不卡| 午夜天堂av天堂久久久| 国产精品99久久免费观看| 国产精品一区二区久久精品无码| 久久精品一区二区三区中文字幕| 久久无码AV一区二区三区| 久久久久久午夜成人影院| 久久精品无码一区二区app| 色综合久久久久无码专区 | 久久午夜无码鲁丝片| 99久久精品九九亚洲精品| 久久精品国产2020| 久久青青草原精品影院| 精品久久亚洲中文无码| 久久久久亚洲精品天堂久久久久久| 亚洲精品乱码久久久久久蜜桃不卡| 久久精品成人一区二区三区| 99久久精品午夜一区二区| 国产成人精品综合久久久| 欧美久久久久久午夜精品| 色婷婷综合久久久久中文| 波多野结衣久久一区二区| 色婷婷久久久SWAG精品| 日韩欧美亚洲综合久久影院d3| 性做久久久久久久| 无码精品久久久久久人妻中字| 无夜精品久久久久久| 亚洲伊人久久综合影院| 欧美激情精品久久久久久久九九九 | 亚洲欧洲精品成人久久曰影片| 91秦先生久久久久久久| 成人妇女免费播放久久久| 久久精品人成免费| 色婷婷综合久久久久中文字幕 | 亚洲精品无码久久久| 亚洲一区二区三区日本久久九| 久久精品a亚洲国产v高清不卡| 久久精品国产亚洲AV麻豆网站| 精品久久久久久国产潘金莲|