ConnectInterceptor是okhttp中負責和服務端建立連接的一個攔截器,ConnectInterceptor類中代碼看起來是不多,但千萬不要被它純潔的外表給欺騙了,實際上負責的操作都被封裝到其他的類裏了。下面來看代碼:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
//獲取StreamAllocation對象,這是在之前的攔截器中就創建好的,這裏只是獲取一遍
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對象,這是一個用來封裝request和response操作的對象
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//獲取到鏈接
RealConnection connection = streamAllocation.connection();
//交給下個攔截器執行
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
上面的代碼就是ConnectInterceptor類中的全部代碼了,主要就是獲取了幾個對象,然後交給組織處理了。其中StreamAllocation是在之前的攔截器中就已經創建好的,這裏主要看一下HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);這句代碼應該是這個攔截器的核心,通過調用newStream(),獲取了一條connection連接。
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
。。。。。
try {
//找到一個健康的連接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//用連接生成HttpCodec
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
newStream方法中主要的任務就是找到一條可用的連接,然後生成HttpCodec。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
//1、找到一條連接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
// 2、判斷連接是否是新生成的,如果 successCount==0 表示新生成的,連接可用 直接返回
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.
// 3、連接不是新生成的,則檢查連接是否健康
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
//4、如果不健康 禁止創建新的stream,繼續下一次循環
noNewStreams();
continue;
}
return candidate;
}
}
findHealthyConnection中執行了一個while循環來獲取connection,在循環裏一共執行了四步,註釋中已經說明。這裏面有幾個方法需要來看一下,findConnection()、candidate.isHealthy()和noNewStreams()。其中findConnection()放在最後說,先來看另外兩個方法。
public boolean isHealthy(boolean doExtensiveChecks) {
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
if (http2Connection != null) {
return !http2Connection.isShutdown();
}
if (doExtensiveChecks) {
try {
int readTimeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
if (source.exhausted()) {
return false; // Stream is exhausted; socket is closed.
}
return true;
} finally {
socket.setSoTimeout(readTimeout);
}
} catch (SocketTimeoutException ignored) {
// Read timed out; socket is good.
} catch (IOException e) {
return false; // Couldn't read; socket is closed.
}
}
return true;
}
isHealthy的判斷主要是通過對socket進行判斷,判斷socket是否關閉、讀寫是否正常,如果都正常,說明是可用的,否則不可用。另外對http2也進行了判斷,通過判斷http2Connection是否關閉來判斷http2是否能用。
接下來看如果不可用的情況下,noNewStreams()做了什麼操作。
public void noNewStreams() {
Socket socket;
Connection releasedConnection;
synchronized (connectionPool) {
releasedConnection = connection;
socket = deallocate(true, false, false);
if (connection != null) releasedConnection = null;
}
//關閉socket
closeQuietly(socket);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
}
private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
assert (Thread.holdsLock(connectionPool));
if (streamFinished) {
this.codec = null;
}
if (released) {
this.released = true;
}
Socket socket = null;
if (connection != null) {
if (noNewStreams) {
connection.noNewStreams = true;
}
if (this.codec == null && (this.released || connection.noNewStreams)) {
release(connection);
if (connection.allocations.isEmpty()) {
connection.idleAtNanos = System.nanoTime();
if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
socket = connection.socket();
}
}
connection = null;
}
}
return socket;
}
可以看出noNewStreams主要調用了dea主要llocate()方法,deallocate()中做的操作主要就是釋放資源。
最後來看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;
//當前的connection不能創建新的stream,就置空
toClose = releaseIfNoNewStreams();
//如果當前connection不爲空可以直接使用
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;
}
//如果當前connection爲空,就去連接池裏面找一條連接
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);
}
//如果當前的connection可用或者從連接池裏面找到了一條可用的connection,那麼直接返回
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;
}
findConnection()方法就結束了,代碼註釋裏把幾個大的步驟也做了註釋,就不在詳細的講解了,因爲我對裏面的內容也是一知半解,比如路由那塊兒。
到此呢,分析就結束了。也不想做總結了,因爲感覺寫的有點兒亂.....