第2章:OkHttp连接池源码解析:ConnectionPool、RealConnection、StreamAllocation

连接池是OkHttp高效复用HTTP连接的核心机制。本章深入剖析ConnectionPool、RealConnection和StreamAllocation三个关键类的源码结构、职责划分及协作流程,为后续优化与配置实践打下坚实基础。

2.1 三个类的角色与关系

类角色核心职责
ConnectionPool连接池管理器维护空闲连接集合,执行清理任务,提供连接复用与过期策略
RealConnection真实连接载体封装Socket、握手信息、路由等,持有当前分配的StreamAllocation引用列表
StreamAllocation流分配控制器为每个HTTP请求/流管理连接的获取、释放与复用计数
关键理解:一个RealConnection可以被多个StreamAllocation共享(通过引用计数),而ConnectionPool负责维护所有RealConnection的生命周期。
ConnectionPool、RealConnection、StreamAllocation 协作结构 ConnectionPool - connections: Deque<RealConnection> + get(), put(), connectionBecameIdle() RealConnection - rawSocket, route, handshake - allocationLimit + noNewStreams, idleAtNanos StreamAllocation - connection, address - codec, canceled + acquire(), release() 1 → N 1 → N (引用计数) StreamAllocation 通过 acquire() 增加 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 秒执行一次。清理逻辑基于以下参数:

参数默认值作用
maxIdleConnections5保持空闲连接的最大数量
keepAliveDuration5 分钟空闲连接的最长存活时间
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)
    }
}