HTTP1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启 Connection: keep-alive,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。
参考链接:HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
【HTTP队头阻塞问题】HTTP 传输是基于请求-应答的模式进行的,报文必须是一发一收,里面的任务被放在一个【任务队列】中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。
但这并没有真正从 HTTP 本身的层面解决问题,只是增加了 TCP 连接,分摊风险而已。而且这么做也有弊端,多条 TCP 连接会竞争有限的带宽,让真正优先级高的请求不能优先处理。
google.com
域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了,事实上也更好地解决了队头阻塞的问题。2.0 主要在性能方面,HTTPS 在安全方面已经做的非常好了
在 HTTP/1.1 及之前的时代,请求体一般会有响应的压缩编码过程,通过 Content-Encoding
头部字段来指定。那头部字段本身的压缩呢?
HTTP1.x 的 header 带有大量信息,而且每次都要重复发送(当请求字段非常复杂的时候,尤其对于 GET 请求,请求报文几乎全是请求头),HTTP/2 针对头部字段,也采用了对应的压缩算法—— HPACK,对请求头进行压缩。
HPACK 算法是专门为 HTTP/2 服务的,它主要的亮点有两个:
首先是在服务器和客户端之间建立【哈希表】,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如0,1,2,...)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。
小贴士
HTTP/2 当中废除了起始行的概念,将起始行中的请求方法、URI、状态码转换成了头字段,不过这些字段都有一个":"前缀,用来和其它请求头区分开。
其次是对于【整数和字符串进行哈夫曼编码】,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。
上面提到 HTTP1.1 优化对头阻塞问题的解决方式,知道这并没有从 HTTP 应用层层面解决,而是借助 TCP 传输层的能力。那么 HTTP2.0 便从 HTTP 协议本身解决了队头阻塞问题。
新的二进制格式(Binary Format)【二进制分帧】
HTTP1.x 协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。(文本的表现形式有多样性,文本、图片、视频等任意数据)
HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输01串,方便了机器的解析。
原来 Headers + Body
的报文格式如今被拆分成了一个个二进制的帧,用 Headers
帧存放头部字段,Data
帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。
通信双方都可以给对方发送二进制帧,这种 二进制帧的双向传输的序列,也叫做 流 (Stream) 。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是【多路复用】的概念。
既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?
首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。
HTTP/2 中传输的帧结构如下图所示:
每个帧分为帧头和帧体。先是三个字节的帧长度,这个长度表示的是帧体的长度。
然后是帧类型,大概可以分为数据帧和控制帧两种。
数据帧用来存放 HTTP 报文,控制帧用来管理流的传输。【(比如使用 PRIORITY 帧更改流的优先级。)】
接下来的一个字节是帧标志,里面一共有 8 个标志位,常用的有 END_HEADERS
表示头数据结束,END_STREAM
表示单方向数据发送结束。
后 4 个字节是 Stream ID
, 也就是流标识符,有了它,接收方就能从乱序的二进制帧中选择出 ID 相同的帧,按顺序组装成请求/响应报文。
以一个普通的请求-响应过程为例来说明:
最开始两者都是空闲状态,当客户端发送 Headers 帧后,开始分配 Stream ID
, 此时客户端的流打开,服务端接收之后服务端的流也打开,两端的流都打开之后,就可以互相传递数据帧和控制帧了。
当客户端要关闭时,向服务端发送 END_STREAM
帧,进入半关闭状态, 这个时候客户端只能接收数据,而不能发送数据。
服务端收到这个 END_STREAM
帧后也进入半关闭状态,不过此时服务端的情况是只能发送数据,而不能接收数据。随后服务端也向客户端发送 END_STREAM
帧,表示数据发送完毕,双方进入关闭状态。
如果下次要开启新的流,流 ID 需要自增,直到上限为止,到达上限后开一个新的 TCP 连接重头开始计数。由于流 ID 字段长度为 4 个字节,最高位又被保留,因此范围是 0 ~ 2的 31 次方,大约 21 亿个。
流传输的特性:
在 HTTP/2 当中,服务器已经不再是完全被动地接收请求,响应请求,它也能新建 stream 来给客户端发送消息,当 TCP 连接建立之后,比如浏览器请求一个 HTML 文件,服务器就可以在返回 HTML 的基础上,将 HTML 中引用到的其他资源文件一起返回给客户端,减少客户端的等待。
HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL 和 TLS 协议代替而已。
SSL 即 安全套接层(Secure Sockets Layer),在 OSI 七层模型中处于会话层(第 5 层)。
之前 SSL 出过三个大版本,当它发展到第三个大版本的时候才被标准化,成为 TLS(传输层安全,Transport Layer Security),现在主流的版本是 TLS/1.2。
HTTPS协议,它比HTTP协议相比多了以下优势:
所谓HTTPS,其实就是身披SSL协议这层外壳的HTTP。
在采用SSL后,HTTP就拥有了HTTPS的加密、证书和完整性保护这些功能。
TLS/SSL 的功能实现主要依赖于三类基本算法:
在交换密钥环节使用非对称加密方式,之后的建立通信交换报文阶段则使用对称加密方式。
具体做法是:
发送密文的一方使用对方的公钥进行加密处理“对称的密钥”,然后对方用自己的私钥解密拿到“对称的密钥”,这样可以确保交换的密钥是安全的前提下,使用对称加密方式进行通信。所以,HTTPS采用对称加密和非对称加密两者并用的混合加密机制。
网络传输过程中需要经过很多中间节点,虽然数据无法被解密,但可能被篡改,那如何校验数据的完整性呢?----校验数字签名。
数字签名有两种功效:
能确定消息确实是由发送方签名并发出来的,因为别人假冒不了发送方的签名。
数字签名能确定消息的完整性,证明数据是否未被篡改过。
数字签名如何生成:
发送方:将一段文本先 用 Hash 函数生成消息摘要,然后 用发送者的私钥加密生成数字签名,与原文一起传送给接收者。
校验数字签名流程:
接收方:接收者只有 用发送者的公钥才能解密被加密的摘要信息,然后 用HASH函数对收到的原文产生一个摘要信息,与上一步得到的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。
证书颁发机构(Certificate Authority,简称CA)
数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的立场上。
数字证书认证机构的业务流程:
参考链接:深入理解HTTPS工作原理
最后: