OkHttp -ConnectInterceptor(连接拦截器)源码分析

683 阅读3分钟
主要代码
Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //从拦截器链获取StreamAllocation对象,这里的StreamAllocation对象是在第一个拦截器中
    初始化完成的(设置了连接池、url路径等),真正使用的地方在这里。
    StreamAllocation streamAllocation = realChain.streamAllocation();

    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //创建HttpCodec对象  HttpCodec:用来编码HTTP request和解码HTTP response
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    //获取RealConnection
    RealConnection connection = streamAllocation.connection();
    //执行下一个拦截器,返回response
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

StreamAllocation.java

public HttpCodec newStream(
    OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
  int connectTimeout = chain.connectTimeoutMillis();
  int readTimeout = chain.readTimeoutMillis();
  int writeTimeout = chain.writeTimeoutMillis();
  int pingIntervalMillis = client.pingIntervalMillis();
  boolean connectionRetryEnabled = client.retryOnConnectionFailure();

  try {
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
    HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

    synchronized (connectionPool) {
      codec = resultCodec;
      return resultCodec;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}
StreamAllocation对象有两个关键角色
  • RealConnection:真正建立socket连接的对象
  • ConnectionPool:连接池,用来管理和复用socket连接
ConnectionPool.java
public ConnectionPool() {
  //连接池最多保存5个连接的keep-alive,每个时长5分钟
  this(5, 5, TimeUnit.MINUTES);
}

public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
  this.maxIdleConnections = maxIdleConnections;
  this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

  if (keepAliveDuration <= 0) {
    throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
  }
}
...
void put(RealConnection connection) {
  assert (Thread.holdsLock(this));
  if (!cleanupRunning) {
    cleanupRunning = true;
   //清理无效的连接
    executor.execute(cleanupRunnable);
  }
  connections.add(connection);
}
//获取可复用的连接
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
  assert (Thread.holdsLock(this));
  for (RealConnection connection : connections) {
    //要拿到的连接与连接池中的连接,连接的配置(DNS、代理、SSL证书、服务器域名、端口等)一致,\
    就可以复用
    if (connection.isEligible(address, route)) {
      streamAllocation.acquire(connection, true);
      return connection;
    }
  }
  return null;
}

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();
       //检查连接是否正在使用
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        inUseConnectionCount++;
        continue;
      }
      //否则记录闲置连接数
      idleConnectionCount++;
        //计算这个连接已经闲置多久
        long idleDurationNs = now - connection.idleAtNanos;
        //找到闲置最久的连接
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
      //keep-alive时间>=5分钟||连接池内闲置连接>5,立即移除
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
      //池内存在闲置连接,就等待 (保活时间-最长闲置时间,即到期时间)
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
      //有正在使用的连接,5分钟后再清理
        return keepAliveDurationNs;
      } else {
      //无连接,停止清理(下次put会再次启动)
        cleanupRunning = false;
        return -1;
      }
    }
    //关闭连接,返回时间0,立即再次进行请求(cleanupRunnable的while (true)会立即执行)
    closeQuietly(longestIdleConnection.socket());
    return 0;
  }
RealConnection的创建和连接的建立

streamAllocation.newStream—>findHealthyConnection—>findConnection

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");
      //①StreamAllocation的connection如果可以复用则复用
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }
      //②如果connection不能复用,则从连接池中获取RealConnection对象,获取成功则返回
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }
      selectedRoute = route;
    }
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }
    RealConnection result;
    synchronized (connectionPool) {
    if (canceled) throw new IOException("Canceled");
      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) {
        route = selectedRoute;
        return connection;
      }
      route = selectedRoute;
      refusedStreamCount = 0;
      //③如果连接池里没有,则new一个RealConnection对象
      result = new RealConnection(connectionPool, selectedRoute);
      acquire(result);
    }
 
    // Do TCP + TLS handshakes. This is a blocking operation.
    //④调用RealConnection的connect()方法发起请求
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      // ⑤将RealConnection对象存进连接池中,以便下次复用
      Internal.instance.put(connectionPool, result);
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);
    //⑥返回RealConnection对象
    return result;
  }
建立连接
public void connect(
      int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
         //...
       //进行Socket连接
       connectSocket(connectTimeout, readTimeout);
       //...
  }


private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    rawSocket.setSoTimeout(readTimeout);
    try {
      //建立socket连接  最终调用Java里的套接字Socket里的connect()方法。
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }
总结:
  • ConnectInterceptor拦截器从拦截器链中获取StreamAllocation对象,这个对象在第一个拦截器中创建,在ConnectInterceptor中才用到。
  • 执行StreamAllocation对象的newStream方法创建HttpCodec对象,用来编码HTTP request和解码HTTP response。
  • newStream方法里面通过findConnection方法返回了一个RealConnection对象。
  • StreamAllocation对象的connect方法拿到上面返回的RealConnection对象,这个RealConnection对象是用来进行实际的网络IO传输的。