php实现大文件断点续传下载实例代码

php实现大文件断点续传下载实例,看完你就知道超过100M以上的大文件如何断点传输了,这个功能还是比较经典实用的,毕竟大文件上传功能经常用得到。

php实现大文件断点续传下载实例代码

  1. require_once('download.class.php');
  2. date_default_timezone_set('Asia/Shanghai');
  3. error_reporting(E_STRICT);
  4. function errorHandler($errno, $errstr, $errfile, $errline) {
  5. echo '<p>error:', $errstr, '</p>';
  6. exit();
  7. }
  8. set_error_handler('errorHandler');
  9. define('IS_DEBUG', true);
  10. $filePath = 'test.zip';
  11. $mimeType = 'audio/x-matroska';
  12. $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : null;
  13. if (IS_DEBUG) {
  14. // $range = "bytes=1000-1999\n2000";
  15. // $range = "bytes=1000-1999,2000";
  16. // $range = "bytes=1000-1999,-2000";
  17. // $range = "bytes=1000-1999,2000-2999";
  18. }
  19. set_time_limit(0);
  20. $transfer = new Transfer($filePath, $mimeType, $range);
  21. if (IS_DEBUG) {
  22. $transfer->setIsLog(true);
  23. }
  24. $transfer->send();
  25. require_once('download.class.php');
  26. date_default_timezone_set('Asia/Shanghai');
  27. error_reporting(E_STRICT);
  28. function errorHandler($errno, $errstr, $errfile, $errline) {
  29. echo '<p>error:', $errstr, '</p>';
  30. exit();
  31. }
  32. set_error_handler('errorHandler');
  33. define('IS_DEBUG', true);
  34. $filePath = 'test.zip';
  35. $mimeType = 'audio/x-matroska';
  36. $range = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : null;
  37. if (IS_DEBUG) {
  38. // $range = "bytes=1000-1999\n2000";
  39. // $range = "bytes=1000-1999,2000";
  40. // $range = "bytes=1000-1999,-2000";
  41. // $range = "bytes=1000-1999,2000-2999";
  42. }
  43. set_time_limit(0);
  44. $transfer = new Transfer($filePath, $mimeType, $range);
  45. if (IS_DEBUG) {
  46. $transfer->setIsLog(true);
  47. }
  48. $transfer->send();

download.class.php

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