• 多线程发送文件


    原文地址:http://bbs.csdn.net/topics/300116094

    多线程传输实现
     
    实现原理
     
    将源文件按长度为分为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[i]=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;
    }
     
    }
     
    这个简单,就是打开一个文件读取到缓冲区,写入文件,再打开第二个......现在多线程传输部分就介绍完了
    下面讨论断断点续传的实现
  • 相关阅读:
    The network bridge on device VMnet0 is not running
    QuickContactBadge去掉三角
    在Android Studio中调用so中的方法
    Android Studio动态调试smali代码
    用AndroidStudio创建so
    Android逆向 破解第一个Android程序
    Java配置----JDK开发环境搭建及环境变量配置
    AndroidKiller报.smali文件丢失问题解决(关闭Android Studio的Instant Run)
    Android逆向 Android平台虚拟机
    Android逆向 APK文件组成
  • 原文地址:https://www.cnblogs.com/dragon2012/p/2959473.html
Copyright © 2020-2023  润新知