上次我们谈到了HTTP报文里的div,知道了HTTP可以传输很多种类的数据,不仅是文本,也能传输图片,音频和视频。
 
早期互联网上传输的基本上都是只有几k大小的文本和小图片,现在的情况则大有不同。网页里包含的信息实在太多了,随随便便一个主页HTML就有可能上百K,高质量的图片都以M论,更不要说那些电影,电视剧了,几G,几十G都有可能。
 
相比之下,100M的光纤固网或者4G移动网络在这些大文件的压力下都变成了小水管,无论是上传还是下载,都会把网络传输链路挤的满满当当。
 
所以如何在有限的带宽下高效快捷的传输这些大文件就成了一个重要的课题,这就好比是已经打开了冰箱门,该怎么把大象塞进去在关上门呢?
 
下面我们就一起看看HTTP协议里有哪些手段能解决这个问题。
 
数据压缩
 
通常浏览器在发送请求时都会带着Accept-Encoding头字段,里面是浏览器支持的压缩格式,例如gzip,deflate等,这样服务器就可以从中选择一种压缩算法,放进Content-Encoding响应头里,在把原数据压缩后发给浏览器。
 
如果压缩率能有50%,也就是说100k的数据能够压缩成50k的大小,那么就相当于在带宽不变的情况下网速提升了一倍,加速的效果是非常明显的。
 
不过这个解决方法也有个缺点,gzip等压缩算法通常只对文本文件有较好的压缩率,而图片,音频视频等多媒体数据本身已经是高度压缩的,在用gzip处理也不会变小,所以它就失效了。
 
不过数据压缩在处理文本的时候效果还是很好的,所以各大网站的服务器都会使用这个手段作为保底,例如,在nginx里就会使用gzip on指令,启用对text/html的压缩。
 
分块传输
 
在数据压缩之外,还能有什么办法来解决大文件的问题呢?
 
压缩是把大文件整体变小,我们可以反过来思考,如果大文件整体不能变小,那就把它拆开,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后在组装复原。
 
这样浏览器和服务器都不用在内存里保存文件的全部,每次只收发一小部分,网络也不会被大文件长时间占用,内存,带宽等资源也就节省下来了。
 
这种化整为零的思路在HTTP协议里就是chunked分块传输编码,在响应报文里用头字段Transfer-Encoding:chunked来表示,意思是报文里的div部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。
 
分块传输也可以用于流式数据,例如有数据库动态生成的表单页面,这种情况下div数据的长度是未知的,无法在头字段Content-Length给出确切的长度,所以也只能用chunked方式分块发送。
 
Transfer-Encoding:chunked和Content-Length这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文里的传输要么是长度已知,要么是长度未知。
 
下面是分块传输的编码规则:
1.每个分块包含两个部分,长度头和数据块
2.长度头是以CRLF结尾的一行明文,用16进制数字表示长度
3.数据块紧跟在长度头后,最后也用CRLF结尾,但数据不包含CRLF
4.最后用一个长度为0的块表示结束
 
 
 
 
 
 
 
 
 
范围请求
 
有了分块传输编码,服务器就可以轻松收发大文件了,但对于上G的超大文件,还有一些问题需要考虑。
 
比如,你在看当下正热播的电视剧,想跳过片头,直接看正片,或者有段剧情无聊,想拖动进度条快进几分钟,这实际上是想获取一个大文件其中的片段数据,而分块传输并没有这个能力。
 
HTTP协议为了满足这样的需求,提出了范围请求的概念,允许客户端在请求头里使用专用字段来表示只获取文件的一部分,相当于是客户端的化整为零。
 
范围请求不是web服务器必备的功能,可以实现也可以不实现,所以服务器必须在响应头里使用字段Accept-Ranges:bytes明确告知客户端:我是支持范围请求的。
 
请求头Range是HTTP范围请求的专用字段,格式是"bytes=x-y",其中的x和y是以字节为单位的数据范围。
 
要注意x,y表示的是偏移量,范围必须从0计数,例如前10个字节表示为0-9,第二个10字节表示为10-19,而0-10实际上是前11个字节。
 
Range的格式也很灵活,起点x和终点y可以不要,能够很方便的表示正数或者倒数的范围。假设文件是100个字节,那么:
"0-"表示从文档起点到文档终点,相当于"0-99",即整个文件
“10-”是从第10个字节开始到文档末尾,相当于"10-99"
"-1"是文档的最后一个字节,相当于"99-99"
"-10"是从文档末尾倒数10个字节,相当于"90-99"
 
服务器收到Range字段后,需要做四件事
 
第一,它必须检查范围是否合法,比如文件只有100个字节,但是请求"200-300",这就是范围越界了,服务器就会返回状态码416,意思是你的范围请求有误,我无法处理,请在检查一下
 
第二,如果范围正确,服务器就可以根据Range头计算偏移量,读取文件的片段了,返回状态码206 partial content,和200的意思差不多,但表示div只是原数据的一部分
 
第三,服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小,格式是bytes x-y/length,与Range头区别在没有“=”,范围后多了总长度
 
最后剩下的就是发送数据了,直接把片段用TCP发送给客户端,一个范围请求就算是处理完了。
 
多段数据
 
刚才说的范围请求一次只获取一个片段,其实它还支持在Range头里使用多个"x-y",一次性获取多个片段数据。
 
这种情况需要使用一种特殊的MIME类型:multipart/byteranges,表示报文的div是由多段字节序列组成的,并且还要用一个参数boundary=xxx给出段之间的分隔标记。
 
 
每一个分段必须以"--boundary"开始,之后要用Content-Type和Content-Range标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,在加上分段数据,最后用一个"--boundary--"表示所有的分段结束。
 

把大象装进冰箱:HTTP传输大文件的方法的更多相关文章

  1. 把大象装进冰箱的N种方法

    作者:折剑头链接:https://www.zhihu.com/question/49214119/answer/115728034来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  2. Linux 下EXT2文件系统 —— 如何将蚂蚁和大象优雅的装进冰箱里

    这一阵子真是偷懒,无时无刻不和自己身体中的懒癌做斗争.最终我还是被打败了,星期天两天几乎都是荒废过去的,在空闲的时候实际上我内心也是有点焦虑的,不知道去怎么度过这时间.学习吧又不想学习,看电视娱乐吧也 ...

  3. 基于RMI服务传输大文件的完整解决方案

    基于RMI服务传输大文件,分为上传和下载两种操作,需要注意的技术点主要有三方面,第一,RMI服务中传输的数据必须是可序列化的.第二,在传输大文件的过程中应该有进度提醒机制,对于大文件传输来说,这点很重 ...

  4. linux传输大文件

    http://dreamway.blog.51cto.com/1281816/1151886 linux传输大文件

  5. C# 的 WCF文章 消息契约(Message Contract)在流(Stream )传输大文件中的应用

    我也遇到同样问题,所以抄下做MARK http://www.cnblogs.com/lmjq/archive/2011/07/19/2110319.html 刚做完一个binding为netTcpBi ...

  6. WCF 用netTcpbinding,basicHttpBinding 传输大文件

    问题:WCF如何传输大文件 方案:主要有几种绑定方式netTcpbinding,basicHttpBinding,wsHttpbinding,设置相关的传输max消息选项,服务端和客户端都要设置,tr ...

  7. 使用QQ传输大文件

    现在在公网上能传输大文件并且稳定支持断点续传的软件非常少了,可以使用qq来做这件事. qq传输单个文件有时候提示不能超过4g有时候提示不能超过60g,没搞明白具体怎么样. 可以使用qq的传输文件夹功能 ...

  8. TCP协议传输大文件读取时候的问题

    TCP协议传输大文件读取时候的问题 大文件传不完的bug 我们在定义的时候定义服务端每次文件读取大小为10240, 客户端每次接受大小为10240 我们想当然的认为客户端每次读取大小就是10240而把 ...

  9. netty接收大文件的方法

    参考:http://blog.csdn.net/linuu/article/details/51371595 https://www.jianshu.com/p/a0a51fd79f62 netty默 ...

随机推荐

  1. Java 访问修饰符详解

    访问修饰符定义了类.属性和方法的访问权限,Java 中包含四种,访问权限从小到大为 private.default.protected 和 public. public,公共修饰符,被其修饰的类.属性 ...

  2. 可视化文件消息收发一体化Socket实现V0.1

    http://blog.csdn.net/laoyang360/article/details/8681918

  3. zyltimer与ZylIdleTimer

    http://www.zylsoft.com/zyltimer.htmhttp://www.zylsoft.com/products.htm

  4. delphi的Socket(有两种分别继承TObject和TComponent的方式)

    在Delphi中,对于Windows中的Socket进行了有效的封装.在Delphi中,按其继承关系,可以分层两类:一.TComponent--TAbstractSocket--TCustomSock ...

  5. 学习Java,容易被你忽略的小细节(2)

    昨天心情真的太糟糕了,写完<学习Java,值得注意你注意的问题(1)>之后,迎来些许的支持以后就是一片片的谴责.我的主页上涌现出许许多多Java方面的牛人,谴责我水平太低,写的问题太初级. ...

  6. python中的基本数据类型之 int bool str

    一.基本数据类型 1. int  ==>  整数.主要用来进行数学运算. 2.str  ==>  字符串.可以保存少量的数据,并进行相应的操作. 3.bool  =>  布尔值.判断 ...

  7. SYN591-C型 时间间隔表

       SYN591-C型 时间间隔表 脉冲计数器数显计数器电机转速表使用说明视频链接: http://www.syn029.com/h-pd-250-0_310_44_-1.html 请将此链接复制到 ...

  8. docker系列(五):网络通信

    1 引言 之前的几篇docker系列博客说的都是单个容器或者镜像的操作.但容器,作为一种简化的操作系统,又怎能不与机器或者容器相互协同工作呢,这就需要用到容器的网络功能.docker中提供了多种不同的 ...

  9. 「中高级前端必须了解的」JS中的内存管理

    前言 像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存. 而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不 ...

  10. vue2.0父子组件以及非父子组件通信传参详解

    1.父组件传递数据给子组件 父组件数据如何传递给子组件呢?可以通过props属性来实现 父组件: <parent> <child :child-msg="msg" ...