王朝网络
分享
 
 
 

如何点对点实现多线程断点续传

王朝vc·作者佚名  2006-01-08
宽屏版  字体: |||超大  

在如今的网络应用中,文件的传送是重要的功能之一,也是共享的基础。一些重要的协议像HTTP,FTP等都支持文件的传送。尤其是FTP,它的全称就是“文件传送协议”,当初的工程师设计这一协议就是为了解决网络间的文件传送问题,而且以其稳定,高速,简单而一直保持着很大的生命力。作为一个程序员,使用这些现有的协议传送文件相当简单,不过,它们只适用于服务器模式中。这样,当我们想在点与点之间传送文件就不适用了或相当麻烦,有一种大刀小用的意味。笔者一直想寻求一种简单有效,且具备多线程断点续传的方法来实现点与点之间的文件传送问题,经过大量的翻阅资料与测试,终于实现了,现把它共享出来,与大家分享。

(我写了一个以此为基础的实用程序(网络传圣,包含源代码),可用了基于IP/TCP的电脑上,供大家学习。下载地址:

http://h2osky.126.com)

实现方法(VC++,基于TCP/IP协议)如下:

仍釆用服务器与客户模式,需分别对其设计与编程。

服务器端较简单,主要就是加入侍传文件,监听客户,和传送文件。而那些断点续传的功能,以及文件的管理都放在客户端上。

首先介络服务器端:

最开始我们要定义一个简单的协议,也就是定义一个服务器端与客户端听得懂的语言。而为了把问题简化,我就让服务器只要听懂两句话,一就是客户说“我要读文件信息”,二就是“我准备好了,可以传文件了”。

由于要实现多线程,必须把功能独立出来,且包装成线程,首先建一个监听线程,主要负责接入客户,并启动另一个客户线程。我用VC++实现如下:

DWORD WINAPI listenthread(LPVOID lpparam)

{

file://由主函数传来的套接字

SOCKET pthis=(SOCKET)lpparam;

file://开始监听

int rc=listen(pthis,30);

file://如果错就显示信息

if(rc<0){

CString aaa;

aaa="listen错误\n";

AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);

aaa.ReleaseBuffer();

return 0;

}

file://进入循环,并接收到来的套接字

while(1){

file://新建一个套接字,用于客户端http://h2osky.126.com

SOCKET s1;

s1=accept(pthis,NULL,NULL);

file://给主函数发有人联入消息

CString aa;

aa="一人联入!\n";

AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aa.GetBuffer(0),1);

aa.ReleaseBuffer();

DWORD dwthread;

file://建立用户线程

::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];//文件名http://h2osky.126.com

};

file://用户线程函数:

DWORD WINAPI clientthread(LPVOID lpparam)

{

file://文件消息

fileinfo* fiinfo;

file://接收缓存

char* m_buf;

m_buf=new char[100];

file://监听函数传来的用户套接字

SOCKET pthis=(SOCKET)lpparam;

file://读传来的信息

int aa=readn(pthis,m_buf,100);

file://如果有错就返回

if(aa<0){

closesocket (pthis);

return -1;

}

file://把传来的信息转为定义的文件信息

fiinfo=(fileinfo*)m_buf;

CString aaa;

file://检验客户想说什么

switch(fiinfo->type)

{

file://我要读文件信息

case 0:

file://读文件

aa=sendn(pthis,(char*)zmfile,1080);

file://有

if(aa<0){

closesocket (pthis);

return -1;

}

file://发消息给主函数

aaa="收到LIST命令\n";

AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);

break;

file://我准备好了,可以传文件了

case 2:

file://发文件消息给主函数

aaa.Format("%s 文件被请求!%s\n",zmfile[fiinfo->fileno].name,nameph[fiinfo->fileno]);

AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);

file://读文件,并传送

readfile(pthis,fiinfo->seek,fiinfo->len,fiinfo->fileno);

file://听不懂你说什么

default:

aaa="接收协议错误!\n";

AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);

break;

}

return 0;

}

/////////////http://h2osky.126.com

file://读文件函数

void readfile(SOCKET so,int seek,int len,int fino)

{

file://文件名

CString myname;

myname.Format("%s",nameph[fino]);

CFile myFile;

file://打开文件

myFile.Open(myname, CFile::modeRead | CFile::typeBinary|CFile::shareDenyNone);

file://传到指定位置

myFile.Seek(seek,CFile::begin);

char m_buf[SIZE];

int len2;

int len1;

len1=len;

file://开始接收,直到发完整个文件

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();//开线程http://h2osky.126.com

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)

{

file://建套接字

sockaddr_in local;

SOCKET m_socket;

int rc=0;

file://初使化服务器地址

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;

file://联接服务器

ret=connect(m_socket,(LPSOCKADDR)&local,sizeof(local));

file://有错的话

if(ret<0){

AfxMessageBox("联接错误");

closesocket(m_socket);

return -1;

}

file://初使化命令

fileinfo fileinfo1;

fileinfo1.len=n;

fileinfo1.seek=50;

fileinfo1.type=1;

file://发送命令

int aa=sendn(m_socket,(char*)&fileinfo1,100);

if(aa<0){ closesocket(m_socket);

return -1;}

file://接收服务器传来的信息

aa=readn(m_socket,(char*)&fileinfo1,100);

if(aa<0) {closesocket(m_socket);

return -1;}

file://关

shutdown(m_socket,2);

closesocket(m_socket);

return 1;

}

有了文件消息后我们就可以下载文件了

在主函数中,用法如下:

file://下载第clno个文件,并为它建一个新cdownload类

down[clno]=new cdownload(clno);

file://开始下载,并初使化

type=down[clno]->startask(clno);

file://建立各线程

createthread(clno);

介绍开始方法

file://开始方法

int cdownload::startask(int n)

{

file://读入文件长度

doinfo.filelen=zmfile[n].length;

file://读入名字

fname=zmfile[n].name;

CString tmep;

file://初使化文件名

tmep.Format("\\temp\\%s",fname);

file://给主函数发消息

CString aaa;

aaa="正在读取 "+fname+" 信息,马上开始下载。。。\n";

AfxGetMainWnd()->SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);

aaa.ReleaseBuffer();

file://如果文件长度小于0就返回

if(doinfo.filelen<=0)return -1;

file://建一个以.down结尾的文件记录文件信息

CString m_temp;

m_temp=fname+".dwon";

doinfo.name=m_temp;

FILE* fp=NULL;

CFile myfile;

file://如果是第一次下载文件,初使化各记录文件

if((fp=fopen(m_temp,"r"))==NULL){

filerange[0]=0;

file://文件分块

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);

file://写入文件长度

myfile.Write(&doinfo.filelen,sizeof(int));

myfile.Close();

CString temp;

for(int ii=0;ii<BLACK;ii++){

file://初使化各进程记录文件信息(以.downN结尾)

temp.Format(".down%d",ii);

m_temp=fname+temp;

myfile.Open(m_temp,CFile::modeCreate|CFile::modeWrite | CFile::typeBinary);

file://写入各进程文件信息

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);

}

file://如果文件已存在,说明是续传,读上次信息

else

{

file://打开文件

fread(&doinfo.filelen,sizeof(int),1,fp);

fclose(fp);

CString temp;

m_temp=fname+".down0";

if((fp=fopen(m_temp,"r"))==NULL)

return 1;

else fclose(fp);

int bb;

bb=0;

file://读各进程记录的信息

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);

}

file://建立下载结束进程timethread,以管现各进程结束时间。

DWORD dwthread;

::CreateThread(NULL,0,timethread,(LPVOID)this,0,&dwthread);

return 0;

}

file://下面介绍建立各进程函数,很简单:http://h2osky.126.com

void CMainFrame::createthread(int threadno)

{

DWORD dwthread;

file://建立BLACK个进程

for(int i=0;i<BLACK;i++)

{

m_thread[threadno][i]= ::CreateThread(NULL,0,downthread,(LPVOID)down[threadno],0,&dwthread);

}

}

file://downthread进程函数

DWORD WINAPI downthread(LPVOID lpparam)

{

cdownload* pthis=(cdownload*)lpparam;

file://进程引索+1

InterlockedIncrement(&pthis->m_index);

file://执行下载进程

pthis->threadfunc(pthis->m_index-1);

return 1;

下面介绍下载进程函数,最最核心的东西了

UINT cdownload::threadfunc(long index)

{

file://初使化联接

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;

file://读入缓存

char* m_buf=new char[SIZE];

int re,len2;

fileinfo fileinfo1;

file://联

ret=connect(m_socket,(LPSOCKADDR)&local,sizeof(local));

file://读入各进程的下载信息

fileinfo1.len=filerange[index*2+1];

fileinfo1.seek=filerange[index*2];

fileinfo1.type=2;

fileinfo1.fileno=doinfo.threadno;

re=fileinfo1.len;

file://打开文件

CFile destFile;

FILE* fp=NULL;

file://是第一次传的话

if((fp=fopen(fname,"r"))==NULL)

destFile.Open(fname, CFile::modeCreate|CFile::modeWrite | CFile::typeBinary|CFile::shareDenyNone);

else

file://如果文件存在,是续传

destFile.Open(fname,CFile::modeWrite | CFile::typeBinary|CFile::shareDenyNone);

file://文件指针移到指定位置

destFile.Seek(filerange[index*2],CFile::begin);

file://发消息给服务器,可以传文件了

sendn(m_socket,(char*)&fileinfo1,100);

CFile myfile;

file://初使化各进程进度信息http://h2osky.126.com

CString temp;

temp.Format(".down%d",index);

m_temp=fname+temp;

file://当各段长度还不为0时

while(re>0){

len2=re>SIZE?SIZE:re;

file://读各段内容

int len1=readn(m_socket,m_buf,len2);

file://有错的话

if(len1<0){

closesocket(m_socket);

break;

}

file://写入文件

destFile.Write(m_buf, len1);

file://更改记录进度信息

filerange[index*2+1]-=len1;

filerange[index*2]+=len1;

file://移动记录文件指针到头

myfile.Seek(0,CFile::begin);

file://写入记录进度

myfile.Write(&filerange[index*2],sizeof(int));

myfile.Write(&filerange[index*2+1],sizeof(int));

file://减去这次读的长度

re=re-len1;

file://加文件长度

doinfo.totle=doinfo.totle+len1;

};

file://这块下载完成,收尾

myfile.Close();

destFile.Close();

delete [] m_buf;

shutdown(m_socket,2);

if(re<=0)good[index]=TRUE;

return 1;

}

到这客户端的主要模块和机制已基本介绍完。希望好好体会一下这种多线程断点续传的方法。

我写了一个以此为基础的实用程序(网络传圣,包含源代码),可用了基于IP/TCP的电脑上,供大家学习。下载地址:

http://h2osky.126.com

赵明

email: papaya_zm@sina.com;zmpapaya@hotmail.com

web: http://h2osky.126.com

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有