Rust in Everything
基于 hyper 1.12.0

HTTP/2

HTTP/2 的帧格式、HPACK、流状态机和流控记账都在独立的 h2 crate(本卷对照 h2 0.4.20)里,hyper 的 proto/h2 只有两千多行:把 h2 的流接到 hyper 的 Service 和 Body 上,决定哪些 future 交给执行器,再加一个 ping 模块。本部分只讲这一层,h2 的内部按版本号在正文里点到为止。

和 HTTP/1 连接 最大的区别是任务的划分。HTTP/1 一条连接一个 future,请求严格串行;HTTP/2 一条连接上有很多个流,hyper 给每个流单独一个任务,于是要 Executor,也换来了连接内的并发。

  1. HTTP/2 服务端:Server 先握手再循环 poll_accept;每个新流在连接任务里调 service.call,返回的 future 包成 H2Stream spawn 出去;响应 body 由 PipeToSendStream 按流控容量写进 h2;服务出错只重置这一个流;graceful_shutdown 发两次 GOAWAY,等在途的流结束。
  2. HTTP/2 客户端:SendRequest 可以 Clone,背后是一个无界通道;h2 连接本身在握手时 spawn 的任务里跑,你 spawn 的 Connection 只负责把请求交给 h2;每个请求再 spawn 一个等响应的任务,body 没一次发完时还有一个发 body 的任务;drop 请求 future 发 RST_STREAM(CANCEL),连接照常可用。
  3. ping:保活与 BDP:一个 Ponger 兼管 keep-alive 和自适应窗口;keep-alive 在读不到帧时发 PING,超时就关连接(要 Timer);BDP 拿 PING 的往返时间估算带宽时延积,把流控窗口翻倍调大。
flowchart TB
  io["IO:rt::Read + rt::Write"] --> h2conn["h2::Connection"]
  h2conn --> srv["服务端:Server"]
  h2conn --> cli["客户端:ConnTask"]
  srv --> stream["每个流一个 H2Stream 任务"]
  cli --> send["每个请求一个 Send 任务"]
  send --> pipe["body 未发完:Pipe 任务"]
  stream --> pipe2["PipeToSendStream 写响应 body"]
  h2conn --> ping["ping:keep-alive 与 BDP"]

读之前最好已经读过 Executor 与 Timer:哪些地方调用 execute、为什么执行器的约束写成 Http2ServerConnExec 这种 sealed trait、没有 timer 时 HTTP/2 keep-alive 为什么 panic,都在那一页。

💬 关联讨论

发起讨论
加载中...