多线程传输实现例子

本文介绍了一种基于多线程的文件传输方案,通过将文件分割成多个小块,并为每一小块分配独立的线程进行传输,最后再将这些小块合并成完整的文件。这种方法能有效提高大文件的传输效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

from: http://topic.youkuaiyun.com/u/20090311/09/b455c7e3-edbb-441d-abee-752054b41f04.html

 

实现原理

将源文件按长度为分为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

;
}

}

这个简单,就是打开一个文件读取到缓冲区,写入文件,再打开第二个......现在多线程传输部分就介绍完了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值