在网络上传输文件(下)

编程爱好者

实现原理:文件分块传输

由于我们在网络上传输的文件有小也有大,因此,我们需要找到一种通用的文件传输方法,使小文件或大文件都能正常传输。大文件要变小,分割它后再传输是唯一的方法。所谓的分块文件传输,就是把文件分成若干块后一块一块地传输。例如,想让每一块的大小为64KB,可以在程序中定义一个常量:

#define CHUNK_SIZE (64*1024) //每块64KB

分块传输的好处就是不用创建一个与文件长度相同的数据缓冲区,数据缓冲区的大小只要跟每一块的大小(即CHUNK_SIZE)一样就可以,这就大大提高了系统的性能。程序先读取文件的第一块到数据缓冲区,接着发送数据缓冲区里的数据,然后再读取文件的第二块到数据缓冲区,再发送,依此类推,直到文件发送完毕为止。

实战:解读例程

以下是文件分块发送的发送方程序代码:

CFile file;

int nChunkCount=0;

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

{int FileLength=file.GetLength(); //得到文件长度

//取得总块数

nChunkCount=FileLength/CHUNK_SIZE;

if(FileLength%CHUNK_SIZE!=0)

nChunkCount++;

//取得文件长度

send(g_ClientSocket,(char*)&FileLength,sizeof(FileLength),0);

//发送文件长度

char filename[64];

strcpy(filename,file.GetFileName().GetBuffer());

send(g_ClientSocket,filename,64,0);

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

//分块发送

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

{int nLeft;

if(i+1==nChunkCount)

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

else

nLeft=CHUNK_SIZE;

int idx=0;

//读取文件数据到数据缓冲区

file.Read(data,CHUNK_SIZE);

while(nLeft > 0)

{int ret=send(g_ClientSocket,&data[idx],nLeft,0);

if(ret==SOCKET_ERROR)

//出错了

nLeft-=ret;

idx+=ret;

}

}

file.Close();//关闭文件

delete[] data; //释放数据缓冲区 }

程序分析:先把要传输的文件打开,得到文件的长度,再根据每一块的长度求得文件一共被分成多少块。当然,文件的最后一块的长度通常是不等于预定义好的块的长度的,这种情况就发生在“FileLength % CHUNK_SIZE!=0”的时候。这时nChunkCount自加1就是加上最后一块。

然后发送文件的总长度和文件名,发送成功后创建一个与“块”相同大小的数据缓冲区。下面的for循环就是把文件的第i块(每循环一次i加1)读入数据缓冲区后发送出去,直到文件发送完后才退出循环。如图所示为程序运行时的界面。

23-g13-1-1.jpg

关键:断点续传

在传输文件的时候,有时会因意外停止传输,在下次传输同一个文件的时候,如果又要重头开始传,就未免太浪费时间了。这时我们就应该为程序加上断点续传的功能。所谓的断点续传,就是文件在传输中途(这时文件当前的位置称之为“断点”)停止后,在下次再传时,可以从上次的“继点”处开始传。

我们用一个简单的方法来实现断点续传的功能。在传输文件的时候为文件创建一个临时文件(我把临时文件的文件名设为:文件名+后缀名+“.temp”)。在传输文件时,每传完一块后便在临时文件里写入当前已传完的文件块的值。

在每一次发送(或接收)文件时,先检测有没有将要发送(或接收)的文件的临时文件存在,如果有,把临时文件的文件块的值(也就是断点)读取出来,然后把文件指针移到上一次的断点处,再继续发送(或接收)文件。这样便可以做到断点续传。以下是加入断点续传后的接收方的代码:

int FileLength;

char filename[64];

int nChunkCount; //文件总块数

int nCurrentPos = 0; //断点

UINT OpenFlags;

recv(g_ClientSocket,(char*)&FileLength,sizeof(FileLength),0);

recv(g_ClientSocket,filename,64,0);

nChunkCount=FileLength/CHUNK_SIZE;

if(FileLength%CHUNK_SIZE!=0)

nChunkCount++;

//如果有临时文件,则读取断点

CFile tempfile;

CString tempfilename=filename;

tempfilename += ".temp";

if(tempfile.Open(tempfilename.GetBuffer(),CFile::modeRead|CFile::typeBinary))

{tempfile.Read((char*)&nCurrentPos,sizeof(nCurrentPos));

tempfile.Close();

OpenFlags=CFile::modeWrite|CFile::typeBinary;}

else

OpenFlags=CFile::modeCreate|CFile::modeWrite|CFile::typeBinary;

CFile file;

if(file.Open(filename,OpenFlags))

{char *data=new char[CHUNK_SIZE];

file.Seek(nCurrentPos*CHUNK_SIZE,CFile::begin);

for(int i=nCurrentPos;i < nChunkCount;i++)

{int nLeft;

if(i+1==nChunkCount)

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

else

nLeft=CHUNK_SIZE;

int idx=0;

while(nLeft > 0)

{int ret=recv(g_ClientSocket,&data[idx],nLeft,0);

if(ret==SOCKET_ERROR)

//出错了

idx+=ret;

nLeft-=ret;}

file.Write(data,CHUNK_SIZE);

//把断点写入临时文件

CFile tempfile;

CString tempfilename=filename;

tempfilename+=".temp";

int seekpos=i+1;

if(tempfile.Open(tempfilename.GetBuffer(),CFile::modeCreate|CFile::modeWrite|CFile::typeBinary))

{tempfile.Write((char*)&seekpos,size of(seekpos));

tempfile.Close();}

}

file.Close();

delete[] data;

}

程序在接收到文件长度和文件名后,根据文件名得到相应的临时文件名,然后打开临时文件。如果临时文件存在,从临时文件里读取“断点”;如果不存在,则“断点”为0(即文件开头)。打开(或创建)将要接收的文件后,把文件指针移到“断点”处,开始接收数据。在每接收完一块数据后,把文件当前的“断点”写入到临时文件。

本期程序下载地址:http://www.cpcw.com/xz/23biancheng.rar