连接池与请求服务拦截器
本文概述:
- 文章讨论了OkHttp 中的连接池与请求服务拦截器;在连接池中介绍了默认连接池、连接池优化思路、怎么从连接池查找连接、连接池工作流程;简单介绍了请求服务拦截器;
连接池:
什么是连接池:
- 对象池
保存的是什么:已经与特定服务端建立好连接的对象,去准备复用
-
需要用容器去保存:OkHttp 中提供了连接对象复用池(RealConnectionPool ),内部有属性connections ,这个东西就是装连接的对象
//线程安全的队列:连接池所有的连接都保存在里面 private val connections = ConcurrentLinkedQueue<RealConnection>()
-
需要提供的方法:get/put
-
get:
-
put:向连接池添加连接对象 + 启动周期性任务(会执行cleanup )
fun put(connection: RealConnection) { connection.assertThreadHoldsLock() connections.add(connection) cleanupQueue.schedule(cleanupTask) }
- cleanup :清理无效(不健康 )的连接
-
什么时候工作?
- 在新建连接之前,回去连接池中查找有没有现成的连接(与本次目标主机相同的已经建立好的连接 ),连接池中拿不到才去新建连接;
OkHttp 允许配置连接池:
-
配置示意:
var okHttpClient = OkHttpClient.Builder() .connectionPool(ConnectionPool())
默认的连接池是怎么玩的?
- 示意图:
-
参数:
- maxIdleConnections :连接池最大允许的空闲连接数,默认5
- keepAliveDuration :连接最大允许的空闲时间,默认5
- timeUnit :时间的单位(默认为分钟)
OkHttp 对连接池的优化思路:cleanup
-
根据连接池中的连接对象闲置了多久
- 指定闲置时间限制:超过了,就清理掉
- 默认为5 min
-
连接池中存放了大量的空闲连接对象
- 此时没有正在使用,
- 将闲置时间最长的清理掉,知道不超过5 个连接(LRU 思想)
-
cleanup 具体的业务逻辑
-
遍历所有的连接,记录正在使用的连接数与空闲的连接数(会记录空闲时间)
// 记录正在使用与已经闲置的连接数 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++ } else { idleConnectionCount++
-
对闲置时间最长的连接,记录其对象名与闲置时间;
// 记录最长闲置时间的连接longestIdleConnection // If the connection is ready to be evicted, we're done. val idleDurationNs = now - connection.idleAtNs if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs longestIdleConnection = connection } else { Unit }
-
最长闲置时间的连接超过了允许闲置时间 或者 闲置数量超过允许数量,从最长闲置连接开始清理
-
从连接池中清理掉,同时关闭其Socket 连接
-
不满足清理条件:
-
会做一次时间的更新
- 例如:配置最大闲置时间为五分钟,本次最长闲置时间为1分钟,那么将其剩余时间更新为四分钟;那么,在四分钟之后就可能去清除这个
-
如果当前连接池中没有闲置的连接,检查有没有正在使用的连接;有,当前的任务在五分钟之后才可能去清理
- 都没有:不清理
-
-
-
怎么从连接池中查找连接:callAcquirePooledConnection
-
返回布尔值:
- true :成功得到一个连接,并将其保存在call 里面去;
-
是怎么得到连接的?
- 对连接池遍历,判断端口、代理、协议版本信息(HTTP1.X 不能拿HTTP 2.0的)、等信息一致,那么就代表可以复用,将其保存在RealCall 对象里面去,完成本次的请求;
连接池的工作流程:
- 新建连接使用后,如果不是一次性的连接(Keep-Alive),将其放入连接池并启动cleanup,这个周期性任务会去清理掉多余的连接以及闲置时间过长的连接;这个任务的周期取决于闲置时间,最大默认为5 分钟,若本轮遍历得到的最大闲置时间为1 分钟,那么在4 分钟后会去重新启动这个任务尝试执行清理操作;
- 一次性连接(<connection,close>)
请求服务拦截器:
干了什么:
-
拿到上一个拦截器处理好的连接以及request 对象执行writeRequestHeaders 方法
exchange.writeRequestHeaders(request)
-
writeRequestHeaders :利用Socket 完成向服务器发送请求行、请求头
-
但对于post 请求,还少了一个请求体;因此其内部做了判断
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
- 如果本次请求不是GET 、HEAD 而是有请求体的方法 (requestBody != null),那么会判断本次请求的请求体是否包含字段Expect
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
-
-
客户端通过readResponseHeaders 解析响应服务端数据成OkHttp 中的Response 对象
- HTTP 1.X ---> 文本
if (responseBuilder == null) { responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
-
还会做一些判定:
- 连接不持久:将此连接的本地Socket 清理并且不会将这个连接放入连接池
- 还会有协议冲突:抛出对应的异常
-
没有特殊情况,那么将这个Response 依次向上抛出
-
HTTP 请求补充:
-
业务场景:客户端需要向服务端发送一个大数据或者需要验证,那么客户端会去先询问服务器是否愿意接收请求体的数据;服务端允许,响应100,此时客户端就发送那个大数据;服务端不允许,那这次请求就中断掉;
-
但是,服务器可能会忽略掉此请求头,一直无法应答,此时请求超时
- 所以,一般还是不用这个东西