1、简介
这一篇文章主要介绍的是http协议下载时的断点续传,详细到各个步骤。主要步骤有:DNS查找、TCP三次握手、http请求发送、TCP协议数据传输、暂停后的状态、继续下载、TCP三次握手、http请求发送、数据传输、。。。、下载成功发送http响应信息、TCP四次握手断开连接。
2、原理知识
2.1、问答问答
问:什么是断点续传?断点续传的原理是什么?
答:断点续传就是信号中断后(掉线或关机等),下次能够从上次的地方接着传送(一般指下载或上传),不支持断点续传就意味着下次下载或上传必须从零开始。http协议中的断点续传是基于Http头Range以及Content-Range。HTTP头中一般断点下载时才用到Range和Content-Range实体头,Range用户请求头中,指定第一个字节的位置和最后一个字节的位置,如( Range:200-300或者Range:200- );Content-Range用于响应头。通俗的来讲就是文件大小为10,这次下载了3,被中断了,下次继续下载时则将指针移到3位置,从3开始下载,最终将整个文件下载下来。
2.2、简单http下载文件
请求下载整个文件:
GET /test.rar HTTP/1.1
Connection: close
Host: 192.168.95.11
Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头
一般正常回应 :
HTTP/1.1 200 OK
Content-Length: 801
Content-Type: application/octet-stream
Content-Range: bytes 0-800/801 //801:文件总大小
2.3、重要的几个头
响应头:
Content-type:Content-type 告诉浏览器文件的MIME 类型,这是非常重要的一个响应头了,MIME种类繁多。很可能会在程序中漏掉一些MIME类型,表示全部为 content-type:application/octet-stream(字节流)
Content-Disposition:是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。 嗯,就是这个头哟,激活弹出提示下载框,一般这样写content-disposition:attachment; filename=name
Content-Length:" Content-Length: 321" 就是告诉浏览器这个文件的大小是321字节,其实我发现好像不设置这个头,浏览器也能自己识别
Pragma Cache-control:把这2个头都设置成public 告诉浏览器缓存,我一般设置 cache-control:public
Content-Range:字段说明服务器返回了文件的某个范围及文件的总长度。这时Content-Length字段就不是整个文件的大小了,而是对应文件这个范围的字节数,这一点一定要注意。一般格式,Content-Range: bytes 500-999/1000
响应头:
Range:可以请求实体的一个或者多个子范围。
例如:
表示头500个字节:bytes=0-499
表示第二个500字节:bytes=500-999
表示最后500个字节:bytes=-500
表示500字节以后的范围:bytes=500- 【下载断点续传(一般range格式为500-)】
第一个和最后一个字节:bytes=0-0,-1
同时指定几个范围:bytes=500-600,601-999
但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是以200(OK)。【206表示服务器已经完成get的部分请求,即表示断点续传】
3、PHP断点续传类
1 <?PHP
2 #文件下载(支持断点续传)
3 class FileDownload
4 {
5 #下载速度
6 private $_speed = 512;
7
8 /**
9 * @desc 下载文件
10 *
11 * @param $file string 下载的文件路径
12 * @param $name string 保存文件时的文件名,不写则最终下载文件默认为原文件名
13 * @param $reload bool 是否使用断点续传方式下载
14 */
15 public function download($file, $name='', $reload=false)
16 {
17 if(file_exists($file)) #判断文件是否存在
18 {
19 if($name == '') #判断命名参数是否存在
20 {
21 $name = basename($file); #采用原文件名进行存储
22 }
23 $fHandle = fopen($file, 'rb'); #只读方式打开;为移植性考虑,使用b标记打开文件(不同系统有不同换行符)
24 $fileSize = filesize($file); #文件大小
25 $ranges = $this->getRange($fileSize); #断点续传时,先查看下载的区间范围
26 header('cache-control:public'); #可以被任何缓存所缓存
27 header('content-type:application/octet-stream'); #告诉浏览器响应的对象的类型(字节流、浏览器默认使用下载方式处理)
28 header('content-disposition:attachment; filename='.$name); #不打开此文件,刺激浏览器弹出下载窗口
29 #判断是否使用续传方式进行下载
30 #且请求头ranges不能为null(为null表示第一次请求下载)
31 if($reload && $ranges!=null)
32 {
33 header('HTTP/1.1 206 Partial Content'); #发送自定义报文 206续传状态码
34 header('Accept-Ranges:bytes'); #表明服务器支持Range请求,所支持的单位是字节
35 # 剩余长度
36 header(sprintf('content-length:%u',$ranges['end']-$ranges['start']));
37 # range信息
38 header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end'], $fileSize));
39 # fHandle指针跳到断点位置
40 fseek($fHandle, sprintf('%u', $ranges['start']));
41 }
42 else
43 {
44 header('HTTP/1.1 200 OK');
45 header('content-length:'.$fileSize);
46 }
47 while(!feof($fHandle))
48 {
49 echo fread($fHandle, round($this->_speed*1024,0));
50 ob_flush(); #把数据从PHP的缓冲中释放出来
51 //sleep(2); // 用于测试,减慢下载速度
52 }
53 ($fHandle!=null) && fclose($fHandle);
54 }
55 else
56 {
57 #没文件
58 header("HTTP/1.1 404 Not Found");
59 return false;
60 }
61 }
62
63 /**
64 * @desc 获取请求头部range信息
65 *
66 * @param $fileSize int 该文件的大小
67 *
68 * @return array|null 返回range信息或者null
69 */
70 public function getRange($fileSize)
71 {
72 if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE']))
73 {
74 #请求头部range信息 Range: bytes=41078-\r\n
75 $range = $_SERVER['HTTP_RANGE'];
76 $range = preg_replace('/[\s|,].*/', '', $range);
77 $range = explode('-', substr($range, 6)); #只需将41078-进行分割变成数组
78 #断点续传头部range信息都是为 4444- 这种形式 ,因此切割后形成的数组就只有两个元素
79 $range = array_combine(array('start','end'), $range);
80 if(empty($range['start']))
81 {
82 $range['start'] = 0;
83 }
84 if(empty($range['end']))
85 {
86 $range['end'] = $fileSize;
87 }
88 return $range;
89 }
90 return null; #第一次请求没有range信息
91 }
92
93 /**
94 * @desc 设置文件下载速度
95 *
96 * @param $speed int 下载速度
97 */
98 public function setSpeed($speed)
99 {
100 if(is_numeric($speed) && $speed>16 && $speed<4096)
101 {
102 $this->_speed = $speed;
103 }
104 }
105
106 }
107
108 ?>
4、抓包分析