Android 网络性能优化(3)复用连接池

========================================================================

连接池,就是请求通过复用存在的连接,达到节省开辟新连接所需开销的结果。这也是一种设计模式,是一种浅学设计模式之享元模式(19/23)

因为连接的场景有多种(Spdy、SSL、WebSocket等),所以Socket的种类也有多种,连接池的分类可以参照下图:

在这里插入图片描述

上图展示了连接池的多种不同类型,可以看到最根基的的TCPSocket连接,分别来看下每个连接池的作用:

  1. SSL连接池

管理SSLSocket,但SSLSocket又依赖于TCP连接池提供的TCPSocket

  1. HTTP代理连接池

如果走HTTP协议,那么就需要TCP连接池提供TCPSocket,如果走HTTPS协议,那么就需要SSL连接池提供SSLSocket;

  1. SpdySession池

依赖SSL连接池提供SSLSocket,这里需要说明下,虽然HTTP/2协议没有强制绑定HTTPS,但是在实际开发中确实都是绑定HTTPS

  1. SOCKS连接池

管理的SOCKSSocket和SOCKS5Socket都需要依赖TCP连接池提供的TCPSocket

  1. WebSocket连接池

依赖TCP连接池提供的TCPSocket,声明下这里没有说明WSS(Web Socket Secure)的情况

3. 源码实现

=========================================================================

这里参考的是OkHttp4的代码

3.1 ConnectionPool类


连接池的类位于okhttp3.ConnectionPool。我们需要了解到如何在timeout时间内复用connection,并且有效的对其进行回收清理操作。我们先来看看该类的作用,因为有文档注释,我们来看看官方是如何描述该类的

在这里插入图片描述

翻译:该类管理 Http/Http2 的连接复用,用来减少网络的消耗。有着相同ip地址的Http请求可以共享一个连接通道。该类实现了一种长连接的策略。

构造函数创造了一个新的连接池和附带参数,这些参数可能会在未来的OkHttp版本中被更改(也就是说不建议我们直接使用)。目前这个连接池可以最多同时持有5个闲置的连接,如果有多的连接,将会被移除掉。

// ConnectionPool.kt

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

))

// 1

constructor() : this(5, 5, TimeUnit.MINUTES)

}

这是构造函数,注释1中可以看出,默认就的最多闲置连接是5个,保持时间是5分钟,taskRunner是一个线程管理器,用来检测闲置socket并对其进行清理,在3.x版本中,它是一个Executor。然后这个类就没别的东西了,其他的都在它的父类RealConnectionPool里面了

3.2 RealConnectionPool的缓存操作


RealConnectionPool是真正的连接池,ConnectionPool是其子类,他除了刚刚那几个子类传来的参数之外,还有一个很重要的参数:

/**

  • 使用线程安全的双向队列来管理所有的 [RealConnection]—Socket连接

*/

private val connections = ConcurrentLinkedQueue()

连接池可以通过 connections来管理连接的添加、删除、复用。

3.2.1 put操作

fun put(connection: RealConnection) {

connection.assertThreadHoldsLock()

// 1

connections.add(connection)

// 2

cleanupQueue.schedule(cleanupTask)

}

注释1: 在连接池connections中添加一个连接。

注释2: 需要整理一遍connections里的连接,比如说多出来的连接需要删除掉,超过保持时长的连接要去掉。

3.2.2 判断连接是否可以复用

在3.x版本,该类提供了一个方法来返回一个可复用的连接,主要逻辑是遍历connections的所有连接,判断是否有连接可复用。而4.x的版本稍微的更改逻辑,先来看下这个方法:

// 1

fun callAcquirePooledConnection(

address: Address,

call: RealCall,

routes: List?,

requireMultiplexed: Boolean

): Boolean {

for (connection in connections) {

synchronized(connection) {

// 2

if (requireMultiplexed && !connection.isMultiplexed) return@synchronized

// 3

if (!connection.isEligible(address, routes)) return@synchronized

// 4

call.acquireConnectionNoEvents(connection)

return true

}

}

return false

}

注释1: 传入一个ip地址,该方法就是判断是否已经存在该ip打通的socket,如果有返回true,说明可以复用,否则返回false

注释2: 判断连接的多路复用,这个属性是给Http2用的

注释3: 检查ip地址和路由列表是否合法

注释4: 调用 RealCall.acquireConnectionNoEvents()方法,将RealCall的 connection指向该连接,表明存在可以复用的连接,并且返回true。那么调用者就可以通过它的RealCall来获取到复用的连接了。

可以看下 RealCall的方法:

// RealCall.kt

fun acquireConnectionNoEvents(connection: RealConnection) {

connection.assertThreadHoldsLock()

check(this.connection == null)

this.connection = connection

connection.calls.add(CallReference(this, callStackTrace))

}

3.2.3 清除和回收连接

在刚刚put方法里面,我们看到了该类会实现一个方法来check连接池里的连接,它的作用是清除和回收超时和多出来的连接,我们来看看这个方法,因为方法比较长,所以分成两个部分来看,下面是上半部分:

// RealConnectionPool.kt

/**

  • 作用是维护连接池,删除那些超时的连接、或者超出最大数量限制的连接

  • 返回的值是睡眠到下次执行该方法的时间,

  • 如果不需要进一步清理,则返回-1

*/

fun cleanup(now: Long): Long {

var inUseConnectionCount = 0

var idleConnectionCount = 0

var longestIdleConnection: RealConnection? = null

var longestIdleDurationNs = Long.MIN_VALUE

// 1

for (connection in connections) {

synchronized(connection) {

// 2

if (pruneAndGetAllocationCount(connection, now) > 0) {

inUseConnectionCount++

} else {

idleConnectionCount++

// 3

val idleDurationNs = now - connection.idleAtNs

if (idleDurationNs > longestIdleDurationNs) {

longestIdleDurationNs = idleDurationNs

longestIdleConnection = connection

} else {

Unit

}

}

}

}

}

注释1: 遍历连接池内的所有连接

注释2: 调用 pruneAndGetAllocationCount()方法,查看该连接是否正在被使用。如果正在使用,则工作连接+1,否则 闲置连接+1

注释3: 计算该连接的闲置时间。遍历一圈,记录下闲置时间最久的连接。

再来看下cleanup()的下半部分:

// RealConnectionPool.kt

when {

// 1

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 = true

(longestIdleConnection)

}

connection.socket().closeQuietly()

if (connections.isEmpty()) cleanupQueue.cancelAll()

// Clean up again immediately.

return 0L

}

// 2

idleConnectionCount > 0 -> {

return keepAliveDurationNs - longestIdleDurationNs

}

// 3

inUseConnectionCount > 0 -> {

return keepAliveDurationNs

}

// 4

else -> {

return -1

}

}

这里是根据上半部分的统计结果进行处理:

注释1:闲置最久的连接时间已经超过5分钟或者当前空闲的连接数超过了5个,则通过 connections.remove()connection.socket().closeQuietly() 移除掉闲置最久的连接,

注释2:当前存在闲置连接,则返回 闲置最久的连接还需要等待多少时间就到5分钟 的时间间隔

注释3:当前没有闲置连接,有工作连接, 则返回 5分钟

注释4:既没有工作连接又没有闲置连接,返回-1

这个方法主要就是通过计算有无超时的限制连接或则超过容量的连接进行删除,其中它使用了一个方法 pruneAndGetAllocationCount()来查看一个连接是否正在被使用,我们可以看看这个方法的逻辑。

3.2.4 查看连接是否闲置

// RealConnectionPool.kt

/**

  • 删除所有的发生泄漏的回调,然后返回[Connection]剩余的实时的被调用的数量

  • 如果一个回调正在被引用但是实际上已经被代码不使用他们了,这个回调就是泄漏的,

  • 这种泄漏检测是不靠谱的,而且依赖于 GC回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值