31 | 时代之风(下):HTTP/2内核剖析
连接前言
连接前言 connection preface
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
在wireshark里称为magic,是一个纯HTTP/1消息,意思是以后的消息切换到HTTP/2
本来h2在ALPN或h2c的connection/upgrade就已经确认了使用h2,发送magic似乎有点多余。
实际的magic消息包括3个流,其中Magic是一个标准HTTP/1消息
settings,windows_update是就HTTP流参数的协商
头部压缩
HTTP/2的消息结构还是HEADER+BODY,只不过细节有了变化,首先去掉了HTTP/1中的起始行,所有头字段统一用键值对表示,原来起始行的请求方法,URI,状态码等,转化为:key: value
的形式,称为伪头字段。
伪头字段共有五个:authority,method,scheme,path,status
HTTP/2定义了2张表,静态表和动态表用来记录头字段。静态表和动态表结构一样,是键值对的索引。区别是静态表保存最常用字段,且是只读的,动态表可以动态更新。如此通过索引映射的方式,达到数据压缩的目的。
注:HTTP/2头字段要求小写
frame结构
frame包括帧头和payload 2个部分,帧头9个字节,其中length 3个字节表示payload的长度,缺省216,最多224,即帧缺省最大16k,理论最大16M。type 1个字节表示帧类型,HTTP/2定义了10种帧类型,基本可以分为数据帧和控制帧,HEADERS和DATA都是数据帧。flag 1个字节表示标记位,不同类型的帧,flag含义不同。如HEADERS(type1)定义了表示帧头结束,END_HEADERS,表示单向流结束 END_STREAM。而DATA(type0)的flag只有END_STREAM标志位。
Stream ID 4个字节表示流编号,第一位保留,即流编号最多2^31位,通常客户端发起的流编号为奇数,服务端发起的流编号为偶数。流是双向的,同一对请求响应消息共用一个流编号,所以一个流对应一次请求应答。
上述帧,红,紫,兰,绿是头字段的Length,Type,Flags,Stream ID,黄色是编码的Header部分,下面wireshark对头字段做了解码
流与多路复用
HTTP/2中,所有消息收发复用到一个TCP请求上,流以frame为载体,用Stream ID作为标记。一个流对应一个发送接收请求。从宏观上看,不同流之间没有次序,但可以有影响。【dependency?】。在TCP层面还是有序的。流内部,帧之间收发是有序的。
流状态转换
流是有状态的,并根据条件,可以在不同状态间切换。
idle,初始状态,流未建立
open,发起headers帧,就创建了stream并处于open状态
half-closed,一方发送请求结束,end_stream置位,则双方进入half-closed状态,一方是local half-closed表示请求发送已结束,等待接收,一方是remote half-closed表示,已接收请求,等待发送。
closed,remote half-closed,发送响应,并置位end_stream,则双方进入closed状态。
新建流不重用stream id,直到耗尽,发起goaway帧,终止TCP连接,重置stream id
中止流:发出一个RST_STREAM的流,type=0x3
参考:HTTP/2学习笔记
课后作业:
HTTP/2 的动态表维护、流状态转换很复杂,你认为 HTTP/2 还是“无状态”的吗?
HTTP/2还是无状态的,动态表维护不过是为HPACK服务,目的是减少收发的数据。
流状态转换不过是一次收发的控制,和HTTP/2是否有状态没有关系。
HTTP/2并没有引入机制记录会话状态。
HTTP/2 的帧最大可以达到 16M,你觉得大帧好还是小帧好?
看应用场景,如果网络质量好,则帧大好,反之小帧好。
结合这两讲,谈谈 HTTP/2 是如何解决“队头阻塞”问题的。
通过虚拟流和帧解决HTTP层面的队头阻塞。一个虚拟流相当于一次HTTP请求应答,多个流可以并发,相互不受影响,故而没有队头阻塞问题。