点对点(P2P)多线程断点续传的实现
更新时间:2023-03-08 05:57:49 阅读量: 综合文库 文档下载
- 多线程p2p下载推荐度:
- 相关推荐
点对点(P2P)多线程断点续传的实现
在如今的网络应用中,文件的传送是重要的功能之一,也是共享的基础。一些重要的协议像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=\错误\\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.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\\n\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(\ 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();//发命令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(\
//给主函数发消息 CString aaa;
aaa=\正在读取 \信息,马上开始下载。。。\\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+\
doinfo.name=m_temp; FILE* fp=NULL; CFile myfile;
//如果是第一次下载文件,初使化各记录文件
if((fp=fopen(m_temp,\ 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::typeBina ry); //写入文件长度 myfile.Write(&doinfo.filelen,sizeof(int)); myfile.Close(); CString temp; for(int ii=0;ii //初使化各进程记录文件信息(以.downN结尾) temp.Format(\ m_temp=fname+temp; myfile.Open(m_temp,CFile::modeCreate|CFile::modeWrite | CFile::typeBina ry); //写入各进程文件信息 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+\ if((fp=fopen(m_temp,\ return 1; else fclose(fp); int bb; bb=0; //读各进程记录的信息 for(int ii=0;ii temp.Format(\ 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,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;i m_thread[threadno][i]= ::CreateThread(NULL,0,downthread,(LPVOID)down[t hreadno],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,\ 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(\ 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; } 到这客户端的主要模块和机制已基本介绍完。希望好好体会一下这种多线程断点续传的方法。
正在阅读:
点对点(P2P)多线程断点续传的实现03-08
社会学概论整理(同名26712)05-02
初三思想品德复习资料04-29
2018-2023年中国生鲜电商行业市场发展分析与投资前景预测研究报告-行业发展趋势分析 - 图文12-26
绿野仙踪 阅读测试题(及答案)09-03
500Kv线路架线施工总方案05-09
自身免疫性溶血性贫血(专科班)09-02
《太阳》教学案例评析_彭慧琴05-19
- 小学生造句大全
- 增压泵投资项目可行性研究报告(模板)
- 高中语文人教版粤教版必修1-5全部文言文知识点归纳
- 两学一做专题民主生活会组织生活会批评与自我批评环节个人发言提
- 管理处环境保洁工作操作标准作业指导书
- 2012六一儿童节活动议程 - 图文
- 移树申请报告
- 《贵州省市政工程计价定额》2016定额说明及计算规则
- 计算机长期没有向WSUS报告状态
- 汉语拼音教学策略研究
- 发展西部领先的航空货运枢纽
- 司法所上半年工作总结4篇
- 如何提高银行服务水平
- 发电厂各级人员岗位职责
- 丰田汽车的外部环境分析
- 2017—2018年最新冀教版四年级数学下册《混合运算》教案精品优质
- 中建八局样板策划 - 图文
- 戚安邦《项目管理学》电子书
- 2015年高级项目经理笔记
- 弯桥的设计要点
- 断点
- 线程
- 实现
- P2P
- 17春福师《现代教育技术》在线作业一
- c++程序设计第二版完整答案
- 中国播音学基础 考试大纲
- 在全市高中教育教学工作会上的讲话
- 财务管理复习题
- 华源制药合并报表的控制权之争案例分析
- 新部编版初中历史八年级下册第19课社会生活的变迁公开课优质课导
- 2013年广西自治区百色市中考数学试卷含答案
- C语言期末笔试2009年1月A
- 武汉二中广雅中学2017-2018学年度下学期九年级英语测试(一)
- 人力资源管理手册及表格实用
- 人力资源管理习题练习(新)
- iphone4跳过激活教程 - 图文
- “互联网+”对现代会计发展的影响分析
- 2018年人教版初中英语七年级下册Unit11单元测试卷含答案
- MD25-50多级矿用离心泵性能参数表-长沙奔腾泵业 - 图文
- 试论高中语文古诗文教学中的传统文化教育
- c++飞机大战实训报告 附带码
- 农机局2011年创先争优宣传工作总结
- 生 物 实 验 设 计 练 习 题