OkHttp3源码解析05-连接池
来源:互联网 发布:淘宝怎么看行业数据 编辑:程序博客网 时间:2024/06/04 15:57
我们都知道HTTP协议采用请求-应答模式,为了解决TCP握手和挥手效率的问题,HTTP有一个keepalive模式。
当使用普通模式(非KeppAlive模式)时,每个请求-应答都要新建一个连接,完成后立即断开连接(HTTP是无连接的协议)
当使用KeepAlive模式(又称持久连接、连接重用)时,KeepAlive功能使客户端到服务器连接持续有效,避免了频繁地重新建立连接。
http 1.0中默认是关闭的,需要在http头加入”Connection: Keep-Alive”,才能启用Keep-Alive;http 1.1中默认启用Keep-Alive,如果加入”Connection: close “,才关闭。目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的连接请求。
HTTP Keep-Alive模式
谈HTTP的KeepAlive
OKHttp对KeepAlive的处理
OKHTTP默认支持5个Socket,默认KeepAlive的时间为5分钟,对于连接池的管理通过ConnectionPool来实现。
public final class ConnectionPool { //线程池 private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); //空闲的socket最大连接数 (默认构造方法会赋值为5) private final int maxIdleConnections; //socket的keepAlive时间 (默认构造方法会赋值为5) private final long keepAliveDurationNs; //连接队列 private final Deque<RealConnection> connections = new ArrayDeque<>(); //记录连接失败的线路名单 final RouteDatabase routeDatabase = new RouteDatabase(); boolean cleanupRunning; //...}
其中,通过put和get方法来放入连接和获取连接。
先来看put方法,可以看到,在加入到connections之前会先调用cleanupRunnable这个runnable清理空闲的线程。
void put(RealConnection connection) { assert (Thread.holdsLock(this)); //检测线程是否拥有锁 if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); } connections.add(connection);}
再来看get方法,这里会遍历整个connections集合,当次数小于限制的大小,并且request的地址的缓存列表中此连接的地址完全匹配时,则直接复用缓存列表中的connection作为request的连接。
RealConnection get(Address address, StreamAllocation streamAllocation) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.allocations.size() < connection.allocationLimit && address.equals(connection.route().address) && !connection.noNewStreams) { streamAllocation.acquire(connection); return connection; } } return null;} public void acquire(RealConnection connection) { connection.allocations.add(new WeakReference<>(this));}
自动回收连接
之前提到在put方法的时候,会先调用cleanupRunnable来清理空闲的线程。
而OKHTTP是根据StreamAllocation引用计数是否为0来实现自动回收连接的。引用计数通过StreamAllocation的acquire和release方法来完成,实际上是在改动RealConnection的allocations列表的大小。
public void acquire(RealConnection connection) { connection.allocations.add(new WeakReference<>(this));}private void release(RealConnection connection) { for (int i = 0, size = connection.allocations.size(); i < size; i++) { Reference<StreamAllocation> reference = connection.allocations.get(i); if (reference.get() == this) { connection.allocations.remove(i); return; } } throw new IllegalStateException();}
再来看cleanupRunnable
private final Runnable cleanupRunnable = new Runnable() { @Override public void run() { while (true) { long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } }};
线程不停地调用cleanup方法来进行清理,并返回下次需要清理的间隔时间,然后调用wait方法进行等待一释放锁和时间片。当等待时间到了后,再次进行清理,并返回下次要清理的时间间隔,如此循环下去。
再来看cleanup方法,首先要明确的longestIdleDurationNs是空闲连接的keepAlive存活时间,idleConnectionCount是空闲连接数,inUseConnectionCount时活跃连接数。
long cleanup(long now) { int inUseConnectionCount = 0; int idleConnectionCount = 0; RealConnection longestIdleConnection = null; long longestIdleDurationNs = Long.MIN_VALUE; synchronized (this) { for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); //如果查询后,活跃数>0,则inUseConnectionCount+1 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++; continue; } idleConnectionCount++; long idleDurationNs = now - connection.idleAtNanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; } } //如果longestIdleDurationNs(空闲连接的keepAlive)超过5分钟或者 //idleConnectionCount(空闲连接数)超过5个,则从Deque中移除此连接 if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { connections.remove(longestIdleConnection); } else if (idleConnectionCount > 0) { //返回此连接即将到期的时间 return keepAliveDurationNs - longestIdleDurationNs; } else if (inUseConnectionCount > 0) { //如果活跃连接数>0,则返回默认的keepAlive时间5分钟 return keepAliveDurationNs; } else { //没有任何连接,跳出循环并返回-1 cleanupRunning = false; return -1; } } closeQuietly(longestIdleConnection.socket()); // Cleanup again immediately. return 0;}
cleanup方法主要就是根据连接(connection)中的引用计数来计算空闲连接数和活跃连接数,然后标记出空闲的连接。
再来看一下pruneAndGetAllocationCount,这个方法是用于获取当前连接引用的计数。
private int pruneAndGetAllocationCount(RealConnection connection, long now) { List<Reference<StreamAllocation>> references = connection.allocations; for (int i = 0; i < references.size(); ) { //遍历 Reference<StreamAllocation> reference = references.get(i); if (reference.get() != null) { //如果被使用中,则遍历下一个 i++; continue; } // 如果未被使用,则从列表中移除 Internal.logger.warning("A connection to " + connection.route().address().url() + " was leaked. Did you forget to close a response body?"); references.remove(i); connection.noNewStreams = true; // 如果列表为空,则说明连接没有引用了,这时返回0,表示此连接是空闲连接。 if (references.isEmpty()) { connection.idleAtNanos = now - keepAliveDurationNs; return 0; } } //返回列表的size,说明还有这几个地方引用着这个连接,表示此连接是活跃连接 return references.size();}
总结
OKHTTP根据HTTP的KeepAlive头域,来判断缓存。
通过一个专门管理连接池的ConnectionPool类,来保存和获取连接,并会每隔一段时间清理过时的连接。
- OkHttp3源码解析05-连接池
- OkHttp3源码分析[复用连接池]
- OkHttp3源码分析[复用连接池]
- Okhttp3源码解析
- OkHttp3源码解析
- OkHttp3源码解析
- Okhttp3使用 + 源码完全解析
- OkHttp3源码解析01-请求
- OkHttp3源码解析03-缓存
- OkHttp3.9源码解析(一)
- 望穿秋水:基于实例纵深解析Okhttp3源码
- Okhttp3源码(2)---CacheInterceptor解析
- OkHttp3.0源码解析---拦截器
- OkHttp3源码解析02-拦截器
- OkHttp3源码解析04-失败重连
- OkHttp3深度源码解析(一),体会OkHttp3设计模式的妙处
- Okhttp3源码(1)---同步异步请求流程解析
- OkHttp3的连接池及连接建立过程分析
- Hibernate的配置
- [Leetcode][python]Reverse Linked List/Reverse Linked List II
- java之动态代理
- Modernizr.js笔记
- 内部类
- OkHttp3源码解析05-连接池
- java 获取当月的第一天和最后一天
- 三周一次课(10月30日)
- Unity之AssetBundle原理全解
- dagger2学习笔记
- 【2017-10-30】这是一个新的Flag
- 万圣节动画-canvas像素点
- gitflow 分支原理
- 英文Windows 10中的中文显示不正常问题解决方案