rt,如何能够确认文件被客户端成功下载?

解决方案 »

  1.   

    一般情况下当输出完头以后就直接将整个文件输出出去
    通常用的是 ceho、print 有的干脆用 readfile
    这样 php 就没有机会去判断用户是否终止了下载你需要在循环中用较小的块,通过 sprintf 输出文件内容
    springf 函数会返回一个表示输出了多少字符的数。你可以累计这个数,并与文件长度进行比较
    从而判断下载是否完成
      

  2.   

    基于http是没办法做到的。比如服务器返回了2000个字节让你下载保存,这时候你在你在浏览器点了取消按钮,或者下载到一半取消,再或者下载完成,这些个动作只在客户端浏览器执行,并不会将状态通知服务器。
      

  3.   

    IE下可使用ActiveX,FireFox下可以使用NPAPI
      

  4.   

    强,这个不错。最近我们公司也在做一个OA项目,也要判断文件下载完成的事件,正需要一个这样的HTTP文件下载控件,能否把地址发一下。谢谢。
      

  5.   

    这个在网上可以直接搜到, http://www.cnblogs.com/xproer/archive/2011/03/26/1996322.html
      

  6.   

    这个好像是Xproer.HttpDownloader。可以在网上直接搜到。
      

  7.   

    如果是通过PHP页面下载的话可以在PHP页面中直接判断。如果想在客户端判断的话,需要借助于插件来实现。
      

  8.   

    哦?正好,贴出来我见识下,怎么保证我在浏览器端点了取消下载,服务器php能知道用户并未保存文件。
    上面说的控件那些不是我想要的,chrome浏览器很可能是未来主流。
      

  9.   

    send_file 函数是在网上找的,写的并不好,但他确能保证数据不丢失
    在此基础上做了些改动,代码中有注释send_file('03.bmp',1);//为了测试方便我取了较小的块尺寸function send_file($file, $speed = 100) { 
          
        //First, see if the file exists    
        if (!is_file($file)) { 
             die("<b>404 File not found!</b>"); 
         }    
        //Gather relevent info about file 
        $filename = basename($file); 
        $file_extension = strtolower(substr(strrchr($filename,"."),1)); 
        // This will set the Content-Type to the appropriate setting for the file 
        switch( $file_extension ) { 
             case "exe": 
                $ctype="application/octet-stream"; 
                 break; 
             case "zip": 
                $ctype="application/zip"; 
                 break; 
             case "mp3": 
                $ctype="audio/mpeg"; 
                 break; 
             case "mpg": 
                $ctype="video/mpeg"; 
                 break; 
             case "avi": 
                $ctype="video/x-msvideo"; 
                 break; 
             
            //   The following are for extensions that shouldn't be downloaded 
             // (sensitive stuff, like php files) 
            case "php": 
             case "htm": 
             case "html": 
             case "txt": 
                 die("<b>Cannot be used for ". $file_extension ." files!</b>"); 
                 break; 
             default: 
                $ctype="application/force-download"; 
         }     //   Begin writing headers 
        header("Cache-Control:"); 
        header("Cache-Control: public"); 
        header("Content-Type: $ctype");     $filespaces = str_replace("_", " ", $filename); 
        // if your filename contains underscores, replace them with spaces     $header='Content-Disposition: attachment; filename='.$filespaces; 
        header($header); 
        header("Accept-Ranges: bytes"); 
         
        $size = filesize($file);    
        //   check if http_range is sent by browser (or download manager)    
        if(isset($_SERVER['HTTP_RANGE'])) { 
            // if yes, download missing part               $seek_range = substr($_SERVER['HTTP_RANGE'] , 6); 
            $range = explode( '-', $seek_range); 
             if($range[0] > 0) { $seek_start = intval($range[0]); } 
             if($range[1] > 0) { $seek_end  =  intval($range[1]); } 
                 
            header("HTTP/1.1 206 Partial Content"); 
            header("Content-Length: " . ($seek_end - $seek_start + 1)); 
            header("Content-Range: bytes $seek_start-$seek_end/$size"); 
         } else { 
            header("Content-Range: bytes 0-$seek_end/$size"); 
            header("Content-Length: $size"); 
         }    
        //open the file 
        $fp = fopen("$file","rb"); 
         
        //seek to start of missing part    
        fseek($fp,$seek_start); /*** 到此为止都是普通的写法 ***/    //start buffered download 
        $n = 0; //输出长度计数器复位
        //reset time limit for big files 
        set_time_limit(0);        
        while(!feof($fp)) {        
            $n += printf('%s', fread($fp,1024*$speed)); //输出内容并累计输出长度
            file_put_contents('file_down_info.txt', sprintf('size:%d len:%d', $size, $n));//因为是测试所以每步都保存了信息。实用时只需在循环后处理
            flush(); 
            sleep(1); 
         } 
        fclose($fp); 
         exit; 

      

  10.   

    原来是Accept-Ranges头 + 206 + 刷新缓冲,原理上是可以,$n<$size表示下载了部分,不过这个sleep(1)很要命啊,会严重增大下载所需时间,我本地用你代码测试下载1M居然需要9分钟,去掉sleep(1)是瞬间。其实我本地nginx下那个flush()是没有用的,或者说apche下不能设置zip压缩。    @apache_setenv('no-gzip', 1);
        @ini_set('zlib.output_compression', 0);
        @ini_set('implicit_flush', 1);
    你再看看还能不能改进。
      

  11.   

    不过sleep(1)肯定会影响下载速度,而且肯定是文件越大,影响越大。
      

  12.   

    哈哈,不是$n不对,是我下载瞬间完成,但是php还在运行。。所以不断刷先记录那个日志文件。。
    主要还是这个sleep(1)。。
      

  13.   

    to xuzuning foolbirdflyfirst这样子可行吗?下载功能代码来源,手册,我改动了部分,
    ignore_user_abort(true);
    downloadFile("download.xls");//测试文件在此function downloadFile( $fullPath ){   // Must be fresh start 
      if( headers_sent() ) 
        die('Headers Sent');   // Required for some browsers 
      if(ini_get('zlib.output_compression')) 
        ini_set('zlib.output_compression', 'Off');   // File Exists? 
      if( file_exists($fullPath) ){ 
        
        // Parse Info / Get Extension 
        $fsize = filesize($fullPath); 
        $path_parts = pathinfo($fullPath); 
        $ext = strtolower($path_parts["extension"]); 
        
        // Determine Content Type 
        switch ($ext) { 
          case "pdf": $ctype="application/pdf"; break; 
          case "exe": $ctype="application/octet-stream"; break; 
          case "zip": $ctype="application/zip"; break; 
          case "doc": $ctype="application/msword"; break; 
          case "xls": $ctype="application/vnd.ms-excel"; break; 
          case "ppt": $ctype="application/vnd.ms-powerpoint"; break; 
          case "gif": $ctype="image/gif"; break; 
          case "png": $ctype="image/png"; break; 
          case "jpeg": 
          case "jpg": $ctype="image/jpg"; break; 
          default: $ctype="application/force-download"; 
        }     header("Pragma: public"); // required 
        header("Expires: 0"); 
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 
        header("Cache-Control: private",false); // required for certain browsers 
        header("Content-Type: $ctype"); 
        header("Content-Disposition: attachment; filename=\"".basename($fullPath)."\";" ); 
        header("Content-Transfer-Encoding: binary"); 
        header("Content-Length: ".$fsize); 
        ob_clean(); 
        flush(); 
        //readfile( $fullPath ); ////改动从这里开始 $fp = fopen($fullPath,"rb");
    while(!feof($fp))
    {
    if (connection_status()!=CONNECTION_NORMAL)
    {
    file_put_contents("download.txt", "abort\t" . date("Y-m-d H:i:s\n"), FILE_APPEND);
    fclose($fp);
    exit;
    }
            echo fread($fp, 1024);
            flush();
    sleep(1);
        } 
        fclose($fp);
    if (connection_status()==CONNECTION_NORMAL)
    {
    file_put_contents("download.txt", "complete\t" . date("Y-m-d H:i:s\n"), FILE_APPEND);
    fclose($fp);
    }
      } else 
        die('File Not Found'); } 
      

  14.   

    原理是一样的:捕获用户中断信息,做相应处理在我这里测试时又两个问题:
    1、不中断,让文件全部下载。但是下载后的文件大小是对的,内容不完整
    其实我自己写的下载函数也是这样,后来才找到那个 send_file2、与链接处理相关的函数,基本不起作用(xp + apache2.2.11 + php 5.3.6)
    只在中断下载时,download.txt 中增加一条
    abort 2012-04-14 15:05:08 
    这样的记录
      

  15.   

    哦……临时想到有个tcp状态判断,
    居然有内容不完整的情况,2的问题,我用chrome,倒没遇到,能正常输出要么abort,要么complete
      

  16.   


    呵呵,是没……方便吗,测试时候,加上set_time_limit限制下时间呢????我本地测试文本很小,不会超出默认的30秒如果稍大文件确实有你说的2点情况
      

  17.   

    80k 的 bmp 图片不算大,完全下载时只有一条有图像其余部分都是透明的我几个版本的 connection_status 的返回值都不正常
      

  18.   

    万米来了,我再啰嗦下个人意见。
    矛盾体是那个sleep,没有这个sleep,flush即时推送给浏览器根本就不能工作,不能工作意味着浏览器在等你web服务器把完整的内容推送给我,我浏览器才接收到,因为你整个while挂在那,不sleep,cpu是不得空处理别的事情的,但是如果有sleep,即时刷新缓冲也能正常运作的话,也是我们人为的增大下载的时间开销。假设内部网环境,下载速度很快,我服务端每次sleep(1)推送缓冲,记录推送字节数,客户端马上接收到,但此时就要随着服务端暂停一秒才接收下一个字节块。假设外网环境,客户下载速度超级慢,下载文件比较大,我服务器端php可能while都运行结束了(意味着记录用户下载完整),但是我客户端很可能还在龟速的接收来自服务端的返回,这时候客户端一点击取消,结果不就不同步了吗。
      

  19.   

    没错,老大你说的是TCP/IP层,但是我们现在只能基于HTTP层讨论这个问题,php作为cgi,或者apache module,能做的是设置报头,报文,至于请求客户端,然后返回数据,这个数据怎么传回去,那是web服务器考虑的问题了。现在我们断点在php这,如何能保证断点前的数据,马上就能够到达客户端浏览器呢,我们假设数据包能正确被客户端被接收,那么肯定要考虑数据传输过程中花的时间,如果需要重传,那到达客户端时间就更长,这个时间如果一长,那么客户端势必就接收得慢。
    这么说把,咱php读1G的数据的时间,是否就肯定等同于客户下载1G数据的时间?
      

  20.   

    可以考虑FLASH,或者控件,或者直接写个客户端......
      

  21.   

    可以考虑FLASH,或者控件,或者直接写个客户端......
      

  22.   

    我知道你担心的是什么,不过是没多大必要的
    如果说 HTTP 是车厢,那么 TCP/IP 就是轮子。轮子不转了,车厢还能动吗?那么坐车的人呢?只有等了,人多的话就要排队了前面我已经说过,加入sleep(1)是为了测试方便:让你有机会去中断下载
    现在把 sleep(1) 去掉,换一种测试方法:$fp = fsockopen("localhost","80",$errer,$errno,1);
    $out = array(
      'GET /file_down.php HTTP/1.0',
      'Host: localhost',
      'Connection: Close',
      '', ''
    );
    fwrite($fp, join("\r\n", $out) );
    $n = 0; //计数器
    $size = 1024; //每次读取的字节数
    while (!feof($fp)){
      $s = fread($fp, $size);
      if(! $n) {
        preg_match('/Content-Length:\s+(\d+)/', $s, $r);
        $cnt = $r[1]/$size;
      }
      $n++;
      if($n >= 0.25 * $cnt) break;//中途跳出,相当于中断下载
    }
    fclose($fp);
    echo "$n ";
    sleep(1);//停一下,等待 file_down_info.txt 交付
    readfile('file_down_info.txt');
    于是在某次测试中得到这样的结果
    23 size:91510 len:32768
    客户端收到 23*1024 后中断,服务端发现客户端中断后停止发送,实际发送 32768
    两者的差就是你最担心的部分
      

  23.   

    呵呵,车轮车厢的比喻得不错,但是问题是php在这里是什么角色呢?php是一群把货物丢到车厢上的农民工。每个农民工把自己货物扔车厢上了,就记录此货物"发送成功"是不对的,"货物"发送成功或者快慢与否要看网络状况和客户的下载速度。
    这个问题是没必要在localhost测试的,因为本地数据包传输速度那是不受任何限制的,每个农民工把个货物丢上车厢,浏览器马上就接收到了。
    我个人也赞同此功能只能在客户端完成,flash是没问题的,首先接收http返回头里的content-length,然后这个字节数和已保存成文件的字节数一作对比即可。
      

  24.   

    而且老大你这个客户端代码的思路正符合我说的和上面很多坛友说的客户端判定。唯一区别是不去读服务端记录的file_down_info.txt文件,而是自己在客户端统计。
      

  25.   

    本帖最后由 xuzuning 于 2012-04-15 14:31:13 编辑
      

  26.   

    没错啊,我也认为目前基于裸浏览器是做不到这个功能的,
    要借用控件,若想兼容各个浏览器,我觉的目前flash算是个方案,页面嵌入个flash,通过flash去请求那个下载链接,flash模拟文件下载那个进度条,然后如果按取消按钮或者下载完成后都会触发一个事件通知服务器。
    未来HTML5有WebSocket,应该可以实现这个功能。
      

  27.   

    本帖最后由 xuzuning 于 2012-04-15 14:43:22 编辑
      

  28.   

    .
    com组件的东东吧,只是不明白这样做用在什么地方?
      

  29.   

    下载不完整是由2个原因导致的。一是由用户点击 STOP 按钮,远程客户端中断连接。二是当连接时间超过 PHP 的时限,TIMEOUT。在php中是可以判断连接状态的。系统维护着连接状态,其状态有三种可能的情况: 
    0 - NORMAL(正常) 
    1 - ABORTED(异常退出) 
    2 - TIMEOUT(超时)
    使用connection_status()函数可以得到状态值。用php读入一个文件并输出后,用此函数查看连接状态就可以了。如果是0 - NORMAL(正常),那么,下载完毕!但是问题没有这么简单。用php读入一个文件并输出,实际上并不是直接输出的。因为php是与操作系统的进程打交道的。文件被写入到了输出缓冲。因此需要刷新PHP程序的输出缓冲,将当前为止程序的所有输出发送到用户的浏览器。 
    用flush()!flush() 函数不会对服务器或客户端浏览器的缓存模式产生影响。因此,必须同时使用ob_flush() 和flush() 函数来刷新输出缓冲。 
    但是有些情况下刷新是无效的,因为个别web服务器程序,特别是Win32下的web服务器程序,在发送结果到浏览器之前,仍然会缓存脚本的输出,直到程序结束为止。这意味着在windows下,可能会无效。有些Apache的模块,比如mod_gzip,可能自己进行输出缓存,这将导致flush()函数产生的结果不会立即被发送到客户端浏览器。这意味着,要下载的文件如果是压缩文件并且是需要即时性生成的,那么可能会无
    效。那么我们的程序流程:读文件,输出文件,刷新缓存,查看连接状态。
    读文件并输出最简单的函数是int fpassthru ( resource $handle )
    将给定的文件指针从当前的位置读取到 EOF 并把结果写到输出缓冲区。因此用这一个函数就够了,不用先读入再输出。
      

  30.   

    高人呀。我OUT了。感谢大家的参与。和你们相比,我简直即井底之蛙。我感觉这个功能也是做个插件,通过端口传送比较可靠。才能准确判断客户端已接收了多少字节,能够判断客户端是否点击了取消按钮。也有可能是IE的dll函数功能有此功能,只是不知道函数而已。