[php]一个发送文件的函数
这是前段时间在live-share.com上使用的文件发送函数,后来由于这种方式在高并发下负载过大,而放弃使用。此函数支持多线及断点续传,在非windows服务器下可限制速度,windows服务器因为不支持usleep函数,但用sleep函数效果不理想,所以放弃。此函数并不完全符合HTTP1.1标准,其中的断点续传部分没有考虑多个range的情况(事实上我还没见过哪个下载软件这么变态)。
下载: sendfile.php
- /**
- * 发送文件
- *
- * @author: legend(legendsky@hotmail.com)
- * @link: http://www.ugia.cn/?p=109
- * @description: send file to client
- * @version: 1.0
- *
- * @param string $fileName 文件名称或路径
- * @param string $fancyName 自定义的文件名,为空则使用filename
- * @param boolean $forceDownload 是否强制下载
- * @param integer $speedLimit 速度限制,单位为字节,0为不限制,不支持windows服务器
- * @param string $$contentType 文件类型,默认为application/octet-stream
- *
- * @return boolean
- */
- function sendFile($fileName, $fancyName = '', $forceDownload = true, $speedLimit = 0, $contentType = '')
- {
- if (!is_readable($fileName))
- {
- header("HTTP/1.1 404 Not Found");
- return false;
- }
- $fileStat = stat($fileName);
- $lastModified = $fileStat['mtime'];
- $md5 = md5($fileStat['mtime'] .'='. $fileStat['ino'] .'='. $fileStat['size']);
- $etag = '"' . $md5 . '-' . crc32($md5) . '"';
- header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT');
- header("ETag: $etag");
- if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModified)
- {
- header("HTTP/1.1 304 Not Modified");
- return true;
- }
- if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_UNMODIFIED_SINCE']) < $lastModified)
- {
- header("HTTP/1.1 304 Not Modified");
- return true;
- }
- if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
- {
- header("HTTP/1.1 304 Not Modified");
- return true;
- }
- if ($fancyName == '')
- {
- $fancyName = basename($fileName);
- }
- if ($contentType == '')
- {
- $contentType = 'application/octet-stream';
- }
- $fileSize = $fileStat['size'];
- $contentLength = $fileSize;
- $isPartial = false;
- if (isset($_SERVER['HTTP_RANGE']))
- {
- if (preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches))
- {
- $startPos = $matches[1];
- $endPos = $matches[2];
- if ($startPos == '' && $endPos == '')
- {
- return false;
- }
- if ($startPos == '')
- {
- $startPos = $fileSize - $endPos;
- $endPos = $fileSize - 1;
- }
- else if ($endPos == '')
- {
- $endPos = $fileSize - 1;
- }
- $startPos = $startPos < 0 ? 0 : $startPos;
- $endPos = $endPos > $fileSize - 1 ? $fileSize - 1 : $endPos;
- $length = $endPos - $startPos + 1;
- if ($length < 0)
- {
- return false;
- }
- $contentLength = $length;
- $isPartial = true;
- }
- }
- // send headers
- if ($isPartial)
- {
- header('HTTP/1.1 206 Partial Content');
- header("Content-Range: bytes $startPos-$endPos/$fileSize");
- }
- else
- {
- header("HTTP/1.1 200 OK");
- $startPos = 0;
- $endPos = $contentLength - 1;
- }
- header('Pragma: cache');
- header('Cache-Control: public, must-revalidate, max-age=0');
- header('Accept-Ranges: bytes');
- header('Content-type: ' . $contentType);
- header('Content-Length: ' . $contentLength);
- if ($forceDownload)
- {
- header('Content-Disposition: attachment; filename="' . rawurlencode($fancyName). '"');
- }
- header("Content-Transfer-Encoding: binary");
- $bufferSize = 2048;
- if ($speedLimit != 0)
- {
- $packetTime = floor($bufferSize * 1000000 / $speedLimit);
- }
- $bytesSent = 0;
- $fp = fopen($fileName, "rb");
- fseek($fp, $startPos);
- //fpassthru($fp);
- while ($bytesSent < $contentLength && !feof($fp) && connection_status() == 0 )
- {
- if ($speedLimit != 0)
- {
- list($usec, $sec) = explode(" ", microtime());
- $outputTimeStart = ((float)$usec + (float)$sec);
- }
- $readBufferSize = $contentLength - $bytesSent < $bufferSize ? $contentLength - $bytesSent : $bufferSize;
- $buffer = fread($fp, $readBufferSize);
- echo $buffer;
- ob_flush();
- flush();
- $bytesSent += $readBufferSize;
- if ($speedLimit != 0)
- {
- list($usec, $sec) = explode(" ", microtime());
- $outputTimeEnd = ((float)$usec + (float)$sec);
- $useTime = ((float) $outputTimeEnd - (float) $outputTimeStart) * 1000000;
- $sleepTime = round($packetTime - $useTime);
- if ($sleepTime > 0)
- {
- usleep($sleepTime);
- }
- }
- }
- return true;
- }
demo: 下载:周杰伦-《千里之外》
<?php
sendFile('./wp-data/jay_chou_-_outside_great_distance.mp3', 'outside_great_distance.mp3');
?>
sendFile('./wp-data/jay_chou_-_outside_great_distance.mp3', 'outside_great_distance.mp3');
?>

email & msn:
司马易风 said,
September 21, 2006 @ 8:04 am
晕晕~~~我也常碰到断线,然后又得重新来过,但是目前我的程序水平太差,还看不太懂,继续努力中……
靓华 said,
September 21, 2006 @ 9:20 am
晕,怎么下载了113K就完成了……说是有6MB多的
legend said,
September 21, 2006 @ 10:07 am
是这样的,这个服务器有个设置,超过一定请求数,重启apache。可能是不巧让你碰上了,重新下,呵呵。
Phzzy said,
September 21, 2006 @ 10:48 am
没看懂..-.-”
header 太多..N多没见过..哈..第一见到 ETag, 刚查了下才知道是啥意思:)
longbill said,
September 26, 2006 @ 4:27 pm
太强了,我以前也做过类似的东西,但是都没有实现短点续传功能,,
rf said,
September 27, 2006 @ 5:51 pm
cache真是无处不在啊
Jay said,
October 10, 2006 @ 12:53 am
服务器需要怎么配置吗?
legend said,
October 10, 2006 @ 1:16 am
不需要
feifengxlq said,
October 27, 2006 @ 11:45 pm
强悍~
ahu said,
November 26, 2006 @ 6:01 pm
嗯,不错!
rec0n said,
January 2, 2007 @ 7:57 pm
非常好。
gzty said,
March 7, 2007 @ 1:37 pm
不错
不知道对超过100M的文件支持怎么样
legend said,
March 7, 2007 @ 1:53 pm
呵呵,几个G的都发过。
cc said,
March 12, 2007 @ 11:01 am
没有注释,看不懂!
gg said,
March 23, 2007 @ 1:45 am
这个类有问题,下载文件,头部会多出几个字节,
legend said,
March 23, 2007 @ 6:42 am
把你的文件发到这里看看。
cglc63 said,
April 25, 2007 @ 3:51 pm
哎呀,我的妈呀,很是有写难度哦,加油努力ing…
xutao said,
May 9, 2007 @ 10:30 am
我用了你这个类,确实下载下来的文件,在头部会多出一个字节,是空格。
怎么解决?望指教阿。
我试验过下载xml文件,txt文件,tar文件,都是一个现象。
如果你们用了没有问题,可能和php或者apache的设置有关,不懂,高手指教。
非常感谢。
legend said,
May 10, 2007 @ 12:20 am
告诉我你们的服务器版本和php版本,并把出问题的php文件和下载过的错误文件前几个字节贴上来看看,谢谢!
我这里是没有问题。
huhu said,
June 14, 2007 @ 8:31 pm
不知道如何使用 下载地址该放到哪里? 还有就是上面的代码该设置到什么地方!
啊哈 said,
August 26, 2007 @ 5:10 pm
唉,用这个函数,下载人数一多,内存使用量飙升啊
啊哈 said,
August 26, 2007 @ 5:10 pm
有没有更好的解决方法?
baidu said,
November 20, 2007 @ 8:50 am
强..
redlz2500 said,
December 27, 2007 @ 11:02 pm
非常感谢!
juli said,
January 19, 2008 @ 2:46 pm
请问你与 Live-Share.com 是什么关系的呢??
juli said,
January 19, 2008 @ 4:19 pm
最近才发现这里,四处看了一下,觉得挺好.
刚接触这东西,有很多不懂的.也来发表一下意见.错了请各位莫见怪.
1,”其中的断点续传部分没有考虑多个range的情况”
我觉得这个不是你函数该处理的问题,也不是服务器该处理的问题.从最基本的层面进行设计,很多东西都是一问一答的简单实现.就好比移动的客服与客户,通常都是每个客户拿自己的手机拨客服电话,而客服也只需要一个服务员接一个电话,没必要假设:当这个问完了,如果不挂机,换另一个人再来接着说,是否要再报一下工号的问题.
因此,我认为服务端只需要处理从哪一点开始取,到哪点结束就行了.现在的下载软件多线程分段也是这样,每线程只负责不同的起点请求,而不同起点间的区段问题,它自己处理,协调,在适当的时候中止线程. 如果是这样,那么这个函数和IIS的机制一样,请求越多,对应的处理越多,点资源越多.这不是一个(仅)发送功能函数的问题.
2,既然这是已不采用的,那么现在采用的是什么呢?
legend said,
January 19, 2008 @ 8:32 pm
只处理一个range的情况
peak said,
August 21, 2008 @ 2:20 pm
看看。不错。
diybl said,
August 24, 2008 @ 11:47 am
有下载的吗