多线程发送文件
原文地址: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;}}这个简单,就是打开一个文件读取到缓冲区,写入文件,再打开第二个......现在多线程传输部分就介绍完了下面讨论断断点续传的实现 
                    
                     
                    
                 
                    
                 
                
            
         
 
         浙公网安备 33010602011771号
浙公网安备 33010602011771号