简单来说,HTTP/2 的多路复用允许在同一个 TCP 连接上并发处理多个请求/响应,这些请求/响应之间互不干扰。OkHttp 通过一套精心设计的类来完成这个复杂的任务。
下面这张图展示了核心类之间的协作关系,我们先有个整体印象,再逐一拆解:
flowchart TD
subgraph ConnectionPool[连接池]
RealConnectionPool
end
subgraph RealConnection[物理连接]
direction TB
RC[RealConnection] --> H2C[Http2Connection]
H2C --> Streams[Http2Stream 1\nHttp2Stream 2\n...]
end
subgraph StreamAllocation[流分配器]
SA[StreamAllocation]
end
subgraph Exchange[数据交换]
E1[Exchange] --> Stream1[Http2Stream]
E2[Exchange] --> Stream2[Http2Stream]
end
Request1[请求 A] -->|1. 获取 StreamAllocation| SA
Request2[请求 B] -->|1. 获取 StreamAllocation| SA
SA -->|2. 从连接池获取/创建| RC
SA -->|3. 在 RealConnection 上<br>创建新的流| H2C
H2C -->|4. 返回 Http2Stream| SA
SA -->|5. 创建 Exchange<br>绑定到 Stream| E1
SA -->|5. 创建 Exchange<br>绑定到 Stream| E2
style RealConnection fill:#e1f5fe,stroke:#01579b
style H2C fill:#fff3e0,stroke:#e65100
style Streams fill:#f3e5f5,stroke:#4a148c
🧱 核心类:各司其职的组件
要实现多路复用,OkHttp 设计了几个关键的类来协同工作:
RealConnection:代表一个真实的 Socket 物理连接。对于 HTTP/2,一个RealConnection可以对应多个并发的请求/响应流。Http2Connection:这是 HTTP/2 连接的核心管理者。它负责帧的读写、流的创建与管理、以及连接级别的流量控制。每个支持 HTTP/2 的RealConnection内部都会持有一个Http2Connection实例。Http2Stream:代表一个逻辑上的双向数据流,用于承载单个请求和响应。一个Http2Connection可以管理多个Http2Stream,每个流由一个唯一的整数 ID 标识。StreamAllocation:可以看作是“流分配器”,它负责为一次具体的请求(Call)从连接池中寻找或创建RealConnection,然后在该连接上分配(或创建)一个Http2Stream。它本质上是一个引用计数对象,用于跟踪一个RealConnection被多少个请求使用着。
🚀 工作流程:多路复用如何发生
当你在同一个客户端连续发起多个到同一主机的请求时,多路复用的魔法就发生了。
- 获取连接:第一个请求到来时,
StreamAllocation发现没有可用的 HTTP/2 连接,于是通过RealConnectionPool创建一个新的RealConnection,并完成 TCP 和 TLS 握手(包括 ALPN 协商,确定使用 HTTP/2 协议)。连接建立后,其内部的Http2Connection也随之启动。 - 创建流:请求需要发送数据。
StreamAllocation会调用Http2Connection.newStream()方法,在该连接上创建一个新的Http2Stream。这个流会获得一个新的、唯一的流 ID(例如,第一个请求的 ID 可能是 1)。 - 发送请求:
Http2Stream将 HTTP 请求头和方法等数据,打包成一个或多个HEADERS帧,通过Http2Connection写入 Socket。写入过程由专门的 Writer 线程处理,或者通过锁机制同步。 - 第二个请求到达:几乎是同时,第二个请求(例如获取另一张图片)到达。它的
StreamAllocation在连接池中找到了刚刚建立的那个 HTTP/2 连接。检查发现该连接是健康的,且还有能力处理新的并发流(未达到maxConcurrentStreams上限)。 - 创建另一个流:在同一个
RealConnection上,Http2Connection再次被调用来创建另一个新的Http2Stream(ID 为 3 或其他奇数,因为客户端发起的流 ID 是奇数且递增的)。 - 帧交织与传输:第二个请求的
HEADERS帧和第一个请求的DATA帧(响应体)在 Socket 层面被交织(interleaved)在一起发送。Http2Connection负责在接收端根据帧头的流 ID,将数据正确地重新组装,分发给对应的Http2Stream。 - 并发处理:这两个请求/响应对在同一个 TCP 连接上并发进行,互不阻塞。对于应用层来说,就像有两个独立的通道在同时工作。
🔬 关键技术细节
- 帧与流:HTTP/2 将请求/响应数据切割成更小的帧(Frame),并为每个帧打上所属流的 ID。这正是多路复用的基础——多个流的帧可以混合发送,接收方再按 ID 分类重组。
- 并发流限制:并不是可以无限并发。服务器会在
SETTINGS帧中告知客户端它允许的最大并发流数(maxConcurrentStreams)。Http2Connection会严格遵守这个限制。 - 流量控制:HTTP/2 提供了流级别和连接级别的流量控制,通过
WINDOW_UPDATE帧来动态调整发送窗口大小,防止接收方被数据淹没。 - 线程模型:为了高效处理,每个
Http2Connection通常会有一个专用的 Reader 线程,负责从 Socket 读取帧并根据流 ID 派发。Writer 端则可能由多个业务线程并发写入,需要通过同步机制来保证帧的完整写入。 - 连接合并(Coalescing):OkHttp 甚至可以将指向不同域名但解析到同一 IP 且证书合法的 HTTP/2 连接进行合并,进一步复用连接。
✅ 总结
OkHttp 的 HTTP/2 多路复用实现,是通过 RealConnection 代表物理通道,Http2Connection 作为通道的管理者,Http2Stream 代表通道内的逻辑子通道,最后由 StreamAllocation 将这些组件串联起来为每个请求服务。这套机制最大限度地利用了单条 TCP 连接,实现了低延迟、高吞吐的网络通信,也是 OkHttp 性能强悍的关键所在。