http2究竟何方神圣?大白话总结

http1.1的弱点

http2的优点

1. 不再像http1那样用字符串的方式来传送数据,而是直接用二进制的方式打包成帧

这句话很多书或者博客都是这样写,其实根本就没有点透,比如你说http1.1是用字符串的方式传递数据,那么就算是字符串,真正到了底层,通过网卡、通过以太网水晶头网线,通过wifi射频信号,那特么不还是高低电平二进制嘛,怎么就说http2是二进制方式打包呢?

本质上是这样的,字符串确实在http1.1中也是二进制发送出去,但是呢,要看是怎么样的编码方式,举个最简单的例子,比如要传送1567.234,如果是ascii编码或者gbk、utf8,要占用8个字节,但是如果是二进制编码,直接按float32写进去,只要4个字节,那很明显,二进制编码方式要比直接字符串方式要节约内存和带宽。

继续说,http1的Header我们知道内容是挺多的,按照字符串方式编码的话,服务器端如何解析呢,服务器端是靠从字节流(一般到了api编程层面,就是字节数组)中读取一行来逐行解析的,也就是说读取字节流中的'\n',从而来读取一行又一行的字符串,然后解析成Header对象(一般在服务端就是一个map,因为本身http请求中的header就是keyvalue的形式)

而换成http2的二进制报文的方式的话,就不是这样干的了,有过写tcp服务端的程序开发经验的朋友都知道,如果是在tcp上裸奔双发收发数据的话,是要约定数据报文的格式的,包括数据报文头字节数、总字节数等,一般都是消息头固定和消息体变长。H2的消息头固定是9个字节,9个字节如何组成:3字节Length、1个字节Type、1个字节Flags、1位R、31位帧ID,加起来正好9个字节,剩下来的就是Frame Payload(这个就是真正的帧内容,那一共多少字节呢,就是最前面3个字节Length指定的,那3个字节最大值多少?2^24-1个字节)

这里写图片描述

所以http2规范就约定了各种帧报文格式,然后客户端和服务端收发数据的时候,都是直接打包成字节数组,按照约定的格式往字节数组里面append,然后一次性把这个字节数组通过tcp发出去的,所以就要知道http2的帧格式,才能知道底层是如何约定帧格式的。

http1.x的首部信息被封装成HEADER帧和CONTINUATION帧,请求体被封装到DATA帧。

H2约定的帧类型

DATA、HEADERS、PRIORITY、RST_STREAM、SETTINGS、PUSH_PROMISE、PING、GOAWAY、WINDOW_UPDATE、CONTINUATION,分别取值0x0~0x9

2. tcp连接多路复用

这玩意其实也没啥,但是要注意和http1.1区别开来,http1.1里面也是通过keep-alive让tcp连接不要关闭的,否则的话,每次重新建立tcp连接,速度就慢下来了。http1.1里面浏览器不是搞出个,针对每个域名,同时支持最多6个tcp连接嘛

假设一个网页先把html down下来,然后浏览器解析一下html,发现里面还有n多图片、css、js等额外资源需要下载,大家都知道现在一个html网页,你去翻它的html源码,会发现还有n多个其他文件资源需要下载的,假设一个网页有60个额外的资源需要下载(图片、css、js等),那么就算是6个tcp连接,每个连接上分到10个文件的下载任务,每个连接上都要串行下载10个文件。

为啥说是每个tcp连接上是串行,因为http1.1规范和源码实现中是这样搞的,每个tcp连接发送1个文件请求后,就要等待(也就是说cpu空转),等到这个请求的响应回来后才能继续发送读取第2个文件的请求,比如在tcp-1连接上下载10个图片是这样搞的:

req-image-1
resp-image-1
req-image-2
resp-image2
......
req-image-10
resp-image-10

必须串行发送,这特么就很搞了,效率低下,因为我们知道tcp是双流,双车道,req-image-1请求出去后,要等响应回来本身就需要一些时间,再这段时间内,其实完全可以继续把req-image2请求先发送出去再说。

再看H2是怎么下载图片的 

 这就是http1.1在tcp层面发送数据接收数据比较低效的原因,在单个TCP链接层面是串行收发的,并没有实现多路复用(所谓的多路复用,就是在同一个链接上收发多个资源文件,就好像我们的骨干网,运营商架设的城域网,2个城市之间的人收发微信消息,都是同一个物理链路,但是运营商的设备之间是时分复用),所以h2对此做了优化,允许在响应没有回来之前,管他妈的,先把第2个请求发出去再说,这就提高了一些效率了,因为h2是基于帧来发送数据包的,我们知道既然是帧,只要是设计过基于tcp的socket程序的人都知道,我们一定会在请求报文和响应报文中搞一个全局唯一的请求id或者叫msgId,这样的话,才能把请求和响应的数据帧一一对应起来。

因为tcp是面向流的协议,流是源源不断的字节流,没有界限,抽刀断水水更流,如何从字节流中把一个帧给读取出来,就靠帧格式的约定了(固定头+变长body)。

然后呢,服务端给客户端发响应的时候,也一定会把req的msgId同样的给传回去,这样的话,客户端发出去10个req,回来10个resp,它就能把resp和req一一对应上了。就好像我写modbus-tcp代码一样的,假设要读取传感器的2个寄存器,连续发2个modbus-tcp请求报文过去,这2个modbus-tcp报文中是有msgId的,然后设备端回来modbus-tcp响应报文的时候,它会把这个msgId原样返回,这样的话,接受端才能把响应和请求一一对应

比如发3个modbus-tcp报文读取设备的电压、电流、温度,3个req报文出去(带上3个msgId),回来3个resp报文,必须从resp中读取msgId,然后和req匹配,这样的话,上层应用层代码才能准确地知道当前电压多少、电流多大、温度多少,否则的话,就无法匹配,会造成乱七八糟,比如把温度数据当成电压,这不是乱套了嘛!

而且,h2可以在一个tcp连接中持续发帧数据包,这样浏览器就不需要和服务端建立所谓的同一个域名下最大6个tcp连接,这就可以节约服务器端打开的tcp连接数了。

H2提倡,在浏览器和服务端之间只要1个TCP连接,用单个链接承载页面涉及的所有资源请求,能享受多路复用带来的好处。

h2还有个首部压缩的功能

浏览器按f12,看到请求和响应,就可以看到一堆的header,还有cookie,太占用内存和带宽了,其实你想啊,浏览器访问同一个网页,网页里面的那些图片、css、js都要单独下载,但是单独发请求去读取这些图片、css、js的时候,根本就没必要重复传输那些header呀,特别是一些网站用的cookie还是比较多的,每次传来传去cookie占了好多带宽。

所以h2就搞出了个首部压缩的功能,对header中的数据进行压缩,称之为HPACK,这个网上资料很多,也是很形式化的东西,核心就是用一些关键字表替换http 头部中的一些固定的字符串。

就好像打仗的时候的密码表一样,比如2个人约定:

今天晚上吃面条        用数字1A表示
明天去爬山                用数字2B表示

那么2个人发数据的时候,我就不需要用微信给你发:明天去爬山,我只要给你发一个2B就行,你收到以后,去一个约定好的表中查询,一查发现2B对应的是”明天去爬山“,那么你也就知道了。通过这种方式,节约了带宽。HPACK大致是这么个意思。

深入理解H2其实也没有太大必要,除非你是协议栈的开发者

要深入理解,有一些测试工具可以帮助我们理解,nghttp2 curl -v --http2  wireshark抓包

http3

谷歌这帮家伙又开始搞出个QUIC协议了,基于UDP来传送HTTP,这称之为HTTP3,连TCP都看不上了。TCP的缺点:慢启动、拥塞控制、不合理的丢包处理机制,TCP还有队首阻塞问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值