首页  >  文章  >  后端开发  >  php 实现文件下载 断点续传 原理

php 实现文件下载 断点续传 原理

WBOY
WBOY原创
2016-06-20 13:05:201162浏览

php 实现文件下载  断点续传 原理

php文件下载

文件下载就是把文件从服务器上面下下来,比如一个文件是http://www.xxx.com/1.rar(真实存在这个文件),直接在浏览器上面输入,是可以弹出下载对话框的,但是如果是一个图片地址,很可能就是不是下载了,而是用浏览器直接打开.还有就是为了不让客户端看到下载地址,最好也是不直接提供下载文件地址的.还有就是比如只有登录用户才能下载。。 等等 .所以还是要单独写个下载程序

有几个响应头比较重要,记录下

Content-type

Content-Disposition

Content-Length

Pragma

Cache-control

Content-type

Content-type 告诉浏览器文件的MIME 类型,这是非常重要的一个响应头了,MIME种类繁多,真是太多了

Content-Disposition

Content-Disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。 嗯,就是这个头哟,激活弹出提示下载框

因为MIME的类型非常多,很可能会在程序中漏掉一些MIME类型.可以这样,漏掉了类型,全部以"Content-type: application/octet-stream" "Content-Disposition: attachment; filename=xxx.xx"(xx.xx为文件名) 经过一些浏览器的测试发现下载下来的文件都是正确的

Content-Length

"Content-Length: 123" 就是告诉浏览器这个文件的大小是123字节,其实我发现好像不设置这个头,浏览器也能自己识别

Pragma Cache-control

把这2个头都设置成public 告诉浏览器缓存(其实对于缓存头 我有很多都没理解清楚 除了public 它表示全部都缓存,但是至于浏览器缓存不缓存,可能还不一定,我试了很多浏览器,没有一个是从浏览器缓存读文件的,但是从服务器上下的)

 

在输出文件的时候,我看网上大概有2中方式:一种是一次性输出,一中是分段输出(每次输出一部分),我去csdn问了下,别人说分段输出好,减轻服务器压力,因为每次读文件的时候占的内存都少

一个例子(里面的MIME很少,更多的MIME最好去网上找)

class downLoad{
	var $file_name;
	var $file_dir;
	var $buffer_size = 1024;
	var $err = "";
	public static $MIME_type = array(
								"pdf"  =>"application/pdf",
								"exe"  =>"application/octet-stream",
								"zip"  =>"application/zip",
								"doc"  =>"application/msword",
								"xls"  =>"application/vnd.ms-excel",
								"ppt"  =>"application/vnd.ms-powerpoint",
								"gif"  =>"image/gif",
								"png"  =>"image/png",
								"jpeg" =>"jpg",
								"mp3"  =>"audio/mpeg",
								"wav"  =>"audio/x-wav",
								"mpeg" =>"mpg",
								"mpe"  =>"video/mpeg",
								"mov"  =>"video/quicktime",
								"avi"  =>"video/x-msvideo",				
							);
	public function __construct($file_dir="",$file_name=""){
		$this->file_dir  = $file_dir;
		$this->file_name = $file_name;
		$this->path = $file_dir."/".$file_name;
		$this->suffix = pathinfo($file_name,PATHINFO_EXTENSION);
	}
	
	public function down(){
		if(!file_exists($this->path)){
			$this->err = "该文件被移除了";
			return false;
		}

		$content_type = $this->getMIME($this->suffix);
		$file_size = filesize($this->path);
		
		header("Content-type: ".$content_type);
		header('Content-Disposition: attachment; filename="'.$this->file_name.'"');
		
		@header("Cache-control: public");
		@header("Pragma: public"); 
		header("Content-Length: ".$file_size);
		ob_end_clean(); 
		//readfile($this->path); 一次性读出来

		$fp= fopen($this->path,"r");
		$buffer_size = $this->buffer_size;
		$cur_pos = 0; //记录读了多少了
		
		while(!feof($fp) && $file_size>$buffer_size+$cur_pos){
			$buffer = fread($fp,$buffer_size); //每次读1024字节
			echo $buffer;
			$cur_pos += $buffer_size;
		}
		//把剩下的读出来 因为文件的带下很有很能不是1024 的整数倍
		$buffer = fread($fp,$file_size-$cur_pos);
  		echo $buffer;
  		fclose($fp);
  		return true;		
	}
	
	public function getMIME($key=""){
		if($key == "" || !isset(self::$MIME_type[$key])){
			return "application/octet-stream";
		}
		return self::$MIME_type[$key];
	}
}
//	$x = new downLoad($file_dir,$file_name);  $file_dir路径 比如 all     $file_name文件名 比如 a.exe  合起来就是全部的路径了all/a.exe
//	$x->down();


 

php 断点续传

这里面的断点续传指的是下载时候的断点续传的断点续传,上传的断点续传完全不明白原理

一些预备的东西

http 状态码206的意思

响应头 "Accept-Ranges: bytes"

响应头 "Content-Range:bytes start-end/all"

fseek

ob_flush

http 状态码 206

请求的 header 必须包含一个 Range 字段,表明自己请求第几个字节到第几个字节的内容。如果服务器支持的话,就返回 206 Partial Content,然后使用 header 的 Content-Range 指明范围,并在 body 内提供这个范围内的数据。

Accept-Ranges: bytes

这个字段说明Web服务器是否支持Range(是否支持断点续传功能),如果支持,则返回Accept-Ranges: bytes,如果不支持,则返回Accept-Ranges: none.

Content-Range:

用于响应头,指定了返回的Web资源的字节范围。这个字段值的格式是: 开始字节位置—结束字节位置/Web资源的总字节数,一个例子:Content-Range:1000-3000/5000

fseek : 该函数把文件指针从当前位置向前或向后移动到新的位置,新位置从文件头开始以字节数度量

ob_flush : 该函数把文件指针从当前位置向前或向后移动到新的位置,新位置从文件头开始以字节数度量

我测试了一下 ie7,8是不支持断点续传的(也许可以 但是我不会),苹果浏览器也不行.哎.......... (还好 firefox chrome Opera支持)

 

断点续传的过程是这样的

    首先客户端发一次请求给php请求下载文件
    这时因为是第一次请求 所以请求头里面是没有 Range头的
    我用Fiddler抓包的截图
    请求头

在php端 $_SERVER['HTTP_RANGE']可以取请求头里面的Rang, 因为第一次不存在这个,所以我们取文件内容就取全部,然后返回的状态码也是200

    响应头

 

    当传了一部分内容后,点击暂停,在点击几乎继续,抓包的响应头就有Range了

 

    php端返回的时候也把返回内容的范围返回了 Content-Range: bytes 4194304-37939151/37939152 是返回数据的范围
    Accept-Ranges: bytes表示支持断点续传
    Content-Length: 33744847 里面的值是根据请求的Range, 也就是 $_SERVER['HTTP_RANGE'], 和文件的总大小,相减得出来的
    另外http状态码是206

例子

	class downLoad
	{
		var $file_path;
		var $file_name;
		var $file_suffix;
		var $speed = 1024; //表示下载速度1m
		var $err = "";
		var $range_start=0;
		var $range_end;
		var $http_range;
		var $file_size;
		public static $MIME_type = array(
									"pdf"  =>"application/pdf",
									"exe"  =>"application/octet-stream",
									"zip"  =>"application/zip",
									"doc"  =>"application/msword",
									"xls"  =>"application/vnd.ms-excel",
									"ppt"  =>"application/vnd.ms-powerpoint",
									"gif"  =>"image/gif",
									"png"  =>"image/png",
									"jpeg" =>"jpg",
									"mp3"  =>"audio/mpeg",
									"wav"  =>"audio/x-wav",
									"mpeg" =>"mpg",
									"mpe"  =>"video/mpeg",
									"mov"  =>"video/quicktime",
									"avi"  =>"video/x-msvideo",				
								);
		public function __construct($file_path="",$http_ranges="",$speed=""){
			$this->file_path = $file_path;
			$this->file_name = basename($file_path);
			$this->file_suffix = substr(strrchr($this->file_name, '.'),1);
			$this->file_size= filesize($file_path);
			$this->http_range = (isset($http_ranges) && !empty($http_ranges)!="")?$http_ranges:false;
			!empty($speed)&&($this->speed = $speed);  
		}
		public function down(){
			if(!file_exists($this->file_path)){
				$this->err = "该文件被移除";
				return false;
			}
			$file_path = $this->file_path;
			$content_type = $this->getMIME($this->file_suffix);
			$http_range = $this->http_range;
			if($http_range){
				//$http_range 的形式为 bytes=3145728-  差不多就是这个样子的
				$http_range = preg_replace('/[\s|,]*/', '', $http_range);
				$arr = explode('-',substr($http_range,6));
				$this->range_start = ( isset($arr[0]) && !empty($arr[0]) ) ? $arr[0] : 0;
				$this->range_end   = ( isset($arr[1]) && !empty($arr[1]) && $arr[1]!=0 ) ? $arr[1] : $this->file_size-1;
			}

			$this->setHeader();
			
			$fh =  fopen($file_path, "rb");			
			fseek($fh, $this->range_start);
			
			$speed = $this->speed;
			while(!feof($fh))
			{
				echo fread($fh, 1024*$speed);
				ob_flush();
				sleep(1);
			}
			fclose($fh);
		}
		public function getMIME($key=""){
			if($key=="" || !isset(self::$MIME_type[$key])){
				return "application/octet-stream";
			}
			return self::$MIME_type[$key];
		}
		public function setHeader(){
			header('Cache-control: public');
			header('Content-Type: application/octet-stream');
			header('Content-Disposition: attachment; filename='.$this->file_name);						
			if($this->http_range){
				$range_start = $this->range_start;
				$range_end = $this->range_end;
				header('HTTP/1.1 206 Partial Content');
				header('Accept-Ranges: bytes');
				header( sprintf('Content-Length: %u',$range_end - $range_start) );
				header( sprintf('Content-Range: bytes %s-%s/%s', $range_start, $range_end, $this->file_size) );
			}else{
				header("HTTP/1.1 200 OK");
				header(sprintf('Content-Length: %s',$this->file_size));
			}
		}
	}
	//$http_range = isset($_SERVER['HTTP_RANGE'])?$_SERVER['HTTP_RANGE']:false;
	//$x = new downLoad("all/1.exe",$http_range);
	//$x->down();

 


声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn