第2章:OkHttp连接池源码解析:ConnectionPool、RealConnection、StreamAllocation
连接池是OkHttp高效复用HTTP连接的核心机制。本章深入剖析ConnectionPool、RealConnection和StreamAllocation三个关键类的源码结构、职责划分及协作流程,为后续优化与配置实践打下坚实基础。
2.1 三个类的角色与关系
| 类 | 角色 | 核心职责 |
|---|---|---|
| ConnectionPool | 连接池管理器 | 维护空闲连接集合,执行清理任务,提供连接复用与过期策略 |
| RealConnection | 真实连接载体 | 封装Socket、握手信息、路由等,持有当前分配的StreamAllocation引用列表 |
| StreamAllocation | 流分配控制器 | 为每个HTTP请求/流管理连接的获取、释放与复用计数 |
关键理解:一个
RealConnection可以被多个StreamAllocation共享(通过引用计数),而ConnectionPool负责维护所有RealConnection的生命周期。
2.2 ConnectionPool 源码结构与核心方法
ConnectionPool 内部使用双端队列Deque<RealConnection>维护所有连接,并提供以下核心方法:
- get(Address, StreamAllocation, Route):遍历队列,寻找可复用连接(相同地址、未关闭、未达到分配上限),命中后复用并增加引用。
- put(RealConnection):新连接建立后加入队列尾部,触发清理调度。
- connectionBecameIdle(RealConnection):当连接空闲时调用,触发清理或修剪。
// OkHttp 4.x 源码片段 (ConnectionPool.kt)
class ConnectionPool(
internal val maxIdleConnections: Int,
internal val keepAliveDurationNs: Long,
internal val cleanupRunnable: Runnable
) {
private val connections = ArrayDeque<RealConnection>()
@Nullable fun get(
address: Address,
streamAllocation: StreamAllocation,
route: Route
): RealConnection? {
for (connection in connections) {
if (connection.isEligible(address, route)) {
// 检查引用计数是否达到上限
if (connection.allocations.size < connection.allocationLimit) {
streamAllocation.acquire(connection)
return connection
}
}
}
return null
}
fun put(connection: RealConnection) {
connections.add(connection)
// 触发清理线程 (cleanupRunnable)
}
fun connectionBecameIdle(connection: RealConnection): Boolean {
return if (connection.noNewStreams || maxIdleConnections == 0) {
connections.remove(connection)
true
} else {
false
}
}
}
调试技巧:在
ConnectionPool.get() 中打印 connections.size 和命中结果,可直观观察连接复用率。建议使用 OkHttpClient 的 connectionPool 属性导出指标。
2.3 RealConnection:连接实体的内部状态
RealConnection 封装了底层的 Socket 连接和握手信息,关键字段包括:
- rawSocket / socket:原生 TCP 套接字。
- route:请求路由(IP、代理、DNS等)。
- handshake / protocol:TLS 握手结果和协议版本(HTTP/1.1、HTTP/2)。
- allocationLimit:最大并发流数量(HTTP/1.1 为 1,HTTP/2 默认为 1,可通过设置调整)。
- allocations:当前分配给该连接的
StreamAllocation弱引用列表,用于引用计数。 - noNewStreams:标记连接是否不再接受新流(如连接错误或关闭)。
// RealConnection 关键字段 (OkHttp 4.x)
class RealConnection(
val taskRunner: TaskRunner,
val connectionPool: ConnectionPool
) : Connection() {
var rawSocket: Socket? = null
var socket: Socket? = null
var handshake: Handshake? = null
var protocol: Protocol? = null
var route: Route? = null
var allocationLimit = 1
// 保存当前分配到此连接的 StreamAllocation 弱引用
val allocations = mutableListOf<Reference<StreamAllocation>>()
@Volatile var noNewStreams = false
@Volatile internal var idleAtNanos = Long.MAX_VALUE
}
常见错误:若
allocationLimit 设置过小(如 HTTP/2 仍为1),会导致连接池中大量连接被创建而未充分利用,降低复用率。需根据协议类型动态调整。
2.4 StreamAllocation:流分配与引用计数
每个 HTTP 请求对应一个 StreamAllocation,它负责:
- acquire(RealConnection):建立关联,将自身弱引用添加到连接的
allocations列表。 - release():解除关联,从连接的
allocations中移除自身引用;若引用数归零且连接空闲,触发connectionBecameIdle()。 - noNewStreams():标记连接不再接受新流(通常用于 HTTP/2 的连接错误)。
// StreamAllocation 核心方法 (OkHttp 4.x)
class StreamAllocation(
val connectionPool: ConnectionPool,
val address: Address,
val callStackTrace: Any?
) {
var connection: RealConnection? = null
fun acquire(connection: RealConnection) {
connection.allocations.add(
object : WeakReference<StreamAllocation>(this, ReferenceQueue()) {
override fun toString() = callStackTrace.toString()
}
)
}
fun release() {
val removedConnection = connection
if (removedConnection != null) {
// 从 connections 中移除当前 allocation 的弱引用
val iterator = removedConnection.allocations.iterator()
while (iterator.hasNext()) {
val ref = iterator.next()
if (ref.get() == this) {
iterator.remove()
break
}
}
// 若引用列表为空,通知 pool 连接空闲
if (removedConnection.allocations.isEmpty()) {
removedConnection.idleAtNanos = System.nanoTime()
if (connectionPool.connectionBecameIdle(removedConnection)) {
removedConnection.socket?.closeQuietly()
}
}
}
connection = null
}
}
2.5 连接清理与空闲策略
ConnectionPool 内部使用 cleanupRunnable 定时扫描空闲连接,默认每 5 秒执行一次。清理逻辑基于以下参数:
| 参数 | 默认值 | 作用 |
|---|---|---|
| maxIdleConnections | 5 | 保持空闲连接的最大数量 |
| keepAliveDuration | 5 分钟 | 空闲连接的最长存活时间 |
| cleanupRunnable | 内部线程 | 定期检查并移除过期连接 |
// 清理任务核心判断 (ConnectionPool.kt)
fun cleanup(now: Long): Long {
var idleConnectionCount = 0
var longestIdleDuration = Long.MIN_VALUE
var longestIdleConnection: RealConnection? = null
for (connection in connections) {
// 跳过正在使用的连接 (allocations 非空)
if (connection.allocations.isNotEmpty()) continue
idleConnectionCount++
val idleDuration = now - connection.idleAtNanos
if (idleDuration > longestIdleDuration) {
longestIdleDuration = idleDuration
longestIdleConnection = connection
}
}
// 如果空闲数超过最大限制,或超时,则移除最旧空闲连接
if (idleConnectionCount > maxIdleConnections
|| longestIdleDuration > keepAliveDurationNs) {
connections.remove(longestIdleConnection)
longestIdleConnection?.socket?.closeQuietly()
return 0 // 立即再次检查
}
// 返回下次检查的等待时间
return maxOf(0, keepAliveDurationNs - longestIdleDuration)
}
性能优化切入点:若服务器支持 HTTP/2 多路复用,建议增大
allocationLimit(例如设为5~10),并增大 maxIdleConnections 减少连接建立开销。同时注意 keepAliveDuration 设置不要超过服务端超时。
2.6 常见错误与调试建议
- 连接泄漏:未正确调用
release()导致引用计数一直 >=1,连接永远不会被回收。常发生在自定义拦截器或异步回调中。 - 复用失败:地址或路由不匹配(如代理不同、DNS 变化),导致
get()返回 null,每次都新建连接。 - 线程安全:
ConnectionPool内部操作使用同步块(synchronized),但RealConnection的allocations并发修改可能引发ConcurrentModificationException(已通过弱引用和迭代器处理)。
// 调试日志示例(可在 OkHttpClient 构造时添加)
class DebugInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val connection = chain.connection() // 当前使用的连接
Log.d("OkHttpDebug", "Request: ${request.url} on connection: ${connection?.socket()}")
return chain.proceed(request)
}
}