5.1 HTTP/2核心架构
5.1.1 HTTP/2 vs HTTP/1.1 关键差异
5.1.2 HTTP/2核心组件
// 源码路径: okhttp3/internal/http2/Http2Connection.kt
class Http2Connection(
val client: Boolean,
val listener: Listener,
val pushObserver: PushObserver
) {
// 流管理
val streams = mutableMapOf<Int, Http2Stream>()
// 帧读写器
val reader: Http2Reader
val writer: Http2Writer
// 流量控制
var initialWindowSize = DEFAULT_INITIAL_WINDOW_SIZE
var peerSettings = Settings()
}
5.2 二进制分帧层
5.2.1 帧结构定义
// 源码路径: okhttp3/internal/http2/Frame.kt
class Frame {
val streamId: Int // 流标识符(31位无符号整数)
val type: Byte // 帧类型(DATA/HEADERS等)
val flags: Byte // 帧标志(END_STREAM等)
val payload: Buffer // 帧载荷数据
}
5.2.2 帧类型详解
| 类型值 | 帧名称 | 功能描述 |
|---|---|---|
| 0x0 | DATA | 传输应用数据 |
| 0x1 | HEADERS | 打开流并传输HTTP头部 |
| 0x2 | PRIORITY | 设置流优先级 |
| 0x3 | RST_STREAM | 立即终止流 |
| 0x4 | SETTINGS | 协商连接参数 |
| 0x5 | PUSH_PROMISE | 服务器推送资源 |
| 0x6 | PING | 测试连接活性 |
| 0x7 | GOAWAY | 关闭连接通知 |
| 0x8 | WINDOW_UPDATE | 更新流量控制窗口 |
5.2.3 帧编码过程
// 源码路径: okhttp3/internal/http2/Http2Writer.kt
fun frameHeader(
streamId: Int,
length: Int,
type: Int,
flags: Int
) {
sink.writeInt(length and 0xffffff) // 24位长度
sink.writeByte(type.toByte()) // 8位类型
sink.writeByte(flags.toByte()) // 8位标志
sink.writeInt(streamId and 0x7fffffff) // 31位流ID
}
// 发送HEADERS帧
fun headers(
streamId: Int,
outHeaders: List<Header>,
flushHeaders: Boolean
) {
// 1. 压缩头部
hpackWriter.writeHeaders(outHeaders)
// 2. 写入帧头
frameHeader(streamId, hpackBuffer.size, TYPE_HEADERS, FLAG_END_HEADERS)
// 3. 写入压缩后的头部
sink.write(hpackBuffer, hpackBuffer.size)
}
5.3 多路复用实现
5.3.1 流状态机
5.3.2 流创建与复用
// 源码路径: okhttp3/internal/http2/Http2Connection.kt
public Http2Stream newStream(
List<Header> requestHeaders,
boolean out,
boolean in
) : Http2Stream {
val streamId: Int
var stream: Http2Stream
synchronized(writer) {
synchronized(this) {
// 分配流ID(奇数表示客户端发起的流)
streamId = nextStreamId
nextStreamId += 2
// 创建新流
stream = Http2Stream(streamId, this, out, in, requestHeaders)
streams[streamId] = stream
}
// 发送HEADERS帧
writer.headers(streamId, requestHeaders)
}
return stream
}
5.3.3 多路复用优势
// 伪代码:HTTP/1.1 vs HTTP/2请求对比
void http1Example() {
// 串行请求(潜在队头阻塞)
Response response1 = client.newCall(request1).execute();
Response response2 = client.newCall(request2).execute();
}
void http2Example() {
// 并行请求(多路复用)
Call call1 = client.newCall(request1);
Call call2 = client.newCall(request2);
// 异步执行
call1.enqueue(callback1);
call2.enqueue(callback2);
}
5.4 头部压缩(HPACK)
5.4.1 HPACK压缩原理
5.4.2 HPACK实现
// 源码路径: okhttp3/internal/http2/Hpack.kt
class Writer(
private val out: BufferedSink,
private val useCompression: Boolean
) {
// 静态表(61个预定义头部)
private val staticTable = StaticTable()
// 动态表(FIFO队列)
private val dynamicTable = DynamicTable()
fun writeHeaders(headers: List<Header>) {
for (header in headers) {
// 1. 尝试查找完整匹配
val index = staticTable.index(header) ?: dynamicTable.index(header)
if (index != -1) {
// 使用索引表示
writeIndex(index)
} else {
// 2. 尝试查找名称匹配
val nameIndex = staticTable.nameIndex(header.name)
?: dynamicTable.nameIndex(header.name)
if (nameIndex != -1) {
// 增量索引表示
writeLiteral(header, nameIndex)
} else {
// 3. 完整字面值表示
writeLiteral(header, 0)
}
}
}
}
}
5.5 流量控制
5.5.1 滑动窗口机制
// 源码路径: okhttp3/internal/http2/Http2Stream.kt
class Http2Stream(
val id: Int,
val connection: Http2Connection
) {
// 接收窗口(客户端接收数据)
var bytesLeftInReceiveWindow: Int = connection.initialWindowSize
// 发送窗口(客户端发送数据)
var bytesLeftInSendWindow: Int = connection.initialWindowSize
// 更新接收窗口
fun receiveData(length: Int) {
bytesLeftInReceiveWindow -= length
// 窗口不足时发送WINDOW_UPDATE
if (bytesLeftInReceiveWindow < windowUpdateThreshold) {
connection.writeWindowUpdateLater(id, bytesLeftInReceiveWindow)
bytesLeftInReceiveWindow = initialWindowSize
}
}
}
5.5.2 窗口更新流程
// 源码路径: okhttp3/internal/http2/Http2Connection.kt
fun writeWindowUpdateLater(streamId: Int, unacknowledgedBytesRead: Long) {
taskRunner.runLater {
val windowUpdateIncrement = unacknowledgedBytesRead - initialWindowSize
writer.windowUpdate(streamId, windowUpdateIncrement)
}
}
5.6 优先级与依赖管理
5.6.1 优先级树结构
5.6.2 优先级设置
// 源码路径: okhttp3/internal/http2/Http2Stream.kt
fun setPriority(
streamDependency: Int,
weight: Int,
exclusive: Boolean
) {
// 验证权重范围
if (weight < 1 || weight > 256) {
throw IllegalArgumentException("weight must be between 1 and 256")
}
// 发送PRIORITY帧
writer.priority(id, streamDependency, weight, exclusive)
}
5.6.3 带宽分配算法
// 伪代码:基于权重的带宽分配
void allocateBandwidth(List<Http2Stream> streams) {
int totalWeight = streams.sumOf { it.weight }
for (stream in streams) {
double ratio = stream.weight / totalWeight.toDouble()
int bytesToSend = (connectionWindow * ratio).toInt()
sendData(stream, bytesToSend)
}
}
5.7 服务器推送
5.7.1 推送流程
5.7.2 推送实现
// 源码路径: okhttp3/internal/http2/PushObserver.kt
interface PushObserver {
// 服务器推送请求
fun onRequest(streamId: Int, request: List<Header>)
// 推送响应头
fun onHeaders(streamId: Int, responseHeaders: List<Header>)
// 推送数据
fun onData(streamId: Int, source: BufferedSource, byteCount: Int)
// 推送流终止
fun onReset(streamId: Int, errorCode: ErrorCode)
}
// 默认实现:拒绝所有推送
object PushObserver.CANCEL : PushObserver {
override fun onRequest(streamId: Int, request: List<Header>): Boolean {
return false // 拒绝推送
}
}
5.8 HTTP/2连接管理
5.8.1 连接健康检查
// 源码路径: okhttp3/internal/http2/Http2Connection.kt
public boolean isHealthy(long nowNs) {
return !shutdown
&& (lastGoodStreamId < Integer.MAX_VALUE)
&& (connectionDegraded || bytesLeftInWriteWindow > 0);
}
5.8.2 连接保活
// 定时发送PING帧
connection.writer().ping(false, 0, 0, null)
// 接收PING帧处理
override fun ping(ack: Boolean, payload1: Int, payload2: Int) {
if (!ack) {
// 回复PING
writer.ping(true, payload1, payload2)
} else {
// 更新最后活动时间
lastPingTimeNs = System.nanoTime()
}
}
5.9 性能优化实践
5.9.1 调整窗口大小
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Arrays.asList(
new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.http2Settings(new Settings.Builder()
.set(Settings.INITIAL_WINDOW_SIZE, 16777216) // 16MB窗口
.set(Settings.MAX_FRAME_SIZE, 16777215) // 最大帧大小
.build())
.build()))
.build();
5.9.2 启用HTTP/2优化
// 强制使用HTTP/2(如果服务器支持)
OkHttpClient client = new OkHttpClient.Builder()
.protocols(Arrays.asList(Protocol.H2_PRIOR_KNOWLEDGE, Protocol.HTTP_1_1))
.build();
// 监听协议升级
EventListener listener = new EventListener() {
@Override
public void protocolSelected(Call call, Protocol protocol) {
Log.d("HTTP Protocol", protocol.toString());
}
};
5.10 故障排除
5.10.1 常见HTTP/2问题
-
协议协商失败:
- 原因:服务器不支持HTTP/2
- 解决:回退到HTTP/1.1
-
流重置(RST_STREAM) :
- 原因:客户端取消请求或超时
- 排查:检查日志中错误码
-
GOAWAY帧接收:
- 原因:服务器关闭连接
- 处理:创建新连接重试请求
5.10.2 诊断工具
# 使用Wireshark抓包分析
tshark -i eth0 -Y "http2" -O http2
# 关键帧过滤
http2.type == 0x7 # GOAWAY帧
http2.type == 0x3 # RST_STREAM帧
本章小结
-
二进制分帧:
- 所有通信通过帧传输
- 帧包含长度、类型、标志和流ID
- 支持多种帧类型满足不同需求
-
多路复用:
- 单个连接支持多个并发流
- 避免HTTP/1.x队头阻塞问题
- 流可设置优先级和依赖关系
-
头部压缩:
- HPACK算法减少头部大小
- 静态表和动态表结合使用
- Huffman编码进一步压缩字符串
-
流量控制:
- 基于窗口的流量控制
- 初始窗口大小65535字节
- 通过WINDOW_UPDATE帧更新窗口
-
服务器推送:
- 服务器可主动推送资源
- 客户端可拒绝推送请求
- 减少页面加载延迟
-
连接管理:
- PING帧检测连接活性
- GOAWAY帧优雅关闭连接
- 健康检查确保连接可用
在下一章中,我们将深入分析OkHttp的缓存系统设计,包括内存缓存和磁盘缓存的实现机制,以及缓存策略的决策过程