欢迎光临
专注前后端开发技术与经验分享

php实现断点续传输出文件

基于PHP实现类似百度网盘文件下载限速的功能

/**
 * 对外输出文件
 * @param string $file 文件相对路径或绝对路径
 * @param string $name 文件名称(客户端浏览器显示使用)
 * @param int $speed 下载速度(b/秒)
 */
function echoFile($file,$name,$speed){
  set_time_limit(0);

  ob_flush();
  flush();

  $fileSize = filesize($file);

  header('Cache-Control: no-cache');
  header('Content-Type: application/octet-stream');
  header('Content-Disposition: attachment; filename="' . urlencode($name) . '"');
  header('Accept-Ranges: bytes');
  header("Date: ".gmdate("D, d M Y H:i:s",time())." GMT");
  header("Last-Modified: " . gmdate("D, d M Y H:i:s",filemtime($file)) . " GMT");

  $isRange = isset($_SERVER['HTTP_RANGE']);

  $rangeList = analysisHttpRange($fileSize,$_SERVER['HTTP_RANGE']??null);
  if(!$rangeList){
    $rangeList = [
      [
        "start" => 0,
        "end" => $fileSize - 1
      ]
    ];
  }

  $contentLength = 0;
  foreach ($rangeList as $item) {
    $contentLength += $item["end"] - $item["start"] + 1;
  }
  header('Content-Length: ' . $contentLength);

  if($isRange){
    header('HTTP/1.1 206 Partial Content');
    $rangeStr = "";
    foreach ($rangeList as $item) {
      $str = $item["start"] . "-" . $item["end"] . "/" . $fileSize;
      if($rangeStr == ""){
        $rangeStr = $str;
      }else{
        $rangeStr = $rangeStr . "," . $str;
      }
    }
    header("Content-Range: bytes " . $rangeStr);
  }else{
    header('HTTP/1.1 200 OK');
  }

  foreach ($rangeList as $item) {
    $fp = fopen($file, 'r');
    fseek($fp,$item["start"]);

    $count = $item["end"] - $item["start"] + 1;
    $sum = ceil($count / $speed);
    for ($i=0; $i < $sum; $i++) {

      if($i == $sum - 1){ // 最后一块文件
        echo fread($fp, ($item["end"]+1) - (($sum - 1) * $speed));
      }else{
        echo fread($fp, $speed);
      }

      ob_flush();
      flush();

      sleep(1);
    }

    fclose($fp);
  }

  exit;
}

/**
 * 解析http请求头中的range
 */
function analysisHttpRange(int $fileSize,$range):array{
  if(!$range)return [];
  $fileEnd = $fileSize - 1;
  $range = preg_replace("/[^0-9\.\-\,]/", "", $range);
  if(!$range)return [];
  $rangeList = explode(",",$range);
  if(count($rangeList) < 1)return [];
  $list = [];
  foreach ($rangeList as $item) {
    $lineIndex = strpos($item,"-");
    if($lineIndex === false)continue;
    if($lineIndex === 0){ //反向获取

      $end = (int)str_replace("-","",$item);
      if($end > $fileEnd)$end = $fileEnd;
      $list[] = [
        "start" => $fileEnd - $end,
        "end" => $fileEnd,
      ];

    }else if($lineIndex == (strlen($item) - 1)){ //从指定位置到结束

      $start = (int)str_replace("-","",$item);
      if($start > $fileEnd)$start = $fileEnd;
      $list[] = [
        "start" => $start,
        "end" => $fileEnd,
      ];

    }else{ //选择区间

      $se = explode("-",$item);
      $se[0] = (int)$se[0];
      $se[1] = (int)$se[1];
      if(
        $se[1] < $se[0]
        || $se[1] > $fileEnd
        || $se[0] > $fileEnd
      )continue;

      $list[] = [
        "start" => $se[0],
        "end" => $se[1]
      ];

    }
  }

  $total = 0;
  foreach ($list as $item) {
    $total += $item["end"] - $item["start"];
  }

  if($total > $fileSize)return [];

  return $list;
}

调用示例:

echoFile(
  "./test.zip",
  "测试.zip",
  1024*1024*2
); //把test.zip以2m/s的速度发送到客户端

实现原理是在HTTP请求头中设置Range字段,指定需要下载的文件的起始字节和结束字节,服务器可以根据这个请求头返回相应的文件内容给客户端,客户端接收到的数据只是文件的一部分,而不是整个文件。客户端可以将这部分数据追加到已经下载的文件中,然后继续请求下一部分数据,直到完成整个文件的下载。

赞(0) 打赏
未经允许不得转载:杰哥博客 » php实现断点续传输出文件

觉得文章有用就打赏杰哥一杯可乐吧~

支付宝扫一扫打赏

微信扫一扫打赏