Grape
全部视频:https://segmentfault.com/a/11…
http 的定义
HTTP 是基于客户端 / 服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求 / 响应协议。
http 的结构
我们目前用到最多的是 http1.x 协议,header 和 body 我们都不陌生,那么 startline 是什么呢?startline 是我们所说的 request_line 或 status_line,也就是
GET /HTTP/1.1 或者 HTTP/1.1 200 OK 这种字段。
在叙述 http 的各种工作方式之前,我们先熟悉一下 TCP/IP 模型:
http 的发展:
http1.0
Http1.0:http1.0 是默认没有 keep-alive 的,在数据请求的时候会先进性应用层以下的处理过程才会到应用层,在这里我们只说传输层和应用层,在 http1.0 中,每一次的请求都会进行建立 tcp 连接(三次握手),然后进行 http 请求,这样每次与服务器交互,都需要新开一个连接!我们的每个链接的请求链路都如下图所示:
想象一下,每发起一个请求就经过上述如此长的一个流程,这将耗去多少资源。
http1.1
基于这个问题,我们的 http 迎来了 http1.1 版本,1.1 版本相对于 1.0 版本有什么改动呢?
- http 增加了 host 字段
- HTTP 1.1 中引入了 Chunked transfer-coding,范围请求,实现断点续传(实际上就是利用 HTTP 消息头使用分块传输编码,将实体主体分块传输)
-
HTTP 1.1 管线化 (pipelining) 理论,客户端可以同时发出多个 HTTP 请求,而不用一个个等待响应之后再请求
- 注意:这个 pipelining 仅仅是限于理论场景下,大部分桌面浏览器仍然会选择默认关闭 HTTP pipelining!
- 所以现在使用 HTTP1.1 协议的应用,都是有可能会开多个 TCP 连接的!
Http1.1 基于上述耗费资源的问题给予了根本的处理,默认长链接,什么意思呢?不去在每一个 http 请求的时候都去进行 http 连接,只建立一个 tcp 链接去处理多个请求,当然,这里的每个请求是串行的,即只是不用去进行 tcp 连接,还是得排队,并且这样子可能会引起线头阻塞(例如发送 100 个请求,第一个被阻塞导致后边 99 个也不能请求)问题。对于 http1.1 默认的工作模式如下图所示:
到这我们想象一下这种模式好么,有什么缺点?可以优化吗?
在此之上我们已经抛出了两个问题 1. 需要排队 2. 可能引起线头阻塞。对于第一个问题,http1.1 已经给出了解决方案,即 pipline,而第二个问题刚开始有一种过渡的方案,即 spdy 协议(google 推出的一项增强 http 的协议,功能包括数据流的多路复用、请求优先级以及 HTTP 报头压缩,有兴趣的可以研究一下),然后再到现在的 http2.0。
首先我们先说一下什么是 pipline,pipline 是一项实现了多个 http 请求但不需要等待响应就能够写进同一个 socket 的技术,仅有 http1.1 规范支持 http 管线化,1.0 并不支持。什么意思呢?我们看上图是在一个 tcp 连接的串行的去处理,那么开启了 pipline 之后就会变成下边这个样子:
我们可以看到发送 http 请求不再是先发送然后等待 response 再发送下个请求了,这样子我们可以看成是所有的请求统一开始,但是这有一个问题,HTTP Pipelining 其实是把多个 HTTP 请求放到一个 TCP 连接中一一发送,而在发送过程中不需要等待服务器对前一个请求的响应;只不过,客户端还是要按照发送请求的顺序来接收响应!这就导致了它虽然解决了排队问题,但是他也仅仅是解决了单方排队的问题,最后接受数据还是按照请求的顺序来接受相应,为什么呢?因为他们不知道哪个是第一个哪个是第二个。这样同样会存在线头阻塞的问题。
总结一下就是在 http1.0 的时候我们是流水线,一个接一个的完成任务,http1.1 的时候呢我们工人的能力提升了,可以一次发出多个工作需求了,但是还没有掌握技巧,还是得按照条例等待工作全部到来的时候一个一个按顺序处理。
http2.0
接下来就是我们的 http2.0,看他如何解决了之前的问题。解决线头阻塞,在 http2.0 中其实是用了一个 stream 的结构,给每一个 stream 分配一个标示即 streamId 就可以来解决线头阻塞的问题。那么 http2 究竟是何方神圣呢?
首先,说起 http2,我们不得不提一下 https,http2 是基于 https 的一个协议,对于 https 我找了一篇写的比较好的文章,Wireshark 抓包理解 HTTPS 请求流程。
文章开头我们对比了 http1 和 http2 的结构,看起来好像完全不一样,但是实际上并非如此,http2 以帧作为最小单位,看了下边的图我们会发现原来 http2 只是做了层封装,其实本质还是 headers 和 body,只不过 http2 是以更高级功能更多的方式进行了展示。
http1.x vs http2.0
关于 http2 好在哪里,那我们得从 http1 坏出发,因为有对比才会有伤害。
- http1 连接数限制,对于同一个域名,浏览器最多只能同时创建 6~8 个 TCP 连接 (不同浏览器不一样)。为了解决数量限制,出现了 域名分片 技术,其实就是资源分域,将资源放在不同域名下 (比如二级子域名下),这样就可以针对不同域名创建连接并请求,以一种讨巧的方式突破限制,但是滥用此技术也会造成很多问题,比如每个 TCP 连接本身需要经过 DNS 查询、三步握手、慢启动等,还占用额外的 CPU 和内存,对于服务器来说过多连接也容易造成网络拥挤、交通阻塞等。那么,http2 是怎么做的呢?http2 采用了多路复用技术,在一个 TCP 连接上,我们可以向对方不断发送帧,每帧的 stream_id 标明这一帧属于哪个流,然后在对方接收时,根据 stream_id 拼接每个流的所有帧组成一整块数据。把 HTTP/1.1 每个请求都当作一个流,那么多个请求变成多个流,请求响应数据分成多个帧,不同流中的帧交错地发送给对方,这就是 HTTP/2 中的多路复用。同时呢,我们知道 http1 的 body 长度是在 header 带过来的,那么如果是以 http2 的形式去传输肯定会出问题,所以 http2 将 body 上架了 length 字段,每一个流都有自己的长度,最后根据流的头部长度是否等于各个流的长度来确定是否合包。同时呢,这样分包合包也解决了线头阻塞的问题。那么问题又来了,怎么确定没有丢包?同一个 stream 秩序有没有乱?这点 tcp 会保证包的有序性且保证了包不会丢失。
- Header 内容多,而且每次请求 Header 不会变化太多,没有相应的压缩传输优化方案。http2 在此用 hpack 算法来压缩首部长度,其原理就是维护一个静态索引表和动态索引表的索引空间,hpack 其原理就是匹配当前连接存在的索引空间,若某个键值已存在,则用相应的索引代替首部条目,比如“:method: GET”可以匹配到静态索引中的 index 2,传输时只需要传输一个包含 2 的字节即可;若索引空间中不存在,则用字符编码传输,字符编码可以选择哈夫曼编码,然后分情况判断是否需要存入动态索引表中,以这种形式节省了很多的空间。
- 明文传输不安全。http1 使用明文传输,不安全。那么 http2 就用二进制分帧层来解决这个问题,帧是数据传输的最小单位,以二进制传输代替原本的明文传输,原本的报文消息被划分为更小的数据帧。
- 为了尽可能减少请求数,需要做合并文件、雪碧图、资源内联等优化工作,但是这无疑造成了单个请求内容变大延迟变高的问题,且内嵌的资源不能有效地使用缓存机制。对于这种情况,http2 推出了服务端推送,浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源,这样浏览器就不用发起后续请求,其主要是针对资源内联做出的优化。
- 应用层的重置连接,对于 HTTP/1 来说,是通过设置 tcp segment 里的 reset flag 来通知对端关闭连接的。这种方式会直接断开连接,下次再发请求就必须重新建立连接。HTTP/2 引入 RST_STREAM 类型的 frame,可以在不断开连接的前提下取消某个 request 的 stream,表现更好。
扩展
关于 http 我们就说这么多,如果想了解的更多读者可以自行用 wireshark 抓包看一下。推荐两个比较好的抓包工具:wireshark 和 charles。
参考文章:
- http 协议浅析(二)
- HTTP2 详解