printf("%s:[%d] restval is %d\n",__FUNCTION__,__LINE__,(int)restval);
2、主要是这货在ftp.c中决定了是否接续读取服务器上文件:
printf("%s:[%d] toread is %d\n",__FUNCTION__,__LINE__,(int)toread);
printf("%s:[%d] startpos is %d\n",__FUNCTION__,__LINE__,(int)startpos);
printf("%s:[%d] qtyread is %d\n",__FUNCTION__,__LINE__,(int)*qtyread);
printf("%s:[%d] qtywritten is %d\n",__FUNCTION__,__LINE__,(int)*qtywritten);
printf("%s:[%d] elapsed is %lf\n",__FUNCTION__,__LINE__,*elapsed);
if (flags & rb_skip_startpos)
skip = startpos;
if (opt.verbose)
{
printf("%s:[%d] \n",__FUNCTION__,__LINE__);
/* If we're skipping STARTPOS bytes, pass 0 as the INITIAL
argument to progress_create because the indicator doesn't
(yet) know about "skipping" data. */
progress = progress_create (skip ? 0 : startpos, startpos + toread);
progress_interactive = progress_interactive_p (progress);
}
if (opt.limit_rate)
limit_bandwidth_reset ();
/* A timer is needed for tracking progress, for throttling, and for
tracking elapsed time. If either of these are requested, start
the timer. */
if (progress || opt.limit_rate || elapsed)
{
timer = ptimer_new ();
last_successful_read_tm = 0;
}
/* Use a smaller buffer for low requested bandwidths. For example,
with --limit-rate=2k, it doesn't make sense to slurp in 16K of
data and then sleep for 8s. With buffer size equal to the limit,
we never have to sleep for more than one second. */
if (opt.limit_rate && opt.limit_rate < dlbufsize)
dlbufsize = opt.limit_rate;
/* Read from FD while there is data to read. Normally toread==0
means that it is unknown how much data is to arrive. However, if
EXACT is set, then toread==0 means what it says: that no data
should be read. */
/*alter by kj*/
//while (!exact || (sum_read < toread))
while (sum_read < toread)
{
// printf("%s:[%d] exact is %d, sum_read is %ld , toread is %ld \n",\
// __FUNCTION__,__LINE__,exact,(long int )sum_read,(long int )toread);
int rdsize = exact ? MIN (toread - sum_read, dlbufsize) : dlbufsize;
double tmout = opt.read_timeout;
if (progress_interactive)
{
/* For interactive progress gauges, always specify a ~1s
timeout, so that the gauge can be updated regularly even
when the data arrives very slowly or stalls. */
tmout = 0.95;
if (opt.read_timeout)
{
double waittm;
waittm = (ptimer_read (timer) - last_successful_read_tm) / 1000;
if (waittm + tmout > opt.read_timeout)
{
/* Don't let total idle time exceed read timeout. */
tmout = opt.read_timeout - waittm;
if (tmout < 0)
{
/* We've already exceeded the timeout. */
ret = -1, errno = ETIMEDOUT;
break;
}
}
}
}
ret = fd_read (fd, dlbuf, rdsize, tmout);
if (progress_interactive && ret < 0 && errno == ETIMEDOUT)
{
ret = 0; /* interactive timeout, handled above */
}
else if (ret <= 0)
{
break; /* EOF or read error */
}
if (progress || opt.limit_rate)
{
ptimer_measure (timer);
if (ret > 0)
last_successful_read_tm = ptimer_read (timer);
}
if (ret > 0)
{
sum_read += ret;
/*add by kj*/
if(sum_read > toread)
{
ret -= (sum_read - toread);
}
if (!write_data (out, dlbuf, ret, &skip, &sum_written))
{
ret = -2;
goto out_;
}
}
if (opt.limit_rate)
limit_bandwidth (ret, timer);
if (progress)
{
progress_update (progress, ret, ptimer_read (timer));
}
#ifdef WINDOWS
if (toread > 0 && !opt.quiet)
ws_percenttitle (100.0 *
(startpos + sum_read) / (startpos + toread));
#endif
}
printf("%s:[%d] Ret is %d\n",__FUNCTION__,__LINE__,ret);
if (ret < -1)
ret = -1;
if(ret > 1)
ret = 0;
out_:
if (progress)
progress_finish (progress, ptimer_read (timer));
if (elapsed)
*elapsed = ptimer_read (timer);
if (timer)
ptimer_destroy (timer);
if (qtyread)
*qtyread += sum_read;
if (qtywritten)
*qtywritten += sum_written;
return ret;
}
下面看一下主要原理吧:
一,最重要的一点,断点续传需要服务器的支持,这个是必要条件。
传统的FTP SERVER是不支持断点续传的,因为它不支持REST指令,传统的FTP指令(我是指服务器端指令)并不包括REST指令。
第二,客户端要知道使用REST等一系列指令来作断点续传。
看看断点续传的详细过程(FTP SERVER):
首先客户端使用REST指令来告诉FTP SERVER它需要从文件的某个点开始传,接着用STOR或者RETR命令开始传文件,大概的命令的流程如下:
TYPE I
200 Type set to I.
PASV
227 Entering Passive Mode (204,48,18,69,98,250)
REST 187392
350 Restarting at 187392. Send STORE or RETRIEVE to initiate transfer.
RETR /pub/audio/pci/maestro-3/win2k/1056.zip
150 Opening BINARY mode data connection for /pub/audio/pci/maestro-3/win2k/1056.zip (936098 bytes).
首先使用TYPE命令告诉FTP SERVER使用BINARY模式传送文件;
然后使用PASV命令告诉FTP SERVER使用被动打开模式来传送文件;
接着使用REST 187392指令告诉FTP SERVER要从文件的187392字节开始传送;
最后使用RETR指令来传送文件。
从上面可以看出,这个FTP SERVER支持REST指令,有的FTP SERVER(特别的老的)是不支持这个指令的,这时即使FTP CLIENT支持断点续传也一点用都没有!
支持断点的FTP SERVER:Serv-U FTP,还有一系列的新出现的FTP SERVER;
不支持断点的:IIS4以前版本所带的都不行,IIS5 有,不家可以测试一下,登录进FTP SERVER,然后输入REST 1000命令,看服务器是否认识,认识就是支持断点。
现在有不少软件可实现多线程下载.如NetAnts,JetCar等,其实多线程下载的原理并不复杂,主要的两项技术就是多线程和断点下载。程序中开启多个线程,每个线程利用断点下载,分别下载文件的不同部分,下载完后合并,就可以了。多线程编程很多书已有介绍,这里不再重复,关于断点下载,MFC中提供的CInternetFile类可实现HTTP的断点下载,但无法实现FTP的断点下载。因此,我们只好从FTP协议中的命令入手,自己编写个FTP类,来实现多线程下载。本人编写的CMultiFTP类(在WIN2000+IIS5。0下测试成功)已在CSDN发表。
FTP指令的详细信息,大家可从http://info.internet.isi.edu/in-notes/rfc/处获得,这里给大家介绍下与多线程下载有关的几个指令极其格式:
USER〈USERNAME〉:登陆FTP的用户名,执行成功返回220;
PASS〈PASSWORD〉:密码,执行成功返回230;
REST〈POS〉:指定文件下载的开始位置,执行成功返回350;
SIZE〈FILENAME〉:文件大小,执行成功返回213;
PASV:建立数据连接,同时取得FTP服务器下载文件时用的端口号,执行成功返回227;
TYPE:指定下载文件的类型,参数为I是二进制文件,为A是字符文件,执行成功返回200;
RETR〈FILENAME〉:下载文件,执行成功返回125;
这些命令中,REST,RETR,SIZE三个命令最关键,在后面会给大家更详细的说明,另外执行FTP命令,FTP服务器会向客户端返回一代码,命令执行成功的代码上面已给出。向服务器发送命令,可把命令当作字符串向服务器发送,如:send(socket,”rest 100\r\n”,…)(注意:要在命令后加\r\n)。
在介绍多线程下载前,先给大家介绍下连接FTP服务器和从FTP服务器下载文件的过程。连接FTP SERVER很简单,创建一套接字,指定服务器的地址和端口号,连接到服务器,再向它发送USER和PASS命令,服务器返回230,就代表登陆成功,并且服务器和客户建立了一控制连接。
FTP服务器下载文件的过程比较复杂。首先,客户端要和服务器建立一数据连接,可用PORT或PASV命令建立数据连接,PORT命令要自己指定一端口号用于下载,PASV命令则由服务器分配一端口号,客户端可从服务器的返回信息提取端口号,返回信息的格式为:
(服务器IP,端口号),本人的程序将使用PASV命令。然后向服务器发送RETR命令下载文件,或先发送一REST命令指明从哪下载文件。之后,要建立一新的套接字,连接到数据连接指定的端口,文件数据就从这个套接字下载。下载完毕后,关闭套接字。
现在进入本篇的精华,实现多线程下载。执行完登陆操作后,先发送“REST 100”命令,测试下服务器是否支持断点下载,如返回成功代码,就可实现多线程下载;然后发送“SIZE”,取得文件的大小,根据文件大小,将文件分为几部分,记下各部分的偏移地址,并作为参数,交给各线程去下载。在下载线程中,先接受主线程传给他的参数(文件名,偏移地址,保存地址等),再发送“PASV”命令,建立数据连接,并新建一套接字连接到新的端口;然后根据文件类型,二进制文件发送“TYPE I“命令,文本文件发送”TYPE A“命令;之后发送“REST 〈文件偏移地址〉”命令,通知服务器改变将要下载的文件的开始地址;最后,执行“RETR 〈文件名〉”命令,下载文件。下载完毕后,编段代码合并文件即可。
在这里有个问题,就是主线程如何得知各下载线程已执行完毕。WINDOWS提供了几种线程互斥技术,如CriticalSection,Mutex等,关于他们的详细信息,大家可参考各种编程书籍,在这里我推荐使用CriticalSection技术。可以在程序中建立一全局计数器,在文件下载前置零,并建立一全局CriticalSection变量。在下载线程中,当文件下载完毕后,先锁定全局CriticalSection变量,之后将计数器加一,再释放全局CriticalSection变量。主线程中,可建立一定时,定期检查计数器的值,或让下载线程在下载完毕后调用主线程的某个函数。这样,主线程就可随时发现文件已下载完毕,可合并文件了。
多线程下载的程序设计就是这样,一点都不难。看来掌握某些计算机技术,特别是网络技术,最好还是从实现原理入手,掌握其最精华的部分,激发自己的灵感,编写出个优秀软件。老停留在使用别人的组件和函数库的基础上,你的水平不会有太大提高。