//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]); }
通常用的是 ceho、print 有的干脆用 readfile
这样 php 就没有机会去判断用户是否终止了下载你需要在循环中用较小的块,通过 sprintf 输出文件内容
springf 函数会返回一个表示输出了多少字符的数。你可以累计这个数,并与文件长度进行比较
从而判断下载是否完成
上面说的控件那些不是我想要的,chrome浏览器很可能是未来主流。
在此基础上做了些改动,代码中有注释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;
}
@ini_set('zlib.output_compression', 0);
@ini_set('implicit_flush', 1);
你再看看还能不能改进。
主要还是这个sleep(1)。。
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'); }
1、不中断,让文件全部下载。但是下载后的文件大小是对的,内容不完整
其实我自己写的下载函数也是这样,后来才找到那个 send_file2、与链接处理相关的函数,基本不起作用(xp + apache2.2.11 + php 5.3.6)
只在中断下载时,download.txt 中增加一条
abort 2012-04-14 15:05:08
这样的记录
居然有内容不完整的情况,2的问题,我用chrome,倒没遇到,能正常输出要么abort,要么complete
呵呵,是没……方便吗,测试时候,加上set_time_limit限制下时间呢????我本地测试文本很小,不会超出默认的30秒如果稍大文件确实有你说的2点情况
矛盾体是那个sleep,没有这个sleep,flush即时推送给浏览器根本就不能工作,不能工作意味着浏览器在等你web服务器把完整的内容推送给我,我浏览器才接收到,因为你整个while挂在那,不sleep,cpu是不得空处理别的事情的,但是如果有sleep,即时刷新缓冲也能正常运作的话,也是我们人为的增大下载的时间开销。假设内部网环境,下载速度很快,我服务端每次sleep(1)推送缓冲,记录推送字节数,客户端马上接收到,但此时就要随着服务端暂停一秒才接收下一个字节块。假设外网环境,客户下载速度超级慢,下载文件比较大,我服务器端php可能while都运行结束了(意味着记录用户下载完整),但是我客户端很可能还在龟速的接收来自服务端的返回,这时候客户端一点击取消,结果不就不同步了吗。
这么说把,咱php读1G的数据的时间,是否就肯定等同于客户下载1G数据的时间?
如果说 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
两者的差就是你最担心的部分
这个问题是没必要在localhost测试的,因为本地数据包传输速度那是不受任何限制的,每个农民工把个货物丢上车厢,浏览器马上就接收到了。
我个人也赞同此功能只能在客户端完成,flash是没问题的,首先接收http返回头里的content-length,然后这个字节数和已保存成文件的字节数一作对比即可。
要借用控件,若想兼容各个浏览器,我觉的目前flash算是个方案,页面嵌入个flash,通过flash去请求那个下载链接,flash模拟文件下载那个进度条,然后如果按取消按钮或者下载完成后都会触发一个事件通知服务器。
未来HTML5有WebSocket,应该可以实现这个功能。
com组件的东东吧,只是不明白这样做用在什么地方?
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 并把结果写到输出缓冲区。因此用这一个函数就够了,不用先读入再输出。