c++实现文件传输之三:断点续传与多线程传输

继木马编程DIY的上两篇,现在我们开始讨论断点续传与多线程文件传输的实现.其实这两项功能是下载软件所
必不可少的功能了,现在我们把它加到自己的木马中来感受感受.提到多线程下载,首先向网络蚂蚁的作者
洪以容前辈致敬,正是由于网络蚂蚁而使得多线程下载被关注并流行起来.在这本篇文章中我们将简单的实现
支持断点续传和多线程传输的程序.为了更清晰的说明问题,我们将断点续传与多线程传输分别用两个程序来实现

多线程传输实现
实现原理
将源文件按长度为分为N块文件,然后开辟N个线程,每个线程传输一块,最后合并所有线线程文件.比如
一个文件500M我们按长度可以分5个线程传输.第一线程从0-100M,第二线程从100M-200M......最后合并5个线程文件.
实现流程
1.客户端向服务端请求文件信息(名称,长度)
2.客户端跟据文件长度开辟N个线程连接服务端
3.服务端开辟新的线程与客户端通信并传输文件
4.客户端将每线程数据保存到一个文件
5.合并所有线程文件
编码实现
大体说来就是按以上步骤进行,详细的实现和一些要点,我们跟据以上流程在编码中实现

结构定义
在通信过程中需要传递的信息包括文件名称,文件长度,文件偏移,操作指令等信息,为了方便操作我们定义如下结构

代码:
typedef struct
{
        char        Name[100];        //文件名称
        int                FileLen;        //文件长度
        int                CMD;                //操作指令
        int                seek;                //线程开始位置
        SOCKET sockid;               
}FILEINFO;
1.请求文件信息
客户端代码如下

代码:
        FILEINFO fi;
        memset((char*)&fi,0,sizeof(fi));
        fi.CMD=1;                //得到文件信息
        if(send(client,(char*)&fi,sizeof(fi),0)==SOCKET_ERROR)
        {
                cout<<"Send Get FileInfo Error/n";
        }
服务端代码如下
        while(true)
        {
                SOCKET client;
                if(client=accept(server,(sockaddr *)&clientaddr,&len))
                {
                        FILEINFO RecvFileInfo;
                        memset((char*)&RecvFileInfo,0,sizeof(RecvFileInfo));
                        if(recv(client,(char*)&RecvFileInfo,sizeof(RecvFileInfo),0)==SOCKET_ERROR)
                        {
                                cout<<"The Clinet Socket is Closed/n";
                                break;
                        }else
                        {
                                EnterCriticalSection(&CS);                //进入临界区
                                memcpy((char*)&TempFileInfo,(char*)&RecvFileInfo,sizeof(RecvFileInfo));
                                switch(TempFileInfo.CMD)
                                        {
                                        case 1:
                                                 GetInfoProc        (client);
                                                 break;
                                        case 2:
                                                 TempFileInfo.sockid=client;
                                                 CreateThread(NULL,NULL,GetFileProc,NULL,NULL,NULL);
                                                 break;
                                        }
                                LeaveCriticalSection(&CS);                //离开临界区
                        }
                }
        }
在这里服务端循环接受连接,并跟据TempFileInfo.CMD来判断客户端的请求类型,1为请求文件信息,2为下载文件
因为在下载文件的请求中,需要开辟新的线程,并传递文件偏移和文件大小等信息,所以需要对线程同步.这里使用临界区
其文件信息函数GetInfoProc代码如下

代码:
DWORD GetInfoProc(SOCKET client)
{
        CFile        file;
        if(file.Open(FileName,CFile::modeRead|CFile::typeBinary))
        {
                int FileLen=file.GetLength();
                if(send(client,(char*)&FileLen,sizeof(FileLen),0)==SOCKET_ERROR)
                {
                        cout<< "Send FileLen Error/n";
                }else
                {
                        cout<< "The Filelen is "<<FileLen<<"/n/n";
                }
        }
        return 0;
}
这里主要是向客户端传递文件长度,而客户端收到长度后则开辟线程进行连接传输文件
2.客户端跟据长度开辟线程
其实现代码如下

代码:
        FILEINFO FI;
        int FileLen=0;
        if(recv(client,(char*)&FileLen,sizeof(FileLen),0)==SOCKET_ERROR)//接受文件长度
        {
                cout<<"Recv FileLen Error/n";
        }else
        {
                cout<<"FileLen is "<<FileLen<<"/n";
                int COUNT_SIZE=FileLen/5;                        //每线程传输大小                       
                for(int i=0;i<5;i++)                                //分5个线程传输
                {
                        EnterCriticalSection(&CS);                //进入临界区
                        memset((char*)&FI,0,sizeof(FI));
                        FI.CMD=2;                                //请求下载文件
                        FI.seek=i*COUNT_SIZE;                        //线程文件偏移
                        if(i+1==5)                                //最后一线程长度为总长度减前4个线程长度
                        {
                                FI.FileLen=FileLen-COUNT_SIZE*i;
                        }else
                        {
                                FI.FileLen=COUNT_SIZE;
                        }
                        Thread=CreateThread(NULL,NULL,GetFileThread,&i,NULL,NULL);
                        Sleep(500);
                        LeaveCriticalSection(&CS);                //离开临界区
                }
        }
        WaitForMultipleObjects(5,Thread,true,INFINITE);                //等所有线程结束
这里默认开辟5个线程传输,当然可以改为想要的线程数目,仍然用临界区来实现线程的同步问题
3.服务端开辟线程传输数据
在1.请求文件信息中以说明了服务端的结构,这里主要介绍线程函数的实现,其代码如下

代码:
DWORD WINAPI GetFileProc(LPVOID lparam)
{
        EnterCriticalSection(&CS);                        //进入临界区
        int FileLen=TempFileInfo.FileLen;
        int Seek=TempFileInfo.seek;
        SOCKET client=TempFileInfo.sockid;
        LeaveCriticalSection(&CS);                        //离开临界区
        CFile        file;
        if(file.Open(FileName,CFile::modeRead|CFile::typeBinary))
        {
                file.Seek(Seek,CFile::begin);                //指针移至偏移位置
                char *date=new char[FileLen];
                int nLeft=FileLen;
                int idx=0;
                file.Read(date,FileLen);
                while(nLeft>0)
                {
                        int ret=send(client,&date[idx],nLeft,0);
                        if(ret==SOCKET_ERROR)
                        {
                                cout<<"Send Date Error /n";
                                break;
                        }
                        nLeft-=ret;
                        idx+=ret;
                }
                file.Close();
                delete[] date;
        }else
        {
                cout<<"open the file error/n";
        }
        closesocket(client);
        return 0;
}
还是比较简单的,主要是获取线程的文件长度和偏移,并移动文件指针到偏移处,最后读取发送数据,而客户端
接受数据并写入文件.

4.客户端将线程数据保存到文件
GetFileThread的实现代码如下

代码:
DWORD WINAPI GetFileThread(LPVOID lparam)
{
        char TempName[MAX_PATH];
        sprintf(TempName,"TempFile%d",*(DWORD*)lparam);        //每线程的文件名为"TempName"+线程数
        SOCKET client;
        SOCKADDR_IN serveraddr;
        int port=5555;
        client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        serveraddr.sin_family=AF_INET;
        serveraddr.sin_port=htons(port);
        serveraddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
        if(connect(client,(SOCKADDR*)&serveraddr,sizeof(serveraddr))==INVALID_SOCKET)
        {
                cout<<"Connect Server Error/n";
        }
        EnterCriticalSection(&CS);                //进入临界区
        if(send(client,(char*)&FI,sizeof(FI),0)==SOCKET_ERROR)
        {
                cout<<"Send GetFile Error/n";
                return 0;
        }
        CFile        file;
        int FileLen=FI.FileLen;                        //文件长度
        int Seek=FI.seek;                        //文件偏移
        LeaveCriticalSection(&CS);                //离开临界区
        if(file.Open(TempName,CFile::modeWrite|CFile::typeBinary|CFile::modeCreate))
        {
                char *date = new char[FileLen];
                int nLeft=FileLen;
                int idx=0;
                while(nLeft>0)
                {
                        int ret=recv(client,&date[idx],nLeft,0);
                        if(ret==SOCKET_ERROR)
                        {
                                cout<<"Recv Date Error";
                                break;
                        }
                        idx+=ret;
                        nLeft-=ret;
                }
                file.Write(date,FileLen);
                file.Close();
                delete[] date;
        }else
        {
                cout<<"Create File Error/n";
        }
        return 0;
}
在此线程函数中,将每线程传输的数据存为一个文件,文件名为"TempName"+线程数,只所以存成单独的文件是
因为比较直观且容易理解,但如果文件很大的话这个方法并不好,因为合并文件又会花费很多时间,另一个方法
是 创始一个文件,让每个线程写入文件的不同偏移,这样就可以不必单独合并文件了,但要记得打开文件时
加入CFile::shareDenyNone属性.这样整个过程就完成了.最后一步合并线程文件
5.合并线程文件

代码:
int UniteFile()                //合并线程文件
{
        cout<<"Now is Unite Fileing.../n";
        int                len;
        char        *date;
        CFile        file;
        CFile        file0;
        /*其它文件......*/
        if(file.Open(FileName,CFile::modeCreate|CFile::typeBinary|CFile::modeWrite))//创建文件
        {
                file0.Open("TempFile0",CFile::modeRead|CFile::typeBinary);//合并第一线程文件
                len=file0.GetLength();
                date=new char[len];
                file0.Read(date,len);
                file.SeekToEnd();
                file.Write(date,len);
                file1.Open("TempFile1",CFile::modeRead|CFile::typeBinary);//合并第二线程文件
                len=file1.GetLength();
                date=new char[len];
                file1.Read(date,len);
                file.SeekToEnd();
                file.Write(date,len);
                /*合并其它线程......*/
               
                file0.Close();
                file1.Close();
                /*.......*/
                delete[] date;
               
                return true;
        }else
        {
                return false;
        }
}
这个简单,就是打开一个文件读取到缓冲区,写入文件,再打开第二个......现在多线程传输部分就介绍完了
下面讨论断断点续传的实现!
  • 1
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 基于TCP/IP的多线程文件传输是一种通过网络进行文件传输的方法。它使用TCP协议作为传输层协议,IP协议作为网络层协议,以确保文件在网络上的可靠传输。 在这种传输方式中,多线程被用于加速文件传输的速度。当需要传输一个文件时,服务器启动一个主线程和多个子线程。主线程负责监听客户端的连接请求,并建立TCP连接。每个子线程负责处理一个客户端的请求,即传输文件的任务。主线程通过接收客户端的请求,将任务分配给子线程来实现文件同时传输。 这种基于TCP/IP的多线程文件传输方式具有以下优点: 1. 提高传输速度:多线程可以同时传输多个文件,充分利用系统资源,提高传输效率。 2. 系统稳定性高:每个传输任务都独立运行在一个线程中,可以避免单个任务的错误对其他任务的影响。即使某个任务失败,其他任务仍然可以继续运行。 3. 传输过程可靠:基于TCP协议的文件传输可以保证文件的正确传输。TCP协议提供了数据分段、确认、重传等机制,确保文件数据在网络上的可靠传输。 4. 支持大文件传输:通过分段传输的方式,可以支持传输文件,避免传输过程中出现内存不足等问题。 总而言之,基于TCP/IP的多线程文件传输是一种高效且可靠的文件传输方式。它通过多线程的并发处理能力,使文件传输更加快速和稳定,适用于大文件的网络传输需求。 ### 回答2: 基于TCP/IP多线程文件传输是一种利用TCP/IP协议和多线程技术来进行文件传输的方法。在这种方式下,传输文件被拆分成多个数据包进行传送,并且利用TCP协议的可靠性进行传输保障。 首先,服务器和客户端之间建立TCP连接。客户端向服务器发送文件传输请求,并指定要传输文件路径和名称。 其次,服务器接收到客户端的请求后,根据指定的文件路径和名称,去服务器存储的文件系统中查找对应的文件,并将文件分割成若干个固定大小的数据包。 然后,服务器将这些数据包发送给客户端。为了提高传输效率,可以利用多线程技术,同时开启多个线程来完成数据包的发送。 客户端接收到数据包后,根据数据包中的文件信息,将数据包按顺序组合成完整的文件,并保存到指定路径。 在传输过程中,由于利用了TCP协议的可靠性机制,数据的传输过程中不会出现丢包或损坏的情况。如果某个数据包在传输过程中出现错误或丢失,TCP协议会自动重新发送该数据包,以确保数据的完整性。 综上所述,基于TCP/IP多线程文件传输是一种高效可靠的文件传输方式,同时利用多线程技术可以提高传输效率。它的应用场景主要是在需要快速、可靠地进行大文件传输的环境中,如网络备份、远程文件同步等。 ### 回答3: 基于TCP/IP的多线程文件传输是一种通过网络将文件从一个设备传输到另一个设备的方法。TCP/IP是一组用于在网络上传输数据的协议,而多线程文件传输是指同时使用多个线程来传输文件,以提高传输速度和效率。 在多线程文件传输中,发送方将文件分成小块,并使用TCP/IP协议将这些块传输到接收方。发送方和接收方之间建立了一个TCP连接,该连接可确保数据的可靠传输,并提供了错误检测和重传机制。 传输过程中,发送方的多个线程同时将文件块发送至接收方,接收方的多个线程同时接收和存储文件块。通过使用多个线程,可以充分利用网络带宽,提高文件传输速度。 多线程文件传输还可以提供一些其他功能,如断点续传。如果传输过程中断开连接,可以通过重新连接并从断点处继续传输文件,而无需从头开始。 然而,多线程文件传输也有一些限制。首先,需要足够的带宽来支持多个线程同时传输文件块。如果网络带宽有限,使用多个线程可能会导致拥堵和传输速度的下降。其次,多线程文件传输可能会增加系统负载和资源占用,特别是在大规模文件传输时。 总体而言,基于TCP/IP的多线程文件传输是一种有效的方法,可以提高文件传输的速度和效率。然而,在实际使用中,需要根据网络环境和文件大小等因素综合考虑,以选择最适合的传输方式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值