PHP 实现大文件分片下载

在贴代码之前我们先看看我们是如何实现分片下载的.

我们发送HTTP请求的时候会设置header头, 在header头里面有一个属性叫做range,这个属性的意思是: 用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:

Range:(unit=first byte pos)-[last byte pos]
	

也就是说我们实现大文件的下载可以设置range来实现, 比如一个文件我们可以分割成多个部分:

比如一个文件2001个字节,我们分成5个线程去执行,每个线程500个字节

1.线程1设置header属性 Range: bytes=0-499          表示头500个字节

2.线程2设置header属性 Range: bytes=500-999      表示头501-1000个字节

3.线程3设置header属性 Range: bytes=1000-1499

4.线程4设置header属性 Range: bytes=1500-1999

5.线程5设置header属性 Range: bytes=2000-2001

下一个请求,应该是上一个请求的[end+1, nextEnd]

根据以上的原理,我们就可以编写代码了


class Async_download
{

	/**
	 * 下载地址
	 * @var string
	 */
	private $fileUrl = "";

	/**
	 * 文件大小 bytes
	 * @var integer
	 */
	private $fileSize = 0;

	/**
	 * 文件最大不可超过数 M
	 * @var [type]
	 */
	private $maxMerory = 20 * 1024;

	/**
	 * 运行时设置的内存几M
	 * @var [type]
	 */
	private $setRunMerory = 2 * 1024;

	/**
	 * 文件保存的路径
	 * @var string
	 */
	private $savePath = "";

	/**
	 * 开几个线程去下载
	 * @var integer
	 */
	private $runProcess = 10;

	/**
	 * 每个线程负责下载多大
	 * @var integer
	 */
	private $oneProcessMerory = 10;

	/**
	 * 线程数组
	 * @var array
	 */
	private $handle = [];

	/**
	 * 下载的数据分段数组
	 * 废弃
	 * @var array
	 */
	private $downloadData = [];

	
	public function __construct($fileUrl, $savePath)
	{
		$this->fileUrl = $fileUrl;
        if (!file_exists($savePath)) {
            mkdir($savePath, 0755, true);
        }

        $this->savePath = $path;
	}
	
	/**
	 * 保存文件
	 * @return [type] [description]
	 */
	public function save()
	{
		$this->getFileSize();
		if ($this->fileSize <= 0) {
			return false;
		}
		$fileSize = ceil($this->fileSize / 1024 / 1024);
		$now = $fileSize * 2;
		if ($this->maxMerory < $now) {
			return false;
		}

		if ($now > $this->setRunMerory) {
			$this->setRunMerory = $now * 1.5;
		}
		$this->runProcess = ceil($fileSize / $this->oneProcessMerory);

		$info = pathinfo($this->fileUrl);
		$fileName = microtime(true) * 10000 . $info['basename'];
		$this->savePath = $this->savePath . $fileName;
		ini_set('memory_limit', $this->setRunMerory . 'M');
		$data = $this->process($this->fileUrl);
		$fp = fopen($this->savePath, 'a');

		foreach ($data as $key => $value) {
			fwrite($fp, $value);
		}
		fclose($fp);
		return true;
	}
	
	/**
	 * 获取文件大小
	 * @return [type] [description]
	 */	
	public function getFileSize()
	{
		$data = $this->get_data($this->fileUrl);
		$headerArr = explode("\r\n", $data);
		if (!empty($headerArr)) {
			foreach ($headerArr as $item) {
			    $value = explode(':', $item);
			    if ($value[0] == 'Content-Length') {
			        $this->fileSize = (int)$value[1];
			        break;
			    }
			}
		}
	}

	/**
     * 获取文件头部信息
     * @param  [type] $url [description]
     * @return [type]      [description]
     */
    public function get_data($url)
    {
	    $oCurl = curl_init();
	 	$header[] = "";
	  	$user_agent = "Mozilla/4.0 (Linux; Andro 6.0; Nexus 5 Build) AppleWeb/537.36 (KHTML, like Gecko)";
	  	curl_setopt($oCurl, CURLOPT_URL, $url);
	  	curl_setopt($oCurl, CURLOPT_HTTPHEADER,$header);
	  	curl_setopt($oCurl, CURLOPT_HEADER, true);
	  	curl_setopt($oCurl, CURLOPT_NOBODY, true);
	  	curl_setopt($oCurl, CURLOPT_USERAGENT,$user_agent);
	  	curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
	  	curl_setopt($oCurl, CURLOPT_POST, false);
	  	$sContent = curl_exec($oCurl);
	  	$headerSize = curl_getinfo($oCurl, CURLINFO_HEADER_SIZE);
	  	$header = substr($sContent, 0, $headerSize);
	  	curl_close($oCurl);
	  	return $header;
	}

	public function process($url)
	{
		ini_set('memory_limit', $this->setRunMerory . 'M');
		$mh = curl_multi_init();
		$fileData = ceil($this->fileSize / $this->runProcess);

		for ($i = 0; $i < $this->runProcess; $i++) { 
			$ch = curl_init();
			if ($fileData > ($this->fileSize - ($i * $fileData))) {
		        $headerData = [
		            "Range:bytes=" . $i * $fileData . "-" . ($this->fileSize)
		        ];
		    } else {
		        $headerData = [
		            "Range:bytes=" . $i * $fileData . "-" .((($i + 1) * $fileData) - 1)
		        ];
		    }
		    curl_setopt($ch, CURLOPT_URL, $url);
		    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		    curl_setopt($ch, CURLOPT_TIMEOUT, 0);
		    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
		    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
		    curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);
		    curl_setopt($ch, CURLOPT_MAXREDIRS, $this->runProcess);
		    curl_multi_add_handle($mh, $ch);
		    $this->handle[$i] = $ch;	
		}

		$active = null;
		
		do {
		   $mrc = curl_multi_exec($mh, $active);
		} while ($active);

		for ($i = 0; $i < $this->runProcess; $i++) {
			// 使用yield 优化内存
		    // $this->downloadData[$i] = curl_multi_getcontent($this->handle[$i]);
		    yield $downloadData = curl_multi_getcontent($this->handle[$i]);
		    curl_multi_remove_handle($mh, $this->handle[$i]);
		}
		curl_multi_close($mh);
	}
}

$url = "http://vfx.mtime.cn/Video/2019/03/21/mp4/190321153853126488.mp4"
$downObj = new Async_download($url, "video_download");
$downObj->save();

标签:
作者:华传财
舞台上有你,就演好角色; 舞台上没你,就静静地做观众;

已有 0 位网友参与,快来吐槽:

发表评论