Http 总结 from 极客时间 - 趣谈网络协议

HTTP 请求的准备

  • DNS解析IP
  • HTTP基于TCP, 首先建立TCP链接
    • 现在默认是1.1协议, 默认是开启了Keep-Alive, 多次请求中复用

HTTP 请求的构建

请求行

  • Request URL
  • Request Method
  • Version

请求首部

key value, 用冒号分隔.

  • Accapt-Charset表示客户端可以接受的字符集
  • Content-Type正文的格式

HTTP 请求的发送

基于TCP协议的, 使用面向连接的方式发送请求, 通过stream二进制流的方式传给对方.

同一个网段

  • IP层->ARP获取目标地址MAC, 添加MAC头(源和目标), 发送出去

不在同一个网段

  • IP层->ARP获取网关的MAC, 然后发送

HTTP 返回的构建

浏览器作为客户端也在监听某个进程.

HTTP 2.0

  • HTTP的头进行一定的压缩, 将原来每次都要携带的大量key value在两端监理一个索引表, 对相同的头只发送索引表中的索引
  • 将一个TCP链接切分成多个流, 流是有优先级的. 流是双向的, 可以是客户端给服务端, 也可以是服务端给客户端. 一个TCP链接里面
    • Header帧, 传输Header内容
    • Data帧, 传输正文实体

可以将多个请求分到不同的流中, 然后将请求内容拆成帧, 进行二进制传输. 这些帧可以打散乱序发送, 然后根据每个帧首部的流标识符重新组装, 并且可以根据优先级, 决定优先处理哪个流的数据.

  • 左是HTTP 1.1, 串行请求
  • 右是HTTP 2.0, 同时发送多个请求和回应
    • 将三个请求变成三个, 将数据分成帧, 乱序发送到一个TCP连接中

总结

  • 2.0解决了1.1的队首阻塞问题, 同时, 也不需要通过HTTP 1.xpipeline机制用多条TCP链接来实现并行请求与相应
  • 减少了TCP连接数对服务器性能的影响, 将多个数据如css, js, jpg等通过一个数据链接进行传输

QUIC 协议

from google

TCP 协议在处理包时是有严格顺序的. 当其中一个数据包遇到问题, TCP 连接需要等待这个包完成重传. 虽然 HTTP 2.0 通过多个 stream, 使得逻辑上一个 TCP 连接上的并行内容, 进行多路数据的传输, 然而这中间并没有关联的数据. 一前一后, 前面 stream 2的帧没有收到, 后面stream 1的帧也会因此阻塞.

TCP切换到UDP就是QUIC协议.

机制一: 自定义连接机制

TCP中的 源 IP,源端口,目的 IP,目的端口 一个发生变化( 比如 wifi, 手机信号不稳 ), 就需要断开重连. 在进行三次握手。

UDP用一个64位速记数作为ID标识, UDP是无连接的. 只要ID不变, 就不需要重新建立连接.

机制二: 自定义重传机制

TCP为保证可靠性, 使用序号应答机制, 解决顺序问题和丢包问题.

  • TCP如果发送100两次(因为第一次没有返回), 这时候返回一个ACK 101, 代表客户端收到了. 这个RTT(采样往返时间)ACK是根据那次的发送计算. 采样不准确
  • QUIC通过序列号递增+offset
    • 发送100, 下次重发会递增序号101. 这样ACK返回就知道对应哪个了, 采样会准确
    • 通过offset来判断100101是不是同样的内容. 通过offset拼接成一个流

机制三: 无阻塞的多路复用

QUIC是基于UDP的, 一个连接上的多个stream之间没有依赖.

假如 stream2 丢了一个 UDP 包, 后面跟着 stream3 的一个 UDP 包, 虽然 stream2 的那个包需要重传,但是 stream3 的包无需等待, 就可以发给用户.

机制四: 自定义流量控制

TCP流量控制是通过滑动窗口协议.

QUIC是通过window_update, 来告诉对端它可以接受的字节数, 适应多路复用机制.

  • 可以在一个连接上控制窗口.
  • 在一个连接中的每个stream控制窗口.

在 TCP 协议中, 接收端的窗口的起始点是下一个要接收并且 ACK 的包, 即便后来的包都到了, 放在缓存里面, 窗口也不能右移, 因为 TCP 的 ACK 机制是基于序列号的累计应答, 一旦 ACK 了一个系列号, 就说明前面的都到了, 所以只要前面的没到, 后面的到了也不能 ACK, 就会导致后面的到了, 也有可能超时重传, 浪费带宽。

QUIC 的 ACK 是基于 offset 的, 每个 offset 的包来了, 进了缓存, 就可以应答, 应答后就不会重发, 中间的空挡会等待到来或者重发即可, 而窗口的起始位置为当前收到的最大 offset, 从这个 offset 到当前的 stream 所能容纳的最大缓存, 是真正的窗口大小。显然, 这样更加准确.