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

VC++环境下点对点多线程断点续传实现 点对点(Peer-to-Peer,简称P2P)网络架构是一种去中心化的网络模型,在该架构中,网络的每个节点既是客户端又是服务器,可以直接与其他节点通信,共享资源和服务。P2P网络因其高度的可扩展性、鲁棒性和负载均衡能力,在分布式计算、文件共享、区块链等多个领域得到了广泛应用。在VC++中,文件IO操作主要是通过标准C++库和Windows API来完成的。C++标准库提供了一套较为简单的文件IO接口,例如<fstream>库中的ifstream和ofstream等。 阅读详情

在如今的网络应用中,文件的传送是重要的功能之一,也是共享的基础。一些重要的协议像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

点对点多线程断点续传的实现 立即下载

相关推荐

用VC实现基于TCP_IP的点对点多线程断点续传

用VC实现基于TCP_IP的点对点多线程断点续传

WinSock2网络编程的步骤(服务器端)

1、构造函数中对套接字对象和其他控制变量初始化 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_hSocket = NULL; isServerStart=FALSE; ChatEnable=FALSE; 2、调用WSAStartup函数对WinSock DLL初始化 WSADATA wsaDat...

dola77521的博客 460

CSocket多线程发送文件,支持断点续传

CSocket多线程发送文件,支持断点续传,可以传送文件,但是不能太大。。。

点对点(P2P)多线程断点续传的实现

点对点(P2P)多线程断点续传的实现作者: 赵明 | 日期: 2004-06-21 | 已阅: 19063次P2P中国下载源代码:upload/2004_06/04062118573143.zip <!--google_ad_client = "pub-7197438303445134";google_ad_width = 300;google_ad_height = 250;

beyondpeijie的专栏 1848

TCP_IP的局域网内点对点的大文件传输

TCP_IP的局域网内点对点的大文件传输TCP_IP的局域网内点对点的大文件传输

深入学习FIFO

深入理解FIFO、同步FIFO和异步FIFO、FIFO设计的难点以及FIFO深度计算

好好学习,天天向上 2646

一种点对点文件断点续传的多线程实现方法

1 概述    文件断点续传就是在主机与主机间传输文件时,可以将文件分多次传输,与非断点续传不同的是,每次传输不必传输整个文件。断点续传是大型文件数据传输的核心。本文将以多线程技术和Socket技术为依托,介绍大型文件断点续传的实现方法。    2 基本实现思想    多线程断点续传实现的基本思想就是在发送端(也称客户端)将要传输的文件分割为大小相当的多块,用多个线程,将这些块同时向目

xutom2006的专栏 1421

论文阅读—Square Attack: a query-efficient black-box adversarial attack via random search

论文阅读—Square Attack: a query-efficient black-box adversarial attack via random search 发表位置 ECCV 2020 摘要 作者提出了一种基于得分的黑盒Square attack,该模型不依赖于模型的局部梯度信息,因此可以绕过梯度隐藏防御攻击。Square Attack是一种随机搜索方法,它在随机位置选择局部的方形更新,使得每次迭代时扰动近似位于可行集的边界。 背景 当涉及到对安全至关重要的机器学习应用时,对抗性的例子尤其值得

yayayamaomaoya的博客 2493

[VC++]点对点(P2P)多线程断点续传的实现(TCP/IP)

 在如今的网络应用中,文件的传送是重要的功能之一,也是共享的基础。一些重要的协议像HTTP,FTP等都支持文件的传送。尤其是FTP,它的全称就是“文件传送协议”,当 初的工程师设计这一协议就是为了解决网络间的文件传送问题,而且以其稳定,高速,简单而一直保持着很大的生命力。作为一个程序员,使用这些现有的协议传送文件相当简单,不过,它们只适用于服务器模式中。这样,当我们想在点与点之间传送文件就

Daniel 2189

李沐之数值稳定性+模型初始化和激活函数

层记为t,这里的y不是预测,还包括了损失函数,y是优化函数,不是预测的结果y。第t层是输出。所有的h都是一些向量,向量关于向量的导数是一个矩阵,所以用了很多次的矩阵乘法,出问题就在这里。

m0_51133942的博客 1527

Apache Paimon-实时数据湖

Flink社区希望能够将 Flink 的 Streaming 实时计算能力和 Lakehouse 新架构优势进一步结合,推出新一代的 Streaming Lakehouse 技术,促进数据在数据湖上真正实时流动起来,并为用户提供实时离线一体化的开发体验。Flink 社区内部孵化了 Flink Table Store (简称 FTS )子项目,一个真正面向 Streaming 以及 Realtime的数据湖存储项目。

m0_57307642的博客 2192
上一篇: JAVA编程技巧之如何实现HTTP的断点续传
下一篇: JavaScript版俄罗斯方块Easy Tetris实现原理
pstrunner
博客等级 码龄19年 50粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值