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类,来保存和获取连接,并会每隔一段时间清理过时的连接。

原创粉丝点击