嵌入式 FTP断点续传原理以及实现指定下载文件起始地址

来源:互联网 发布:深圳青年旅社知乎 编辑:程序博客网 时间:2024/06/03 23:06
1、主要这货决定了告诉ftp服务器,我想从文件的什么位置下载:
printf("%s:[%d] restval is %d\n",__FUNCTION__,__LINE__,(int)restval);


/* Decide whether or not to restart.  */
if (opt.always_rest
&& stat (locf, &st) == 0
&& S_ISREG (st.st_mode))  
/* When -c is used, continue from on-disk size.  (Can't use
  hstat.len even if count>1 because we don't want a failed
  first attempt to clobber existing data.)  */
restval = st.st_size;
      else if (count > 1)
restval = len;/* start where the previous run left off */
      else
restval = 0;

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命令,看服务器是否认识,认识就是支持断点。

其实FTP断点续传的原理很简单,可分为断点下载和断点上传。
   
一、下载:
1、向服务器发送“REST + 本地文件长度”命令,告诉服务器,客户端要断点下载了。这时服务器还不知道客户端要下载哪个文件;
2、向服务器发送“RETR + 文件名”命令,通知服务器要下载的文件名,这时服务器开始定位文件指针读文件并发送数据。
3、客户端定位本地文件指针(文件末尾);
4、两端的准备工作都做完了以后,客户端创建socket,以被动或非被动方式建立数据通道,循环调用recv接收数据并追加入本地文件;
 
二、上传:
1、获取服务器上和本地要上传文件的同名文件大小;
2、向服务器发送“APPE +文件名”,通知服务器,接下来从数据通道发送给你的数据要附加到这个文件末尾。
3、定位本地文件指针(和FTP上文件大小相同的位置)
4、从文件指针处读数据并发送。
 
这里重点说说PASV模式,即被动模式,这是FTP命令里比较不容易理解的一个,这条命令请求服务器在某个端口(非FTP默认端口或控制命令端口)创建一个监听socket,服务器创建的端口号会在客户端的控制命令通道上得到响应。得到这个端口号后,客户端就可以创建新的socket(数据通道)connect过去,并进行文件传输等工作。否则,如果为非被动模式,那么监听的socket由客户端创建,服务器connect过来。
对于这条命令的存在我是这么理解的,存在这么一种情况:客户端的IP是个内网的IP,服务器的IP是个外网的,当进行数据传输时内网的IP对于服务器是不可见的,只有由服务器启动监听socket才能建立数据通道,所以必须以被动模式进行。:)
 
 
 
===========以下转载==========

现在有不少软件可实现多线程下载.如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变量。主线程中,可建立一定时,定期检查计数器的值,或让下载线程在下载完毕后调用主线程的某个函数。这样,主线程就可随时发现文件已下载完毕,可合并文件了。
多线程下载的程序设计就是这样,一点都不难。看来掌握某些计算机技术,特别是网络技术,最好还是从实现原理入手,掌握其最精华的部分,激发自己的灵感,编写出个优秀软件。老停留在使用别人的组件和函数库的基础上,你的水平不会有太大提高。


0 0
原创粉丝点击