BCB编程中实现断点续传
编程爱好者
NMFTP是Delphi、Borland C++ Builder(以下简称BCB)自带的FastNet控件组中的一个组件,主要用于实现FTP上传、下载等功能。它在开发一些小型、简单的网络应用软件时非常有用。笔者最近在使用BCB6开发一套FTP客户端软件时需要实现断点上(续)传(以下用"断点续传"特指断点上(续)传),最终通过NMFTP得以实现,在此将问题解决过程写出来,与广大程序员共飨。
一、系统函数假续传
用过NMFTP的朋友都知道,NMFTP有三个上传函数可以满足绝大部分的上传操作。它们分别是:Upload()、UploadAppend()、UploadRestore()、UploadUnique(),这四个函数的原型为:
procedure Upload(LocalFile, Remote
File: string);
procedure UploadAppend(LocalFile, RemoteFile: string);
procedure UploadRestore(LocalFile, RemoteFile: string; Position: Integer);
procedure UploadUnique(LocalFile: string);
笔者的软件要求在FTP服务器不授予"删除"权限的条件下完成文件传输,且上传的文件要与本地文件同名。因此断点续传是解决问题的唯一途径。通过帮助文档可以知道,UploadRestore()、UploadAppend()两个函数都能满足要求,两者唯一不同之处在于前者需要指定断点位置,而后者自动将文件接续在已上传的文件后(服务器上文件名需要与"RemoteFile"参数指定的文件名相同)。
实际使用过程中,发现两者确实都可以完成续传的目的,但是直接使用这两个函数完成续传以后的文件无法使用,而文件大小是续传前的大小与原文件大小之和,也就是说,这两个函数完成的"续传"是把文件从前次断点处重新从头传了一遍,而并没有真正的从断点处"续传"。
二、通过指令细分析
通过Serv-U的"监视用户"功能,我们可以看到服务器与客户端FTP指令。
当使用UploadRestore()时,客户端发送指令:"REST ****"(*号为函数中指定的Position参数值),服务器响应为:"350 Restarting at ****. Send STORE or RE
TRIEVE "然后函数开始上传数据。
当使用UploadAppend()时,客户端发送指令:"APPE ****"(*号为函数中指定的RemoteFile参数值),服务器响应为:"150 Opening BINARY mode data connection for ****."。
如果我们使用CuteFTP进行续传的话,我们可以看到客户端发送的指令首先为"REST ****"(*号为断点所在位置),待服务器响应后,客户端发送的指令变为"APPE ****"(*号为服务器上的文件名)。
通过分析以上的指令,我们可以看到,CuteFTP完成续传主要还是调用Append的方法,"REST"指令仅是获得断点位置,但是由于其内部机制控制了续传从断点处开始而不是从文件起始处开始,而UploadRestore()和UploadAppend()并不能控制续传仅从断点处开始,从而导致续传后的文件无法使用。
三、彻底解决问题
通过前面的分析,我们知道了,NMFTP无法实现真正的续传关键在于无法从断点处传送本地文件。既然如此,我们只要在续传命令前从断点处截取源文件作为临时文件,然后续传这个临时文件即可解决续传问题。事实证明这个方法是有效的。
下面以在BCB6中调用UploadAppend()函数完成续传为例给出源代码,读者也可以使用UploadRestore()函数完成。程序用到的控件见表1,全局变量见表2。

void __fastcall TForm1::btn_Upload
Click(TObject *Sender)
{ int handle=FileOpen(edt1->Text,0);
int size=FileSeek(handle,0,2);//获取源文件大小,以便续传前对比
nftp1->List();//通过列表,判定服务器上是否已经有了同名文件及其大小,以便决定是否续传
if (!edt1->Text.IsEmpty())
{
if (filesize!=size)//只有当文件大小不同的时候才能上传或续传
{
if (filesize!=-1)
{
ShowMessage("上次已经传送:"+Int
ToStr(filesize)+"字节");
//====建立续传临时文件=====//
String tempfile=edt1->Text+".tmp";
int iToFileHandle=FileCreate(tempfile);//打开文件
FileSeek(handle,filesize,0);//将文件指针定位在断点处
char *pszBuffer=new char[2049];//缓冲区
int iBytesRead,iBytesWritten;//读写缓冲变量
do
{ iBytesRead=FileRead(handle,pszBuffer,2048);
iBytesWritten=FileWrite(iToFileHandle,pszBuffer,iBytesRead);
}while(iBytesRead==2048);
delete[] pszBuffer;
FileClose(handle);
FileClose(iToFileHandle);
//========进行续传=======//
try
{nftp1->UploadAppend(tempfile,Extract
FileName(edt1->Text)); }
catch (...) //使用"try...catch"是为了避免在上传过程中点击"断开"后出现异常
{
ShowMessage("上传操作已被终止!");
}
DeleteFile(tempfile);//删除临时文件
}
else
{
ShowMessage("现在开始全新上传!");
FileClose(handle); //关闭文件,否则上传时会因为打不开文件而出错
try
{
nftp1->Upload(edt1->Text,ExtractFile
Name(edt1->Text));
}
catch (...)
{
ShowMessage("上传操作已被终止!");
}
}
}
else
ShowMessage("文件已经在服务器上存在,并已经传送完毕!");
nftp1->Disconnect();//传送完毕以后断开连接
i=0; //计数变量清零
filesize=-1; //变量恢复初始值
}
else
ShowMessage("请选择需要上传的文件!");
}
//------------------
void__fastcall TForm1::btn_OpenFile
Click(TObject *Sender)
{
if (Open1->Execute())
edt1->Text=Open1->FileName;
}
//------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
//====变量初始化========//
filesize=-1;
i=0;
}
//------------------
(void __fastcall TForm1::btn_ExitClick(TObject *Sender)
{
Close();
}
//------------------
(void __fastcall TForm1::nftp1Packet
Sent(TObject *Sender)
{
lbl2->Caption=nftp1->BytesSent+file
size;
}
//------------------
void __fastcall TForm1::nftp1ListItem(AnsiString Listing)
{
if (nftp1->FTPDirectoryList->name->Strings[i]==ExtractFileName(edt1->Text))
filesize=StrToInt(nftp1->FTPDirecto
ryList->Size->Strings[i]);
else
i++;
}
//------------------
void__fastcall TForm1::btn_Con
nectClick(TObject *Sender)
{
nftp1->Host=lbledt_Host->Text;
nftp1->UserID=lbledt_ID->Text;
nftp1->Password=lbledt_Pass->Text;
nftp1->Connect();
nftp1->Mode(MODE_BYTE);
}
//------------------
void __fastcall TForm1::btn_DisConnect
Click(TObject *Sender)
{
nftp1->Disconnect();
i=0; //计数变量清零
filesize=-1; //变量恢复初始值
}
//------------------
void __fastcall TForm1::nftp1Authenti
cationFailed(bool &Handled)
{
ShowMessage("用户名或密码不正确");
}
//------------------
void __fastcall TForm1::nftp1Success(TCmdType Trans_Type)
{
switch(Trans_Type)
{
case cmdUpload: ShowMessage("上传完毕"); break;
case cmdAppend: ShowMessage("续传完毕"); break;
}
}