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

php实现断点续传输出文件

jiege阅读(431)

基于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字段,指定需要下载的文件的起始字节和结束字节,服务器可以根据这个请求头返回相应的文件内容给客户端,客户端接收到的数据只是文件的一部分,而不是整个文件。客户端可以将这部分数据追加到已经下载的文件中,然后继续请求下一部分数据,直到完成整个文件的下载。

php使用微秒级定时器及获取毫秒级时间戳

jiege阅读(830)

日常开发php常用的sleep()time()都只支持秒级单位,部分场景需要用到毫秒级的时间戳或定时器就比较淡疼了,在翻阅百度后总结一下:

/**
 * 获取当前毫秒级时间戳
 */
function getMillisecond() {
  list($t1, $t2) = explode(' ', microtime());
  return (float)sprintf('%.0f',(floatval($t1)+floatval($t2))*1000);
}

$startTime = getMillisecond();

usleep(1000 * 500); //微秒级定时器,一微秒等于百万分之一秒(1000*1000)

$endTime = getMillisecond();

var_dump("运行时间 => " . ($endTime - $startTime) . "ms");

运行输出结果:

demo.php:18:string '运行时间 => 501ms' (length=19)

亲测有效~

linux环境下使用命令运行php脚本

jiege阅读(931)

  • 将php脚本挂到后台运行,并输出日志到指定文件
    nohup php ./demo.php > demoLog.log &1
  • 查看上面运行的php脚本进程
    ps -aux|grep demo.php
  • 杀掉指定进程
    kill -9 ID

PHP判断变量类型是否为匿名函数

jiege阅读(1273)

PHP7新增的匿名函数,利用is_callable()检测变量是否为匿名函数

$fun = function(){
  return "我是匿名函数";
};
var_dump(is_callable($fun));
//将输出=> bool(true)

php正则匹配(.*?)失效的问题

jiege阅读(579)

今天写正则匹配一个网页的bas64图片时,死活匹配不出img标签的src属性,百度谷歌完发现是正则对匹配字符串长度和循环次数有限制,加上以下代码就正常啦:

$html = "你的html字符串";
ini_set("pcre.backtrack_limit",-1); //不限制最大回溯数
ini_set("pcre.recursion_limit",-1); //不限制最大嵌套数
preg_match_all("/src=\"(.*?)\"/s",$html,$rt);
var_dump($rt[1]);

VScode上配置Xdebug调试php代码

jiege阅读(1249)

环境WSL: win10+ubuntu18.4+宝塔

1.在宝塔上为php安装xdebug扩展

( 这里以php8.0为例,其他版本也是一样的步骤,注意安装完扩展需要重启PHP才生效 )
file

2.vscode安装插件“PHP Debug”

打开vscode搜索插件“PHP Debug”安装第一个即可
file

3.根据vscode插件“PHP Debug”提示配置php.ini

返回宝塔,打开PHP8.0的配置文件,在最后一行插入:

[XDebug]
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_port = 9001
xdebug.remote_enable = 1
xdebug.remote_autostart = 1

保存后重启PHP
(这里xdebug端口选择9001,默认的9000端口可能会与其他软件起冲突)
file

4.vscode创建debug配置文件launch.json

打开目录: .vscode/launch.json
(如果没有这个文件请手动创建)
file
粘贴进以下配置:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9001
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9001
        }
    ]
}

粘贴完成后保存文件并重启vscode

5.运行xdebug调试

打开你的php项目,在vscode内按下{f5},打上断点,发起请求后即可运行debug调试代码
file