OkHttp源码解析(四)

今天讲解的是ConnectIntercepter这个拦截器,这个拦截器从名字上大概都可以知道就是用来获取网络连接的I/O流,建立连接。好,我们现在看看它的intercept()方法:

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  StreamAllocation streamAllocation = realChain.streamAllocation();
  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

首先第4行这里,会从拦截器链里面获取到一个StreamAllocation,而这个StreamAllocation其实是在第一个拦截器RetryAndFollowUpIntercepter里面创建的,并且通过拦截器链一步一步地传递下来。接下来在第7、8行这里要介绍两个类,HttpCodec和RealConnection。
HttpCode:用于编码我们的Request和解码Response
RealConnection:实际的I/O传输
拿到这两个类的实例之后,就会继续传递给下一个拦截器进行处理,这样子ConnectIntercepter就大致完成了。

好,那我们现在分析一下第7行的streamAllocation.newStream()方法:

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);
  }
}

这里首先会获取连接的超时时间,读取的超时时间,写入的超时时间,然后在第9行调用findHealthyConnection()获取RealConnection,再通过RealConnection获取HttpCode,最后在同步代码块里面返回这个HttpCode。那我们就看看这个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);
    // If this is a brand new connection, we can skip the extensive health checks.
    synchronized (connectionPool) {
      if (candidate.successCount == 0) {
        return candidate;
      }
    }
    // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
    // isn't, take it out of the pool and start again.
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      noNewStreams();
      continue;
    }
    return candidate;
  }
}

我们看到在第4行这里,会开启一个while(true)的循环,第5行这里会调用findConnection()来获取RealConnection。
在9行这里,会判断这个获取到的RealConnection的successCount是否为0,如果是的话就证明这个连接是一个新的连接,就可以跳过下面的检查,直接返回。
第15行这里,会判断这个从连接池里面复用的连接是否还是健康的?何为不健康呢,就例如说socket没有关闭,输入输出流没有关闭等,如果是不健康的话,就会去调用noNewStreams()去销毁资源,把它从连接池中移除,然后继续while循环去获取另外的RealConnection。那我们现在看看这个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;
  Connection releasedConnection;
  Socket toClose;
  synchronized (connectionPool) {
    if (released) throw new IllegalStateException("released");
    if (codec != null) throw new IllegalStateException("codec != null");
    if (canceled) throw new IOException("Canceled");
    // Attempt to use an already-allocated connection. We need to be careful here because our
    // already-allocated connection may have been restricted from creating new streams.
    releasedConnection = this.connection;
    toClose = releaseIfNoNewStreams();
    if (this.connection != null) {
      // We had an already-allocated connection and it's good.
      result = this.connection;
      releasedConnection = null;
    }
    if (!reportedAcquired) {
      // If the connection was never reported acquired, don't report it as released!
      releasedConnection = null;
    }
    if (result == null) {
      // Attempt to get a connection from the pool.
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        foundPooledConnection = true;
        result = connection;
      } else {
        selectedRoute = route;
      }
    }
  }
  closeQuietly(toClose);
  if (releasedConnection != null) {
    eventListener.connectionReleased(call, releasedConnection);
  }
  if (foundPooledConnection) {
    eventListener.connectionAcquired(call, result);
  }
  if (result != null) {
    // If we found an already-allocated or pooled connection, we're done.
    return result;
  }
  // If we need a route selection, make one. This is a blocking operation.
  boolean newRouteSelection = false;
  if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
    newRouteSelection = true;
    routeSelection = routeSelector.next();
  }
  synchronized (connectionPool) {
    if (canceled) throw new IOException("Canceled");
    if (newRouteSelection) {
      // Now that we have a set of IP addresses, make another attempt at getting a connection from
      // the pool. This could match due to connection coalescing.
      List<Route> routes = routeSelection.getAll();
      for (int i = 0, size = routes.size(); i < size; i++) {
        Route route = routes.get(i);
        Internal.instance.get(connectionPool, address, this, route);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
          this.route = route;
          break;
        }
      }
    }
    if (!foundPooledConnection) {
      if (selectedRoute == null) {
        selectedRoute = routeSelection.next();
      }
      // Create a connection and assign it to this allocation immediately. This makes it possible
      // for an asynchronous cancel() to interrupt the handshake we're about to do.
      route = selectedRoute;
      refusedStreamCount = 0;
      result = new RealConnection(connectionPool, selectedRoute);
      acquire(result, false);
    }
  }
  // If we found a pooled connection on the 2nd time around, we're done.
  if (foundPooledConnection) {
    eventListener.connectionAcquired(call, result);
    return result;
  }
  // Do TCP + TLS handshakes. This is a blocking operation.
  result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
      connectionRetryEnabled, call, eventListener);
  routeDatabase().connected(result.route());
  Socket socket = null;
  synchronized (connectionPool) {
    reportedAcquired = true;
    // Pool the connection.
    Internal.instance.put(connectionPool, result);
    // If another multiplexed connection to the same address was created concurrently, then
    // release this connection and acquire that one.
    if (result.isMultiplexed()) {
      socket = Internal.instance.deduplicate(connectionPool, address, this);
      result = connection;
    }
  }
  closeQuietly(socket);
  eventListener.connectionAcquired(call, result);
  return result;
}

首先看第14行,这里会先尝试复用这个connection,如果这个connection不为空的话,就可以直接把这个返回。
第27行,当前的这个连接还没有分配,不能复用的话,就会尝试从ConnectionPool连接池中去获取。
在第88行,result.connect()就是进行连接的,然后在第95行把这个RealConnection放进连接池中。

上面说到有一个连接池复用的,那我们现在就来看看这个连接池。
一、get方法

Internal.instance.get(connectionPool, address, this, route);

而这个Internal是一个抽象类,而它的实现是在OkHttpClient里面:

@Override public RealConnection get(ConnectionPool pool, Address address,
    StreamAllocation streamAllocation, Route route) {
  return pool.get(address, streamAllocation, route);
}

此时就真正的调用连接池的get方法:

@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
  assert (Thread.holdsLock(this));
  for (RealConnection connection : connections) {
    if (connection.isEligible(address, route)) {
      streamAllocation.acquire(connection, true);
      return connection;
    }
  }
  return null;
}

这里出现了一个connections,这个是什么呢?其实它就是连接池里面维护的连接队列。

private final Deque<RealConnection> connections = new ArrayDeque<>();

在get方法中,会遍历这个维护的connections的队列,判断这个连接是否可以为我们所用,可以的话就会通过streamAllocation.acquire方法,把connection赋值给streamAllocation的成员变量:

public void acquire(RealConnection connection, boolean reportedAcquired) {
  assert (Thread.holdsLock(connectionPool));
  if (this.connection != null) throw new IllegalStateException();
  this.connection = connection;
  this.reportedAcquired = reportedAcquired;
  connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}

这里出现了一个connection.allocations的变量,那我们就看看这个变量:

/** Current streams carried by this connection. */
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();

原来它是一个弱引用StreamAllocation的List集合,那这个变量有什么用呢?其实它在后面的连接池的回收发挥着重要的作用。

二、put方法

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

这里首先会把cleanupRunnable这个线程放到线程池中去运行,然后把这个连接添加到connections这个集合中去。那我们现在看看这个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) {
          }
        }
      }
    }
  }
};

第三行这里有一个while(true)的循环,然后第四行这里会调用cleanup()的这个方法,其实这个方法里面就是调用了GC的标记-清除算法,并且会返回下次需要清理的时间间隔,在第11行根据刚刚获得的时间间隔调用wait方法进行等待以释放锁和时间片。当时间一过了,又会重新去清除。那我们现在看看这个cleanup方法:

long cleanup(long now) {
  int inUseConnectionCount = 0;
  int idleConnectionCount = 0;
  RealConnection longestIdleConnection = null;
  long longestIdleDurationNs = Long.MIN_VALUE;
  // Find either a connection to evict, or the time that the next eviction is due.
  synchronized (this) {
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
      RealConnection connection = i.next();
      // If the connection is in use, keep searching.
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        inUseConnectionCount++;
        continue;
      }
      idleConnectionCount++;
      // If the connection is ready to be evicted, we're done.
      long idleDurationNs = now - connection.idleAtNanos;
      if (idleDurationNs > longestIdleDurationNs) {
        longestIdleDurationNs = idleDurationNs;
        longestIdleConnection = connection;
      }
    }
    if (longestIdleDurationNs >= this.keepAliveDurationNs
        || idleConnectionCount > this.maxIdleConnections) {
      // We've found a connection to evict. Remove it from the list, then close it below (outside
      // of the synchronized block).
      connections.remove(longestIdleConnection);
    } else if (idleConnectionCount > 0) {
      // A connection will be ready to evict soon.
      return keepAliveDurationNs - longestIdleDurationNs;
    } else if (inUseConnectionCount > 0) {
      // All connections are in use. It'll be at least the keep alive duration 'til we run again.
      return keepAliveDurationNs;
    } else {
      // No connections, idle or in use.
      cleanupRunning = false;
      return -1;
    }
  }
  closeQuietly(longestIdleConnection.socket());
  // Cleanup again immediately.
  return 0;
}

第23行,如果空闲连接keepAlive时间超过5分钟或者空闲的连接超过5个,就会从队列中清除这个RealConnection。
第28行,如果idleConnectionCount空闲连接大于0的话,就返回这个连接的即将到期时间。
第31行,如果inUseConnectionCount(正在使用的连接数)大于0,而且走到这里肯定空闲连接为0了,所以就是所有都是活跃连接,就会返回默认的keepAlive的5分钟。
最后如果没有连接的话,就直接返回-1。

CallServerInterceptor

下面就把最后一个拦截器—CallServerInterceptor也讲一下:
这个拦截器其实就是我们okhttp真正向服务器发起网络请求和响应服务器返回的response。

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  HttpCodec httpCodec = realChain.httpStream();
  StreamAllocation streamAllocation = realChain.streamAllocation();
  RealConnection connection = (RealConnection) realChain.connection();
  Request request = realChain.request();
  long sentRequestMillis = System.currentTimeMillis();
  realChain.eventListener().requestHeadersStart(realChain.call());
  httpCodec.writeRequestHeaders(request);
  realChain.eventListener().requestHeadersEnd(realChain.call(), request);
  Response.Builder responseBuilder = null;
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
    // Continue" response before transmitting the request body. If we don't get that, return
    // what we did get (such as a 4xx response) without ever transmitting the request body.
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      httpCodec.flushRequest();
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(true);
    }
    if (responseBuilder == null) {
      // Write the request body if the "Expect: 100-continue" expectation was met.
      realChain.eventListener().requestBodyStart(realChain.call());
      long contentLength = request.body().contentLength();
      CountingSink requestBodyOut =
          new CountingSink(httpCodec.createRequestBody(request, contentLength));
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
      realChain.eventListener()
          .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
    } else if (!connection.isMultiplexed()) {
      streamAllocation.noNewStreams();
    }
  }
  httpCodec.finishRequest();
  if (responseBuilder == null) {
    realChain.eventListener().responseHeadersStart(realChain.call());
    responseBuilder = httpCodec.readResponseHeaders(false);
  }
  Response response = responseBuilder
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();
  int code = response.code();
  if (code == 100) {
    responseBuilder = httpCodec.readResponseHeaders(false);
    response = responseBuilder
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    code = response.code();
  }
  realChain.eventListener()
          .responseHeadersEnd(realChain.call(), response);
  if (forWebSocket && code == 101) {
    response = response.newBuilder()
        .body(Util.EMPTY_RESPONSE)
        .build();
  } else {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }
  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }
  if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
    throw new ProtocolException(
        "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
  }
  return response;
}

其实这里主要是五步:
1、第9行这里httpCodec.writeRequestHeaders(request),就是往socket里面写入请求头信息。
2、第28行request.body().writeTo(bufferedRequestBody),这里是写入请求body的信息。
3、第36行httpCodec.finishRequest(),表明已经完成了写入工作。
4、第39行responseBuilder = httpCodec.readResponseHeaders(false),通过HttpCode读取response的头部信息,并且构建出response。
5、第65行就是读取我们的body信息,并且赋值到response中去。
其实在71行这里,streamAllocation.noNewStreams(),这里还会进行一些关闭流的操作。

这样子,整个okhttp自己也算是分析完成,由于本人水平有限,有错漏的欢迎指出,谢谢~

猜你喜欢

转载自blog.csdn.net/lihuanxing/article/details/83026410