OkHttp3源码解析05-连接池

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

猜你喜欢

转载自blog.csdn.net/ethanco/article/details/78399051