Heim >Backend-Entwicklung >PHP-Tutorial >php 实现文件下载 断点续传 原理

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

WBOY
WBOYOriginal
2016-06-20 13:05:201182Durchsuche

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();

 


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn