在网络上传输文件(下)
编程爱好者
实现原理:文件分块传输
由于我们在网络上传输的文件有小也有大,因此,我们需要找到一种通用的文件传输方法,使小文件或大文件都能正常传输。大文件要变小,分割它后再传输是唯一的方法。所谓的分块文件传输,就是把文件分成若干块后一块一块地传输。例如,想让每一块的大小为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)读入数据缓冲区后发送出去,直到文件发送完后才退出循环。如图所示为程序运行时的界面。

关键:断点续传
在传输文件的时候,有时会因意外停止传输,在下次传输同一个文件的时候,如果又要重头开始传,就未免太浪费时间了。这时我们就应该为程序加上断点续传的功能。所谓的断点续传,就是文件在传输中途(这时文件当前的位置称之为“断点”)停止后,在下次再传时,可以从上次的“继点”处开始传。
我们用一个简单的方法来实现断点续传的功能。在传输文件的时候为文件创建一个临时文件(我把临时文件的文件名设为:文件名+后缀名+“.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