三亚承包土方工程骗局:点对点(P2P)多线程断点续传的实现

来源:百度文库 编辑:中财网 时间:2024/03/29 23:48:22
日期:2004-06-21 来源:P2P中国  作者:赵明 字体:【大中小】评论:(25)条
P2P中国下载源代码:
upload/2004_06/04062118573143.zip
在如今的网络应用中,文件的传送是重要的功能之一,也是共享的基础。一些重要的协议像HTTP,FTP等都支持文件的传送。尤其是FTP,它的全称就是“文件传送协议”,当 初的工程师设计这一协议就是为了解决网络间的文件传送问题,而且以其稳定,高速,简单而一直保持着很大的生命力。作为一个程序员,使用这些现有的协议传送文件相当简单,不过,它们只适用于服务器模式中。这样,当我们想在点与点之间传送文件就不适用了或相当麻烦,有一种大刀小用的意味。笔者一直想寻求一种简单有效,且具备多线程断点续传的方法来实现点与点之间的文件传送问题,经过大量的翻阅资料与测试,终于实现了,现把它共享出来,与大家分享。
我写了一个以此为基础的实用程序(网络传圣,包含源代码),可用了基于TCP/IP的电脑上,供大家学习。
upload/2004_06/04062118541204.gif
(本文源代码运行效果图)
实现方法(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.Get
Buffer(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.GetBuff
er(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.GetBu
ffer(0),1);break;//我准备好了,可以传文件了case 2://发文件消息给主函数aaa.Format("%s 文件被请求!%s\n",zmfile[fiinfo->fileno].name,nameph[fii
nfo->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.GetBu
ffer(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::shareDen
yNone);//传到指定位置 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();//发命令1downinfo doinfo;//文件信息(与服务器定义一样)int startask(int n);开始传文件nlong 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;i0)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::typeBina
ry);//写入文件长度myfile.Write(&doinfo.filelen,sizeof(int));myfile.Close(); CString temp;for(int ii=0;iiry);//写入各进程文件信息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;iim_work.m_ListCtrl->AddItemtwo(n,2,doi
nfo.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;ihreadno],0,&dwthread);}}
downthread进程函数
DWORD WINAPI downthread(LPVOID lpparam){cdownload* pthis=(cdownload*)lpparam;//进程引索+1InterlockedIncrement(&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::typeB
inary|CFile::shareDenyNone);else//如果文件存在,是续传destFile.Open(fname,CFile::modeWrite | CFile::typeBinary|CFile::shareD
enyNone);//文件指针移到指定位置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