前文

对于http请求我们都知道开始于TCP链接的三次握手然后传输数据然后释放,如下图

而当我们开启连接复用keep-alive后就是指在上一次链接不立马断开链接在超时范围内复用connectiontimeout 空闲的时间内就会复用相同的Request来减少握手大幅度提高了网络请求效率;如下图

而在Okhttp3中是怎么做到连接池复用的,本文从源码(版本v4.9.3)角度来进行探索

Okhttp3的连接池复用、清理、回收机制

连接池的代码类位于okhttp3.ConnectionPool,该类作为默认ConnectionPool 有5个空闲状态的链接和默认5min的超时设置,如果要更改可以通OkHttpClient.Builder().connectionPool() 来配置更改

class ConnectionPool internal constructor(internal val delegate: RealConnectionPool
) {constructor(maxIdleConnections: Int,keepAliveDuration: Long,timeUnit: TimeUnit) : this(RealConnectionPool(taskRunner = TaskRunner.INSTANCE,maxIdleConnections = maxIdleConnections,keepAliveDuration = keepAliveDuration,timeUnit = timeUnit))constructor() : this(5, 5, TimeUnit.MINUTES)//fun evictAll() {delegate.evictAll()}
}

实际对连接池的管理代码是在okhttp3.internal.connection.RealConnectionPool 该类管理当用户发起请求时判断是否有可以复用的connection;对connection进行缓存、获取、清理回收的操作

// 存储RealConnection的双向队列,并在添加或者删除时持有锁可以防止同时被删除或采用

private val connections = ConcurrentLinkedQueue<RealConnection>()

缓存

添加缓存是在put方法,添加完成后并会进行一次清理操作(清理在下面说到)

fun put(connection: RealConnection) {connection.assertThreadHoldsLock()connections.add(connection)// 添加到cleanupQueue循环执行,如果task已经存在则使用最早的时间执行cleanupQueue.schedule(cleanupTask)
}

获取

fun callAcquirePooledConnection(doExtensiveHealthChecks: Boolean,address: Address,call: RealCall,routes: List<Route>?,requireMultiplexed: Boolean
): RealConnection? {for (connection in connections) {// In the first synchronized block, acquire the connection if it can satisfy this call.val acquired = synchronized(connection) {when {// 要求多路复用且不是HTTP2的返回falserequireMultiplexed && !connection.isMultiplexed -> false//判断是否可以跟缓存的链接复用(ip、prxy_type等)!connection.isEligible(address, routes) -> falseelse -> {// 可以复用call.acquireConnectionNoEvents(connection)true}}}if (!acquired) continue// 检查该链接是否健康if (connection.isHealthy(doExtensiveHealthChecks)) return connection// 释放不健康的connection...val toClose: Socket? = synchronized(connection) {connection.noNewExchanges = truecall.releaseConnectionNoEvents()}toClose?.closeQuietly()}return null
}

复用判断isEligible() 在该方法判断是否可以复用,在该方法里进行了各种判断如下,判断成功后才可进行复用

internal fun isEligible(address: Address, routes: List<Route>?): Boolean {assertThreadHoldsLock()// 负载超过最大限制,不复用if (calls.size >= allocationLimit || noNewExchanges) return false// 主机字段不一样,不复用if (!this.route.address.equalsNonHost(address)) return false// 主机完全匹配,则可以复用if (address.url.host == this.route().address.url.host) {return true // This connection is a perfect match.}// 1.判断是不是Http2if (http2Connection == null) return false// 2. 判断地址是不是同一个ipif (routes == null || !routeMatchesAny(routes)) return false// 3. 链接的服务器证书可以覆盖新的主机if (address.hostnameVerifier !== OkHostnameVerifier) return falseif (!supportsUrl(address.url)) return false// 4. 证书是否匹配try {address.certificatePinner!!.check(address.url.host, handshake()!!.peerCertificates)} catch (_: SSLPeerUnverifiedException) {return false}return true
}

总结 获取复用的链接池的步骤为

  • 判断:要求多路复用且不是HTTP2的返回null
  • 判断:当前请求是否可以跟缓存池里的concection复用,通过isEligible() 方法来判断
  • 可以复用,调用acquireConnectionNoEvents()
  • 检查链接是否健康可以被使用,
    • 可以则返回
    • 不可以则清除,返回null

链接池的清理和回收

链接池的清理是在cleanupQueue一个循环执行,并可以设置延时时间执行的task的线程池里操作的Queue:
cleanupTask

private val cleanupTask = object : Task("$okHttpName ConnectionPool") {override fun runOnce(): Long = cleanup(System.nanoTime())
}

清理cleanup

cleanup是来执行清理的方法,该方法主要就是GC的标记清除算法,先标记后清除;判断能不能清除是通过弱引用来判断的

fun cleanup(now: Long): Long {var inUseConnectionCount = 0var idleConnectionCount = 0//长闲置的链接var longestIdleConnection: RealConnection? = nullvar longestIdleDurationNs = Long.MIN_VALUE// 循环当前池子for (connection in connections) {synchronized(connection) {// 通过弱引用来判断是否在使用if (pruneAndGetAllocationCount(connection, now) > 0) {inUseConnectionCount++} else {idleConnectionCount++// 计算这个链接闲置了多久val idleDurationNs = now - connection.idleAtNsif (idleDurationNs > longestIdleDurationNs) {longestIdleDurationNs = idleDurationNslongestIdleConnection = connection} else Unit}}}when {// 判断是否超过了保活时间或者池内数量超过了限制数量,则立马移除longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections -> {val connection = longestIdleConnection!!synchronized(connection) {if (connection.calls.isNotEmpty()) return 0L // No longer idle.if (connection.idleAtNs + longestIdleDurationNs != now) return 0L // No longer oldest.connection.noNewExchanges = trueconnections.remove(longestIdleConnection)}connection.socket().closeQuietly()if (connections.isEmpty()) cleanupQueue.cancelAll()// Clean up again immediately.return 0L}idleConnectionCount > 0 -> {// 池内存在闲置的链接数量,在继续等待;等待时间为:保活时间-最大闲置时间return keepAliveDurationNs - longestIdleDurationNs}inUseConnectionCount > 0 -> {// 有使用中的链接,再等待一个保活时间return keepAliveDurationNs}else -> {// 都不满足,没有在使用;则被清理return -1}}
}

总结:cleanup的主要逻辑为

  • 判断闲置的链接是否如果大于超时时间或者闲置链接的最大数量则进行清理
  • 返回其下次执行清理的时间间隔,条件为
    • 如果闲置的连接数大于0就返回用户设置的允许限制的时间-闲置时间最长的那个连接的闲置时间。
    • 如果清理失败就返回-1
    • 如果清理成功就返回0
    • 如果没有闲置的链接就直接返回用户设置的最大空闲时间间隔(默认5min)

回收pruneAndGetAllocationCount

pruneAndGetAllocationCount() 方法是来判断当前链接是否在被使用,没有则进行回收;
而这个链接被创建时会被放入弱引用,就是通过判断这个弱引用来判断链接是否还在使用

private fun pruneAndGetAllocationCount(connection: RealConnection, now: Long): Int {connection.assertThreadHoldsLock()val references = connection.callsvar i = 0while (i < references.size) {val reference = references[i]if (reference.get() != null) {i++continue}// 移除val callReference = reference as CallReferencereferences.removeAt(i)connection.noNewExchanges = true// 没有链接,则设置可以被立即清除if (references.isEmpty()) {connection.idleAtNs = now - keepAliveDurationNsreturn 0}}return references.size
}

总结

  • 获取:在RealRoutePlanner类调用RealConnectionPool.callAcquirePooledConnection方法来获取可复用的RealConnection类如果没有可复用的则返回为空并重新创建一个新的RealConnection类
  • put:在一个请求成功结束后在ConnectPaln.handleSuccess方法里会把当前的RealConnection类 put到RealConnectionPool里然后也会触发清理缓存池的循环操作
  • 清理:清理是在一个线程池里循环执行的,每次执行cleanup方法时会进行根据当前的空闲链接和等待时间计算下次执行的时间

Okhttp3 链接池复用机制源码探索相关推荐

  1. android进阶篇02、RecyclerView回收复用机制源码解析,h5移动端开发进行定位

    public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) { final View view = getChi ...

  2. 【Java 并发编程】线程池机制 ( 线程池执行任务细节分析 | 线程池执行 execute 源码分析 | 先创建核心线程 | 再放入阻塞队列 | 最后创建非核心线程 )

    文章目录 一.线程池执行任务细节分析 二.线程池执行 execute 源码分析 一.线程池执行任务细节分析 线程池执行细节分析 : 核心线程数 101010 , 最大小成熟 202020 , 非核心线 ...

  3. Android Handler消息机制源码解析

    好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...

  4. React事件机制 - 源码概览(下)

    上篇文档 React事件机制 - 源码概览(上)说到了事件执行阶段的构造合成事件部分,本文接着继续往下分析 批处理合成事件 入口是 runEventsInBatch // runEventsInBat ...

  5. JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

    文章目录 Java 执行代码的大致流程 类加载loadClass的步骤 类加载器和双亲委派机制 sun.misc.Launcher源码解析 Launcher实例化 Launcher 构造函数 双亲委派 ...

  6. CoreCLR源码探索(六) NullReferenceException是如何发生的

    NullReferenceException可能是.Net程序员遇到最多的例外了, 这个例外发生的如此频繁,以至于人们付出了巨大的努力来使用各种特性和约束试图防止它发生, 但时至今日它仍然让很多程序员 ...

  7. CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇

    在上一篇中我分析了CoreCLR中GC的内部处理, 在这一篇我将使用LLDB实际跟踪CoreCLR中GC,关于如何使用LLDB调试CoreCLR的介绍可以看: 微软官方的文档,地址 我在第3篇中的介绍 ...

  8. CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

    在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...

  9. Zookeeper--Watcher机制源码剖析一

    Watcher-- 数据变更通知 我们知道Zookeeper提供来分布式数据的订阅/发布功能,一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能让多个订阅者同时监听某个主题对象,当这个被监听对 ...

最新文章

  1. html动态生成榜单信息,排行榜.html
  2. API 版本控制的几种方式
  3. 【Paper】2019_Consensus Control of Multiple AUVs Recovery System Under Switching Topologies and Time D
  4. ML之RF:kaggle比赛之利用泰坦尼克号数据集建立RF模型对每个人进行获救是否预测
  5. 利用ASP.NET向服务器上传文件[转]
  6. linux shell 无法ssh,linux – BASH和/或.BASHRC在SU或SSH登录后无法正常工作,除非运行“bash”命令...
  7. HDU 3584 三维树状数组
  8. C# json解析字符串总是多出双引号_一篇长文带你在python里玩转Json数据
  9. 光源发散角怎么设置_Three.js 中的光源
  10. efucms搭建教程_EFUCMS E16小说漫画系统源码 最新完美UI设计漫画/听书直播源码程序...
  11. 二维离散傅里叶变换 matlab
  12. 下载文件时报错:无法复制文件,无法读源文件或磁的解决方法
  13. Python下探究随机数的产生原理和算法
  14. xt.loadOnStartup web应用程序[]中的Servlet[springmvc]引发了load()
  15. 使用VUE组件创建SpreadJS自定义单元格(二)
  16. 网络架构、云平台和微信公众平台开发接入
  17. Gym - 101606L Lizard Lounge——LIS
  18. RecyclerView的全能适配器,带有header和bottom
  19. 用python代码(turtle库)绘制好看的效果图
  20. cad中直径符号不显示_怎么在CAD、Word里敲出直径符号,你会吗?

热门文章

  1. 云计算是否早已成为基础设施?
  2. ubuntu18.04安装redis教程
  3. strcmp()函数中的ASCII知识点
  4. windows 开机取消登录密码
  5. 点云压缩 GPCC属性编码 LOD划分方式介绍(G-PCC codec description v12)
  6. hutool http巨坑
  7. 公司年会脱口秀《家》
  8. 0基础学习VR全景平台篇第46篇:底部菜单- 【开场地图】与【高清矩阵】的对比
  9. Java原生JNI的使用、javah指令的使用以及图解教材
  10. Android Compatibility