在网络上传输文件(上)
编程爱好者
了解:原理与方法
本篇实现文件传输的程序有一个发送方和一个接收方。
接收方要接收一个文件的数据,然后在本地写一个文件。接收方怎样才知道文件已经接收完成了呢?
方法一:自定义一个结尾标志符,发送方在文件数据的结尾加上这个结尾标志符,接收方接收到这个结尾标志符就可以知道文件是否结束了。但这个算法的弊端是:接收方每一次接收到数据都要检查刚收到的数据里包不包含有结尾标志符(匹配字符串是需要时间的)。另一方面,这个结尾标志符取什么好呢?如果文件的数据中正好有一段与结尾标志符相匹配,那么文件将不能完整地接收。
方法二:发送方先发送文件的总长度,再发送文件名,如果以上两个数据都发送成功后,建立一个大小与文件长度相同的数据缓冲区,把文件数据读入数据缓冲区,最后把数据缓冲区的数据发送出去。
相应地,接收方先接收文件的总长度,再接收文件名,如果以上两个数据都接收成功后,建立一个大小与文件长度相同的数据缓冲区,用数据缓冲区接收文件数据。接收完成后把数据缓冲区的数据写入文件。
这种方法就分三步发送和接收(当然,第一次发送可以把文件长度和文件名都发送出去,而不用分两次),因为预先知道了文件的长度,所以接收方就不用结尾标志符都能判断文件是否接收完成了。具体步骤如图1所示。

本篇的文件传输程序采用的是第二种方法。
实战:解读例程
实现第二种方法的程序编写起来比较简单,代码也较短。我们先来看看发送方和接收方的核心代码,笔者在后面为大家做详细分析。该程序下载网址:
http://www.cpcw.com/cx/22biancheng.rar
1.发送方
CFile file;
//打开文件
if( file.Open(g_chFilePath, CFile::modeRead | CFile::typeBinary))
{//取得文件长度
int FileLength = file.GetLength();
//发送文件长度
send(g_ClientSocket,(char*)&FileLength, sizeof(FileLength), 0);
//发送文件名
char filename[64];
strcpy(filename,file.GetFileName().GetBuffer());
send(g_ClientSocket,filename,64,0);
int nLeft = FileLength;
int idx = 0;
char *data = new char[FileLength];//创建数据缓冲区
//读取文件数据到数据缓冲区
file.Read(data, FileLength);
while(nLeft > 0)
{int ret = send(g_ClientSocket,&data[idx],nLeft,0);
if(ret == SOCKET_ERROR)
{//出错了}
nLeft -= ret;
idx += ret; }
file.Close(); //关闭文件
delete[] data; //释放数据缓冲区 }
程序分析:先打开一个文件,如果文件打开成功,取得文件的总长度,把文件的总长度发送给接收方,再发送文件名到接收方。然后创建一个大小与文件长度相同的数据缓冲区,把文件数据读入数据缓冲区,用一个while循环发送数据缓冲区的数据。每一次发送数据send函数返回实际发送的字节数,如果返回的是SOCKET_ERROR表示发送错误了。最后关闭文件,释放数据缓冲区。
图2是发送方程序的运行界面。在“IP地址”里填入接收方的IP地址,在“端口”里填入端口号,按“连接”按钮连接到接收方。连接成功后,在“文件”里输入要发送的文件名和路径,然后按“发送”按钮便可以把文件发送到接收方。进度条显示了文件传输的进度。

2.接收方
int FileLength;
char filename[64];
//接收文件长度
recv(g_ClientSocket,(char*)&FileLength,sizeof(FileLength),0);
//接收文件名
recv(g_ClientSocket, filename, 64, 0);
//创建一个文件
CFile file;
if(file.Open(filename,CFile::modeCreate|CFile::modeWrite|CFile::typeBinary))
{//创建数据缓冲区
char*data = new char[FileLength];
int nLeft = FileLength;
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, FileLength);
file.Close();//关闭文件
delete[] data; //释放数据缓冲区}
程序分析:先接收文件长度,再接收文件名,以上两项都接收成功后,便用文件名创建一个文件。如果文件创建成功后,创建一个大小与文件长度相同的数据缓冲区,然后用这个数据缓冲区以一个while循环接收文件数据。接收函数recv每次返回实际接收到的字节数,如果返回SOCKET_ERROR表示接收错误。当文件数据全部接收完成后,把数据缓冲区的数据写入到文件。最后关闭文件,释放数据缓冲区。
图3是接收方程序运行时的界面。

在“端口号”输入接收端口后按“启动”按钮启动服务器(在这里服务器为接收方)。当发送方成功连接进来,并向接收方发送文件时,接收方就会开始接收文件,进度条显示了文件传输的进度。
在本文中我们学到了什么?
本篇的文件传输程序实现了简单的文件传输功能,而且大家能从中很快领悟到在网络上传输文件的原理和基本方法,用它传输体积较小的文件时是非常实用的。
我们还能学到什么?
如果用上面的方法,当我们遇到文件体积比较大时,如一个文件有几百MB甚至上GB,这样的文件传输程序就无能为力了。原因是,无论是发送方还是接收方,在发送文件或接收文件时,都会动态地在内存里创建一个与文件长度相同大小数据缓冲区,在内存里创建一个几百MB甚至上GB的数据缓冲区是很惊人的!所以,在传输大文件时就要把文件分成一块块来传输了。