简介
连接池也是OkHttp的核心部分。通过维护连接池,最大限度重用现有连接,减少网络连接的创建开销,以此提升网络请求效率。
OkHttp 使用 ConnectInterceptor 拦截器来负责建立与服务器的连接,那么连接池就工作在这一阶段。
keep-alive机制
HTTP 1.0
在 Http 1.0 中,每次连接只处理一个请求,服务端在响应客户端的请求后,就主动断开连接,不继续维护该连接。但是如果一个网页包含很多图片,典型场景下这些资源中大部分来自同一个站点。按照 Http 1.0 的做法,每个图片的请求都要创建一个连接,意味着一次 Socket 的创建销毁,创建一个TCP连接需要3次握手,而释放连接则需要2次或4次握手,又是一个相对比较费时的过程。重复的创建和释放连接极大地影响了网络效率,同时也增加了系统开销。
HTTP 1.1
为了有效地解决这一问题,HTTP/1.1提出了Keep-Alive机制:当一个HTTP请求的数据传输结束后,TCP连接不立即释放,如果此时有新的HTTP请求,且其请求的Host通上次请求相同,则可以直接复用为释放的TCP连接,从而省去了TCP的释放和再次创建的开销,减少了网络延时。在现代浏览器中,一般同时开启6~8个keepalive connections的socket连接,并保持一定的链路生命,当不需要时再关闭;而在服务器中,一般是由软件根据负载情况(比如FD最大值、Socket内存、超时时间、栈内存、栈数量等)决定是否主动关闭。
在该版本中默认是打开持久连接的,即在请求头中添加Connection:keep-alive,如果要关闭,需要在请求头中指定Connection:close,keep-alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
HTTP 2.0
HTTP 2.0 的改进如下:
- 报头压缩:HTTP/2使用HPACK压缩格式压缩请求和响应报头数据,减少不必要流量开销。
- 新的二进制格式:http/1.x使用的是明文协议,其协议格式由三部分组成:request line,header,body,其协议解析是基于文本,但是这种方式存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合;基于这种考虑,http/2.0的协议解析决定采用二进制格式,实现方便且健壮。
- 多路复用:HTTP 2通过引入新的二进制分帧层实现了完整的请求和响应复用,客户端和服务器可以将HTTP消息分解为互不依赖的帧,然后交错发送,最后再在另一端将其重新组装。在一个 TCP 连接上,我们可以向对方不断发送帧,每帧的 stream identifier 的标明这一帧属于哪个流,然后在对方接收时,根据 stream identifier 拼接每个流的所有帧组成一整块数据。把 HTTP/1.1 每个请求都当作一个流,那么多个请求变成多个流,请求响应数据分成多个帧,不同流中的帧交错地发送给对方,这就是 HTTP/2 中的多路复用。流的概念实现了单连接上多请求 - 响应并行,解决了线头阻塞的问题,减少了 TCP 连接数量和 TCP 连接慢启动造成的问题.http2 对于同一域名只需要创建一个连接,而不是像 http/1.1 那样创建 6~8 个连接。
- 数据流优先级:将 HTTP 消息分解为很多独立的帧之后,我们就可以复用多个数据流中的帧,客户端和服务器交错发送和传输这些帧的顺序就成为关键的性能决定因素。为了做到这一点,HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系
源码分析
无论是HTTP/1.1的Keep-Alive机制还是HTTP/2的多路复用机制,在实现上都需要引入连接池来维护网络连接。接下来看下OkHttp中的连接池实现。
OkHttp内部通过 ConnectionPool 来管理连接池,主要涉及下面几个类:
- StreamAllocation
- RealConnection
- ConnectionPool
- HttpCodec:两个子类:Http1Codec和Http2Codec,分别对应Http1.1协议以及Http2.0协议。具体在介绍 CallServerInterceptor 时详细介绍。
ConnectInterceptor
1 | public final class ConnectInterceptor implements Interceptor { |
流程
1 | ├── StreamAllocation.newStream |
获取连接
重点介绍一下 StreamAllocation.findConnection
方法:
1 | private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, |
清理连接
ConnectionPool有一个独立的线程cleanupRunnable来清理连接池。
1 | private final Runnable cleanupRunnable = new Runnable() { |
当连接池中put新的连接时,开始执行 cleanupRunnable。
1 | void put(RealConnection connection) { |
这段死循环实际上是一个阻塞的清理任务,首先进行清理(clean),并返回下次需要清理的间隔时间,然后调用wait(timeout)进行等待以释放锁与时间片,当被唤醒后,再次进行清理,并返回下次要清理的间隔时间。
其唤醒时机有两个:
- 超时时间到。
- 调用 connectionBecameIdle 时,这个方法在 StreamAllocation.deallocate 方法中调用。
1 | boolean connectionBecameIdle(RealConnection connection) { |
再来看一下 cleanup 方法:
1 | long cleanup(long now) { |
推荐文章
https://developer.aliyun.com/article/78101
https://mp.weixin.qq.com/s/TeQhe4T4wRjdAEPz6Ne45g
https://blog.csdn.net/qq_30993595/article/details/94405694