php实现大文件断点续传下载实例,看完你就知道超过100M以上的大文件如何断点传输了,这个功能还是比较经典实用的,毕竟大文件上传功能经常用得到。
1 require_once('download.class.php');
2 date_default_timezone_set('Asia/Shanghai');
3 error_reporting(E_STRICT);
4
5 function errorHandler($errno, $errstr, $errfile, $errline) {
6 echo '<p>error:', $errstr, '</p>';
7 exit();
8 }
9
10 set_error_handler('errorHandler');
11 define('IS_DEBUG', true);
12
13 $filePath = 'test.zip';
14 $mimeType = 'audio/x-matroska';
15 $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : null;
16 if (IS_DEBUG) {
17 // $range = "bytes=1000-1999\n2000";
18 // $range = "bytes=1000-1999,2000";
19 // $range = "bytes=1000-1999,-2000";
20 // $range = "bytes=1000-1999,2000-2999";
21 }
22 set_time_limit(0);
23 $transfer = new Transfer($filePath, $mimeType, $range);
24 if (IS_DEBUG) {
25 $transfer->setIsLog(true);
26 }
27 $transfer->send();
download.class.php
1 /**
2 * 文件传输,支持断点续传。
3 * 2g以上超大文件也有效
4 * @author MoXie
5 */
6 class Transfer {
7
8 /**
9 * 缓冲单元
10 */
11 const BUFF_SIZE = 5120; // 1024 * 5
12
13 /**
14 * 文件地址
15 * @var <String>
16 */
17
18 private $filePath;
19
20 /**
21 * 文件大小
22 * @var <String> Php超大数字 字符串形式描述
23 */
24 private $fileSize;
25
26 /**
27 * 文件类型
28 * @var <String>
29 */
30 private $mimeType;
31
32 /**
33 * 请求区域(范围)
34 * @var <String>
35 */
36 private $range;
37
38 /**
39 * 是否写入日志
40 * @var <Boolean>
41 */
42 private $isLog = false;
43
44 /**
45 *
46 * @param <String> $filePath 文件路径
47 * @param <String> $mimeType 文件类型
48 * @param <String> $range 请求区域(范围)
49 */
50 function __construct($filePath, $mimeType = null, $range = null) {
51 $this->filePath = $filePath;
52 $this->fileSize = sprintf('%u', filesize($filePath));
53 $this->mimeType = ($mimeType != null) ? $mimeType : "application/octet-stream"; // bin
54 $this->range = trim($range);
55 }
56
57 /**
58 * 获取文件区域
59 * @return <Map> {'start':long,'end':long} or null
60 */
61 private function getRange() {
62 /**
63 * Range: bytes=-128
64 * Range: bytes=-128
65 * Range: bytes=28-175,382-399,510-541,644-744,977-980
66 * Range: bytes=28-175\n380
67 * type 1
68 * RANGE: bytes=1000-9999
69 * RANGE: bytes=2000-9999
70 * type 2
71 * RANGE: bytes=1000-1999
72 * RANGE: bytes=2000-2999
73 * RANGE: bytes=3000-3999
74 */
75 if (!empty($this->range)) {
76 $range = preg_replace('/[\s|,].*/', '', $this->range);
77 $range = explode('-', substr($range, 6));
78 if (count($range) < 2) {
79 $range[1] = $this->fileSize; // Range: bytes=-100
80 }
81 $range = array_combine(array('start', 'end'), $range);
82 if (empty($range['start'])) {
83 $range['start'] = 0;
84 }
85 if (!isset($range['end']) || empty($range['end'])) {
86 $range['end'] = $this->fileSize;
87 }
88 return $range;
89 }
90 return null;
91 }
92
93 /**
94 * 向客户端发送文件
95 */
96 public function send() {
97 $fileHande = fopen($this->filePath, 'rb');
98 if ($fileHande) {
99 // setting
100 ob_end_clean(); // clean cache
101 ob_start();
102 ini_set('output_buffering', 'Off');
103 ini_set('zlib.output_compression', 'Off');
104 $magicQuotes = get_magic_quotes_gpc();
105 // set_magic_quotes_runtime(0);
106 // init
107 $lastModified = gmdate('D, d M Y H:i:s', filemtime($this->filePath)) . ' GMT';
108 $etag = sprintf('w/"%s:%s"', md5($lastModified), $this->fileSize);
109 $ranges = $this->getRange();
110 // headers
111 header(sprintf('Last-Modified: %s', $lastModified));
112 header(sprintf('ETag: %s', $etag));
113 header(sprintf('Content-Type: %s', $this->mimeType));
114 $disposition = 'attachment';
115 if (strpos($this->mimeType, 'image/') !== FALSE) {
116 $disposition = 'inline';
117 }
118 header(sprintf('Content-Disposition: %s; filename="%s"', $disposition, basename($this->filePath)));
119
120 if ($ranges != null) {
121 if ($this->isLog) {
122 $this->log(json_encode($ranges) . ' ' . $_SERVER['HTTP_RANGE']);
123 }
124 header('HTTP/1.1 206 Partial Content');
125 header('Accept-Ranges: bytes');
126 header(sprintf('Content-Length: %u', $ranges['end'] - $ranges['start']));
127 header(sprintf('Content-Range: bytes %s-%s/%s', $ranges['start'], $ranges['end'], $this->fileSize));
128 //
129 fseek($fileHande, sprintf('%u', $ranges['start']));
130 } else {
131 header("HTTP/1.1 200 OK");
132 header(sprintf('Content-Length: %s', $this->fileSize));
133 }
134 // read file
135 $lastSize = 0;
136 while (!feof($fileHande) && !connection_aborted()) {
137 $lastSize = sprintf("%u", bcsub($this->fileSize, sprintf("%u", ftell($fileHande))));
138 if (bccomp($lastSize, self::BUFF_SIZE) > 0) {
139 $lastSize = self::BUFF_SIZE;
140 }
141 echo fread($fileHande, $lastSize);
142 ob_flush();
143 flush();
144 }
145 set_magic_quotes_runtime($magicQuotes);
146 ob_end_flush();
147 }
148 if ($fileHande != null) {
149 fclose($fileHande);
150 }
151 }
152
153 /**
154 * 设置记录
155 * @param <Boolean> $isLog 是否记录
156 */
157 public function setIsLog($isLog = true) {
158 $this->isLog = $isLog;
159 }
160
161 /**
162 * 记录
163 * @param <String> $msg 记录信息
164 */
165 private function log($msg) {
166 try {
167 $handle = fopen('transfer_log.txt', 'a');
168 fwrite($handle, sprintf('%s : %s' . PHP_EOL, date('Y-m-d H:i:s'), $msg));
169 fclose($handle);
170 } catch (Exception $e) {
171 // null;
172 }
173 }
174
175 }
参考文章:http://blog.ncmem.com/wordpress/2023/09/20/php实现大文件断点续传下载实例/