OkHttp源码分析(三)——网络操作(两个拦截器)

Http1.1的Keep-Alive机制和Http2.0的多路复用机制,在实现上都需要引入连接池来维护网络连接。OkHttp中连接池的实现——连接拦截器ConnectInterceptor。

ConnectInterceptor

ConnectInterceptor#intercept

连接拦截器代码如下:

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    return realChain.proceed(request, transmitter, exchange);
  }
}

这里使用transmitter.newExchange获取Exchange实例。作为参数传入调用拦截器链的proceed方法。(proceed方法有三个参数,因为若没有配置网络拦截器发的话,就会使用CallServerInterceptor进行真正的网络IO操作)。

Exchange负责从创建连接的IO流中写入请求和读取响应,完成一次请求/响应的过程。在CallServerInterceptor会看到它真正的作用,这里先忽略。继续看Transmitter的newChange方法。

Transmitter是okhttp中应用层和网络层的桥梁,管理同一个Call的所有连接、请求、响应和IO流之间的关系。

Exchange#newExchange

//Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {

    //...省略异常处理

    //通过ExchangeFinder的find方法找到一个ExchangeCodec(编码解码)
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);

    //创建Exchange,并把ExchangeCodec实例codec传进去,所以Exchange内部持有ExchangeCodec实例
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    //...
    
    return result;
}

ExchangeFinder对象早在RetryAndFollowUpInterceptor中通过Transmitter的prepareToConnect方法创建,它的find方法是连接真正创建的地方。

ExchangFinder就是负责连接的创建,把创建好的连接放入连接池,如果连接池中已经有该链接,就直接取出来复用,所以ExchangeFinder管理着两个重要的角色:RealConnectionRealConnectionPool

ExchangeFinder#find

public ExchangeCodec find(
      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);
      //利用连接实例化ExchangeCodec对象,如果是HTTP2.0返回Http2ExchangeCodec,否则返回Http1ExchangeCodec
      return resultConnection.newCodec(client, chain);
    } catch (RouteException e) {
      trackFailure();
      throw e;
    } catch (IOException e) {
      trackFailure();
      throw new RouteException(e);
    }
  }

findHealthyConnection方法就是去寻找可用TCP连接的这个方法内部和连接池有紧密的联系。

ExchangeFinder#findHealthyConnection

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
          
      while (true) {
      //找连接
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // 是新连接,不用检测
      synchronized (connectionPool) {
        if (candidate.successCount == 0 && !candidate.isMultiplexed()) {
          return candidate;
        }
      }

      // 检测不合格,继续找
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        candidate.noNewExchanges();
        continue;
      }

      return candidate;      
}

这里循环找连接,如果是不健康的连接,标记不可用(标记会移除,后面的连接池会有),然后继续找。健康是指连接可以承载新的数据流,socket是连接状态。下面进入findConnection方法。

ExchangeFinder#findConnection

//为承载新的数据流寻找连接。寻找的顺序是已分配的连接、连接池、新建连接
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    RealConnection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      //请求已被取消
      if (transmitter.isCanceled()) throw new IOException("Canceled");
      hasStreamFailure = false;

      // 尝试使用已经创建过的连接,已经创建过的连接可能已经被限制创建新的流
      releasedConnection = transmitter.connection;
      // 如果已经创建过的连接已经被限制创建新的流,就释放该连接(releaseConnectionNoEvents中会把该连接置空,并返回该连接的socket已关闭)
      toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
          ? transmitter.releaseConnectionNoEvents()
          : null;
      
      //已经创建过的连接还能使用,就直接使用它当做结果
      if (transmitter.connection != null) {
        result = transmitter.connection;
        releasedConnection = null;
      }
      
      //已经创建过的连接不能使用
      if (result == null) {
        // 尝试从连接池中找可用的连接,如果找到,这个连接会赋值先保存在Transmitter中
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          //从连接池中找到可用的连接,如果找到,这个连接会赋值先保存在Transmitter中
          foundPooledConnection = true;
          result = transmitter.connection;
        } else if (nextRouteToTry != null) {
          selectedRoute = nextRouteToTry;
          nextRouteToTry = null;
        } else if (retryCurrentRoute()) {
          selectedRoute = transmitter.connection.route();
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // 如果有已分配可用连接 或 从连接池获取到连接,结束。没有就走下面的新建连接过程
      return result;
    }

    // 看是否需要路由选择,多IP操作
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    List routes = null;
    synchronized (connectionPool) {
      if (transmitter.isCanceled()) throw new IOException("Canceled");
      
      //如果有下一个路由
      if (newRouteSelection) {
        routes = routeSelection.getAll();
        // 这里第二次尝试从连接池中找可用连接
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          //从连接池中找到了可用的连接
          foundPooledConnection = true;
          result = transmitter.connection;
        }
      }
      
      //在连接池中没有找到可用连接
      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // 所以这里新创建一个连接,后面会进行socket连接
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }

    // 如果在连接池中找到可用的连接,直接返回该连接
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // 第二次没成功,就把新建的连接进行TCP+TLS握手,与服务端建立连接,是阻塞操作
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      connectingConnection = null;
      // 最后一次尝试从连接池获取,最后一个参数为true,即要求多路复用
      // 为了保证多路复用,会再次确认连接池中此时是否有同样的连接
      if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
        // 如果获取到,就关闭在创建里的连接,返回获取的连接
        result.noNewExchanges = true;
        socket = result.socket();
        result = transmitter.connection;

        // 这个刚刚连接成功的路由,可以用作下次 尝试的路由
        nextRouteToTry = selectedRoute;
      } else {
        //最后一次尝试也没有的话就把刚刚新建的连接存入连接池
        connectionPool.put(result);
        transmitter.acquireConnectionNoEvents(result);
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }

这一节的代码比较长,所以这里再来总结下流程:

findConnection方法目的就是为承载新的数据流寻找连接,寻找的顺序是已分配的连接、连接池、新建连接。、

  1. 首先会尝试使用已给数据流分配的连接(transmitter.connection),已分配连接的情况如重定向时的再次请求,说明上次已经有了连接;
  2. 若没有已分配的可用连接,就尝试从连接池中匹配获取。因为此时没有路由信息,所以匹配条件:address一致——host、port、代理等一致,且匹配的连接可以接受新的数据流;
  3. 若从连接池没有获取到,则取下一个代理的路由信息(多个route,即多个IP地址),再次尝试从连接池获取,此时可能因为连接合并而匹配到;
  4. 若第二次也没有获取到,就创建RealConnection实例,进行TCP+TLS握手,与服务端建立连接;
  5. 此时为了确保Http2.0连接的多路复用性,会第三次从连接池匹配。因为新建立的连接的握手过程是非线程安全的,所以此时可能连接池新存入了相同的连接;
  6. 第三次若匹配到,就使用已有连接,释放刚刚新建的连接;若未匹配到,则把新连接存入连接池并返回。

这段代码里面包含了太多的信息,RealConnection是什么、RealConnectionPool内部到底是如何实现的,路由是计网里学到的有路由表那样的操作么。

RealConnection-连接实现

RealConnection实现了Connection接口,内部利用Socket建立连接,如下:

public interface Connection {
    
    //返回这个连接使用的Route
    Route route();
    
    //返回这个连接使用的socket
    Socket socket();
    
    //如果是HTTPS,返回TLS握手信息用于建立连接,否则返回null
    Handshake handshake();
    
    //返回应用层使用的协议,Protocol是一个枚举,如HTTP1.1、HTTP2
    Protocol protocol();
}
public final class RealConnection extends Http2Connection.Listener implements Connection {
    
    public final RealConnectionPool connectionPool;
    //路由
    private final Route route;
    //内部使用这个rawSocket在TCP层建立连接
    private Socket rawSocket;
    //如果没有使用HTTPS,那么socket == rawSocket,否则这个socket == SSLSocket
    private Socket socket;
    //TLS握手
    private Handshake handshake;
    //应用层协议
    private Protocol protocol;
    //HTTP2连接
    private Http2Connection http2Connection;
    //okio库的BufferedSource和BufferedSink,相当于javaIO的输入输出流
    private BufferedSource source;
    private BufferedSink sink;
    
    
    public RealConnection(RealConnectionPool connectionPool, Route route) {
        this.connectionPool = connectionPool;
        this.route = route;
    }


    public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
        //...
    }
    //...
}

这里的connect方法,可以和外部服务器建立连接。

//RealConnection.java
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    List connectionSpecs = route.address().connectionSpecs();
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    //路由选择
    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      if (!Platform.get().isCleartextTrafficPermitted(host)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    } else {
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        throw new RouteException(new UnknownServiceException(
            "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
      }
    }

    //开始连接
    while (true) {
      try {
        if (route.requiresTunnel()) {//如果是通道模式,则建立通道连接
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {//否则进行Socket连接,大部分是这种情况
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        //建立HTTPS连接
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        break;
      }
      //...省略异常处理

    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }

一般都会调用connectSocket方法建立socket连接。

//RealConnection.java
private void connectSocket(int connectTimeout, int readTimeout, Call call,
                           EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    //根据代理类型的不同创建Socket
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
        //1、建立Socket连接
        Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    }
    //...省略异常处理

    try {
        //获得Socket的输入输出流
        source = Okio.buffer(Okio.source(rawSocket));
        sink = Okio.buffer(Okio.sink(rawSocket));
    } 
     //...省略异常处理
}

Platform是okhttp中根据不同Android版本平台差异实现的一个兼容类。Platform的connectSocket方法最终会调用rawSocket的connect()方法建立其Socket连接,建立socket连接后,就可以通过Socket连接获得输入输出流source和sink,okhttp就可以从source读取或往sink写入数据。source和sink是BufferedSource和BufferedSink类型,他们来自于okio库。

RealConnectionPool-连接池

ConnectionPool,用于管理http1.1/http2.0连接重用,以减少网络延迟。相同Address的http请求可以共享一个连接,ConnectionPool就是实现了连接的复用。

public final class RealConnectionPool {
  
  //线程池
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
    Integer.MAX_VALUE /* maximumPoolSize */, 
    60L /* keepAliveTime */, 
    TimeUnit.SECONDS,
     new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  private final int maxIdleConnections;
  private final long keepAliveDurationNs;
  //清理连接任务,在executor中执行
  private final Runnable cleanupRunnable = () -> {
    while (true) {
      //调用cleanup方法执行清理逻辑
      long waitNanos = cleanup(System.nanoTime());
      if (waitNanos == -1) return;
      if (waitNanos > 0) {
        long waitMillis = waitNanos / 1000000L;
        waitNanos -= (waitMillis * 1000000L);
        synchronized (RealConnectionPool.this) {
          try {
            //调用wait方法进入等待
            RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
          } catch (InterruptedException ignored) {
          }
        }
      }
    }
  };
  
  //双端队列,保存连接
  private final Deque connections = new ArrayDeque<>();
  final RouteDatabase routeDatabase = new RouteDatabase();
  boolean cleanupRunning;

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

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }
  
  //返回空连接数
  public synchronized int idleConnectionCount() {
    int total = 0;
    for (RealConnection connection : connections) {
      if (connection.transmitters.isEmpty()) total++;
    }
    return total;
  }
  
  //返回池子中的连接数
  public synchronized int connectionCount() {
    return connections.size();
  }
}

从连接池的部分代码可看到,其内部维护了一个连接池,用来执行清理连接任务是线程cleanRunnable,还维护了一个双端队列connections,用来缓存已经创建的连接。创建一次连接要经历TCP握手,如果是HTTP还要经历TLS握手,握手的过程都是耗时的,所以为了提高效率,就需要connections来对连接进行缓存,从而可以复用;还有如果连接使用完毕,长时间不释放,也会造成资源的浪费,所以就需要cleanupRunnable定时清理无用的连接,okhttp支持5个并发连接,默认每个连接keep-alive 5分钟。

  • cleanup()
 //RealConnectionPool.java
long cleanup(long now) {
    
    int inUseConnectionCount = 0;//正在使用连接数
    int idleConnectionCount = 0;//空闲连接数
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
        //遍历所有连接,记录空闲连接和正在使用连接各自的数量
        for (Iterator i = connections.iterator(); i.hasNext(); ) {
            RealConnection connection = i.next();

            //如果该连接还在使用,pruneAndGetAllocationCount种通过引用计数的方式判断一个连接是否空闲
            if (pruneAndGetAllocationCount(connection, now) > 0) {
                //使用连接数加1
                inUseConnectionCount++;
                continue;
            }
            
            //该连接没有在使用

            //空闲连接数加1
            idleConnectionCount++;

            //记录keepalive时间最长的那个空闲连接
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs;
                //这个连接很可能被移除,因为空闲时间太长
                longestIdleConnection = connection;
            }
        }
        
        //跳出循环后

        //默认keepalive时间keepAliveDurationNs最长为5分钟,空闲连接数idleConnectionCount最大为5个
        if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {//如果longestIdleConnection的keepalive时间大于5分钟 或 空闲连接数超过5个
            //把longestIdleConnection连接从队列清理掉
            connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {//如果空闲连接数小于5个 并且 longestIdleConnection连接还没到期清理
            //返回该连接的到期时间,下次再清理
            return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {//如果没有空闲连接 且 所有连接都还在使用
            //返回keepAliveDurationNs,5分钟后再清理
            return keepAliveDurationNs;
        } else {
            // 没有任何连接,把cleanupRunning复位
            cleanupRunning = false;
            return -1;
        }
    }

    //把longestIdleConnection连接从队列清理掉后,关闭该连接的socket,返回0,立即再次进行清理
    closeQuietly(longestIdleConnection.socket());

    return 0;
}

cleanup清理连接的逻辑如下:

  • 首先遍历所有连接,记录空闲连接数idleConnectionCount和正在使用连接数inUseConnectionCount,记录空闲连接数时,还要找出空闲时间最长的空闲连接longestIdleConnection,这个连接是很有可能被清理的;
  • 遍历完后,根据最大空闲时长和最大空闲连接数来决定是否清理longestIdleConnection.
    • 如果longestIdleConnection空闲时间>最大空闲时长或空闲连接数>最大空闲连接数,那么该连接就会从队列中移除,然后关闭该连接的socket,返回0,立即再次进行清理;
    • 如果空闲连接数<5 且 longestIdleConnection空闲时间<最大空闲时长,还没到期清理,就返回该连接的到期时间,下次再清理;
    • 如果没有空闲连接 且 所有连接都还在使用,那么返回默认的keepAlive时间,5分钟后再清理;
    • 没有任何的连接,idleConnectionCount和inUseConnectionCount都为0,把cleanupRunning复位,等待下一次put连接时,再次使用线程池执行cleanupRunnable。
  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    //连接上的数据流,弱引用列表
    List> references = connection.transmitters;
    for (int i = 0; i < references.size(); ) {
      Reference reference = references.get(i);
      if (reference.get() != null) {
        i++;
        continue;
      }

      // 到这里,transmitter是泄漏的,要移除,且此连接不能再承载新的数据流(泄漏的原因就是下面的message)
      TransmitterReference transmitterRef = (TransmitterReference) reference;
      String message = "A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?";
      Platform.get().logCloseableLeak(message, transmitterRef.callStackTrace);
      references.remove(i);
      connection.noNewExchanges = true;

      //连接因为泄漏没有数据流了,那么可以立即移除了。所以设置 开始空闲时间 是5分钟前(厉害厉害!)
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }
    //返回连接上的数据流数量,大于0说明正在使用。
    return references.size();
  }

connection.transmitters表示在此连接上的数据流,transmitter size大于1表示多个请求复用此连接。Transmitter是弱引用。

下面看看连接池除了清理空闲线程的其他操作:

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
}

这里就是把连接存入队列,同时开始循环尝试清理过期连接。

  //为transmitter 从连接池 获取 对应address的连接。若果routes不为空,可能会因为 连接合并(复用) 而获取到HTTP/2连接
  boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
      @Nullable List routes, boolean requireMultiplexed) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (requireMultiplexed && !connection.isMultiplexed()) continue;
      if (!connection.isEligible(address, routes)) continue;
      transmitter.acquireConnectionNoEvents(connection);
      return true;
    }
    return false;
  }
  
   void acquireConnectionNoEvents(RealConnection connection) {
    assert (Thread.holdsLock(connectionPool));

    if (this.connection != null) throw new IllegalStateException();
    this.connection = connection;
    connection.transmitters.add(new TransmitterReference(this, callStackTrace));
  }

transmitterAcquirePooledConnection意思是 为transmitter 从连接池 获取连接,实际上transmitter就代表一个数据流,也就是一个http请求。注意到,在遍历中 经过判断后是transmitter的acquireConnectionNoEvents方法,即把匹配到的connection赋给transmitter。

  //关闭并移除所有空闲连接
  public void evictAll() {
    List evictedConnections = new ArrayList<>();
    synchronized (this) {
      for (Iterator i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        if (connection.transmitters.isEmpty()) {
          connection.noNewExchanges = true;
          evictedConnections.add(connection);
          i.remove();
        }
      }
    }

    for (RealConnection connection : evictedConnections) {
      closeQuietly(connection.socket());
    }
  }

遍历连接池,如果连接上的数据流是空,那么就从连接池移除并且关闭。

CallServerIntercepter

请求服务拦截器,进行真正的网络IO读写——写入http请求的header和body数据、读取响应的header和body。

在ConnectInterceptor中主要介绍了如何寻找连接以及连接池如何管理连接。在获取到连接后,调用了RealConnection的newCodec方法返回ExchangeCodec实例。然后使用ExchangeCodec实例创建了Exchange实例传入CallServerInterceptor了。上面提到过ExchangeCodec负责请求和响应的IO读写,我们先来看看ExchangeCodec创建过程——RealConnection的newCodec方法:

  ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
    if (http2Connection != null) {
      return new Http2ExchangeCodec(client, this, chain, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1ExchangeCodec(client, this, source, sink);
    }
  }

http2Connection不为空就创建Http2ExchangeCodec,否则是Http1ExchangeCodec。而http2Connection的创建是连接进行TCP、TLS握手的时候,即在RealConnection的connect方法中,具体就是connect方法中调用的establishProtocol方法:

private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    //针对http请求,如果配置的协议包含Protocol.H2_PRIOR_KNOWLEDGE,则开启Http2连接
    if (route.address().sslSocketFactory() == null) {
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        socket = rawSocket;
        protocol = Protocol.H2_PRIOR_KNOWLEDGE;
        startHttp2(pingIntervalMillis);
        return;
      }

      socket = rawSocket;
      protocol = Protocol.HTTP_1_1;
      return;
    }
    //针对https请求,会在TLS握手后,根据平台获取协议(),如果协议是Protocol.HTTP_2,则开启Http2连接
    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);

    if (protocol == Protocol.HTTP_2) {
      startHttp2(pingIntervalMillis);
    }
  }

  private void startHttp2(int pingIntervalMillis) throws IOException {
    socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
    http2Connection = new Http2Connection.Builder(true)
        .socket(socket, route.address().url().host(), source, sink)
        .listener(this)
        .pingIntervalMillis(pingIntervalMillis)
        .build();
    http2Connection.start();
  }

拦截器:

public final class CallServerInterceptor implements Interceptor {
  private final boolean forWebSocket;

  public CallServerInterceptor(boolean forWebSocket) {
    this.forWebSocket = forWebSocket;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();//上个拦截器传入的exchange
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    //写请求头
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    //含body的请求
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // 若请求头包含 "Expect: 100-continue" , 就会等服务端返回含有 "HTTP/1.1 100 Continue"的响应,然后再发送请求body. 
      //如果没有收到这个响应(例如收到的响应是4xx),那就不发送body了。
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }
      //responseBuilder为null说明服务端返回了100,也就是可以继续发送body了
      if (responseBuilder == null) {
        if (request.body().isDuplex()) {//默认是false不会进入
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          // 满足了 "Expect: 100-continue" ,写请求body
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
       //没有满足 "Expect: 100-continue" ,请求发送结束
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
     //没有body,请求发送结束
      exchange.noRequestBody();
    }

    //请求发送结束
    if (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }
    //回调 读响应头开始事件(如果上面没有)
    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }
    //读响应头(如果上面没有)
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }
    //构建response
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
      //这里服务端又返回了个100,就再尝试获取真正的响应()
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }
    //回调读响应头结束
    exchange.responseHeadersEnd(response);
    //这里就是获取响应body了
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }
    //请求头中Connection是close,表示请求完成后要关闭连接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }
    //204(无内容)、205(充值内容),body应该是空
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    
    return response;
  }
}

最终返回response。

参考文章

网络请求框架OkHttp3全解系列 - (四)拦截器详解2:连接、请求服务(重点)

okhttp3源码分析之拦截器

面试官:听说你熟悉OkHttp原理?

你可能感兴趣的:(OkHttp源码分析(三)——网络操作(两个拦截器))