分享
 
 
 

点对点多线程断点续传的实现

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

点对点多线程断点续传的实现

作者:赵明

下载配套源代码(网络传圣源代码)

下载地址二 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(aatype)

{

//我要读文件信息

case 0:

//读文件

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

//有错

if(aaSendMessageToDescendants(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(len10){

len2=lenSIZE?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(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::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(re0){

len2=reSIZE?SIZE:re;

//读各段内容

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

//有错的话

if(len1

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

作者信息:

姓名:赵明

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

主页: 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- 王朝網路 版權所有