002 /**
003 * 文件传输,支持断点续传。
004 * 2g以上超大文件也有效
005 * @author MoXie
006 */
007 class Transfer {
008 /**
009 * 缓冲单元
010 */
011 const BUFF_SIZE = 5120; // 1024 * 5
012 /**
013 * 文件地址
014 * @var
015 */
016 private $filePath;
017 /**
018 * 文件大小
019 * @var
020 */
021 private $fileSize;
022 /**
023 * 文件类型
024 * @var
025 */
026 private $mimeType;
027 /**
028 * 请求区域(范围)
029 * @var
030 */
031 private $range;
032 /**
033 * 是否写入日志
034 * @var
035 */
036 private $isLog = false;
037 /**
038 *
039 * @param
040 * @param
041 * @param
042 */
043 function __construct($filePath, $mimeType = null , $range = null) {
044 $this->filePath = $filePath;
045 $this->fileSize = sprintf('%u',filesize($filePath));
046 $this->mimeType = ($mimeType != null)?$mimeType:"application/octet-stream"; // bin
047 $this->range = trim($range);
048 }
049 /**
050 * 获取文件区域
051 * @return
052 */
053 private function getRange() {
054 /**
055 * Range: bytes=-128
056 * Range: bytes=-128
057 * Range: bytes=28-175,382-399,510-541,644-744,977-980
058 * Range: bytes=28-175n380
059 * type 1
060 * RANGE: bytes=1000-9999
061 * RANGE: bytes=2000-9999
062 * type 2
063 * RANGE: bytes=1000-1999
064 * RANGE: bytes=2000-2999
065 * RANGE: bytes=3000-3999
066 */
067 if (!empty($this->range)) {
068 $range = preg_replace('/[s|,].*/','',$this->range);
069 $range = explode('-',substr($range,6));
070 if (count($range) < 2 ) {
071 $range[1] = $this->fileSize; // Range: bytes=-100
072 }
073 $range = array_combine(array('start','end'),$range);
074 if (empty($range['start'])) {
075 $range['start'] = 0;
076 }
077 if (!isset ($range['end']) || empty($range['end'])) {
078 $range['end'] = $this->fileSize;
079 }
080 return $range;
081 }
082 return null;
083 }
084 /**
085 * 向客户端发送文件
086 */
087 public function send() {
088 $fileHande = fopen($this->filePath, 'rb');
089 if ($fileHande) {
090 // setting
091 ob_end_clean();// clean cache
092 ob_start();
093 ini_set('output_buffering', 'Off');
094 ini_set('zlib.output_compression', 'Off');
095 $magicQuotes = get_magic_quotes_gpc();
096 set_magic_quotes_runtime(0);
097 // init
098 $lastModified = gmdate('D, d M Y H:i:s', filemtime($this->filePath)).' GMT';
099 $etag = sprintf('w/"%s:%s"',md5($lastModified),$this->fileSize);
100 $ranges = $this->getRange();
101 // headers
102 header(sprintf('Last-Modified: %s',$lastModified));
103 header(sprintf('ETag: %s',$etag));
104 header(sprintf('Content-Type: %s',$this->mimeType));
105 $disposition = 'attachment';
106 if (strpos($this->mimeType,'image/') !== FALSE) {
107 $disposition = 'inline';
108 }
109 header(sprintf('Content-Disposition: %s; filename="%s"',$disposition,basename($this->filePath)));
110
111 if ($ranges != null) {
112 if ($this->isLog) {
113 $this->log(json_encode($ranges).' '.$_SERVER['HTTP_RANGE']);
114 }
115 header('HTTP/1.1 206 Partial Content');
116 header('Accept-Ranges: bytes');
117 header(sprintf('Content-Length: %u',$ranges['end'] - $ranges['start']));
118 header(sprintf('Content-Range: bytes %s-%s/%s', $ranges['start'], $ranges['end'],$this->fileSize));
119 //
120 fseek($fileHande, sprintf('%u',$ranges['start']));
121 }else {
122 header("HTTP/1.1 200 OK");
123 header(sprintf('Content-Length: %s',$this->fileSize));
124 }
125 // read file
126 $lastSize = 0;
127 while(!feof($fileHande) && !connection_aborted()) {
128 $lastSize = sprintf("%u", bcsub($this->fileSize,sprintf("%u",ftell($fileHande))));
129 if (bccomp($lastSize,self::BUFF_SIZE) > 0) {
130 $lastSize = self::BUFF_SIZE;
131 }
132 echo fread($fileHande, $lastSize);
133 flush();
134 ob_flush();
135 }
136 set_magic_quotes_runtime($magicQuotes);
137 ob_end_flush();
138 }
139 if ($fileHande != null) {
140 fclose($fileHande);
141 }
142 }
143 /**
144 * 设置记录
145 * @param
146 */
147 public function setIsLog($isLog = true) {
148 $this->isLog = $isLog;
149 }
150 /**
151 * 记录
152 * @param
153 */
154 private function log($msg) {
155 try {
156 $handle = fopen('transfer_log.txt', 'a');
157 fwrite($handle, sprintf('%s : %s'.PHP_EOL,date('Y-m-d H:i:s'),$msg));
158 fclose($handle);
159 }catch(Exception $e) {
160 // null;
161 }
162 }
163 }
164 date_default_timezone_set('Asia/Shanghai');
165 error_reporting(E_STRICT);
166 function errorHandler($errno, $errstr, $errfile, $errline) {
167 echo '
error:',$errstr,'
';168 exit();
169 }
170 set_error_handler('errorHandler');
171 define('IS_DEBUG',true);
172
173 //
174 //
175 $filePath = '/Movie/The.Hurt.Locker.2008.x264.AC3-WAF.mkv';
176 $mimeType = 'audio/x-matroska';
177 $range = isset($_SERVER['HTTP_RANGE'])?$_SERVER['HTTP_RANGE']:null;
178 if (IS_DEBUG) {
179 // $range = "bytes=1000-1999n2000";
180 // $range = "bytes=1000-1999,2000";
181 // $range = "bytes=1000-1999,-2000";
182 // $range = "bytes=1000-1999,2000-2999";
183 }
184 set_time_limit(0);
185 $transfer = new Transfer($filePath,$mimeType,$range);
186 if (IS_DEBUG) {
187 $transfer->setIsLog(true);
188 }
189 $transfer->send();
190 ?>