Android框架原始碼分析-OkHttp3

語言: CN / TW / HK

淺析OkHttp3

這篇文章主要用來回顧Okhttp3原始碼中,同步非同步請求的區別、攔截器的責任鏈模式、連線池管理以及探討socket通訊到底在哪裡實現。

列出的程式碼可能刪掉了非核心部分的展示,如果有異議請檢視原始碼

連線池涉及知識:可能根據 IP 地址集合,嘗試進行子網合併/構造超網,這部分請大家回顧計算機網路的構造超網進行復習。

1.基本使用

使用OkHttp來發送網路請求,一般我們會通過同步請求和非同步請求兩種方式:

OkHttpClient okhttpClient = new OkHttpClient(); ​ //拼接請求 Request request = new Request.Builder()   .url("http://xxx.xxx.xxx")   .get()//傳送get請求    //.post(requestBody)//傳送post請求   .build(); ​ //建立請求實體Call Call call = okHttpClient.newCall(request); ​ //發起同步請求,阻塞等待返回的response Response response = call.execute(); ​ //傳送非同步請求,通過介面回撥來獲取資料 call.enqueue(new Callback(){    @Override    public void onFailure(Call call,IOException e){        //e.getMessage()   }    @Override    public void onResponse(Call call,Response response) throws IOException{        //處理返回的響應體response        //response.body()   } });

同步請求,將會阻塞等待返回的response。非同步請求,將會通過介面回撥來獲得返回的資料。這也是最經典的獲取返回資料的兩個方式:同步return返回,以及非同步回撥返回。

首先我們關注到請求實體,請求實體實現了 Call 介面,Call 介面中定義了一些請求實體會用到的方法:

public interface Call extends Cloneable {    //獲取請求實體中的Request物件    Request request(); //發起同步請求    Response execute() throws IOException; //發起非同步請求    void enqueue(Callback var1); //取消請求    void cancel(); //是否被執行了(子類實現用到了synchronized,我們認為標誌是臨界區,不希望在多執行緒場景中產生歧義)    boolean isExecuted(); //是否被取消了(子類的呼叫鏈中,也用到了synchronized)    boolean isCanceled(); //返回一個AsyncTimeout物件例項,它的timedOut()方法可以呼叫到transmitter的cancel()方法    //簡言:可以用於超時取消    Timeout timeout(); //這裡是淺拷貝    Call clone(); //讓外界能夠以此規範來產生出一個Call請求實體    public interface Factory {        Call newCall(Request var1);   } }

在它的實現類RealCall和AsyncCall中,isExecuted()和 isCanceled()都被加以了 synchronized 修飾,因為 executed 和 canceled 在多執行緒場景下,可能會被多個執行緒使用,認為是臨界區資料,使用 synchronized 修飾是希望這兩個變數對所有執行緒保持可見性以及互斥性。

我們接下來來看一下 Call 介面的具體實現類,並對比他們的區別

2. RealCall 和 AsyncCall

我們發現,雖然非同步任務也命名為Call結尾,但它並沒有實現 Call 介面,而是實現了 Runnable 介面。這裡的設計思想涉及到【非靜態內部類】的使用,它可以呼叫外部類的方法。我們來看一下哪些方法是RealCall和AsyncCall所共用的。

先來看一下兩個類的結構:

final class RealCall implements Call {    //這部分程式碼很多,而且不論是同步還是非同步,處理邏輯是一模一樣的!    Response getResponseWithInterceptorChain(){        //...   }    public Response execute(){        //外部類RealCall可以呼叫它        this.getResponseWithInterceptorChain();   }        final class AsyncCall extends NamedRunnable{        protected void execute(){            //內部類AsyncCall也可以呼叫它            RealCall.this.getResponseWithInterceptorChain();       }   } }

我們發現有一個方法 getResponseWithInterceptorChain() 被他們共用了。

我們需要認識到,AsyncCall 實現了 Runnable 介面,所以它執行的邏輯需要放在 run() 方法中執行,我們可以把程式碼解耦,將對執行緒的命名和請求執行邏輯分離開來:

  • 具體執行邏輯寫在 execute() 方法中
  • run() 除了呼叫 execute() 還要對執行緒進行命名

AsyncCall 繼承的 NamedRunnable 的 run() 方法就做了上述的邏輯解耦,讓程式碼更清晰:

public abstract class NamedRunnable implements Runnable {    //...    public final void run() {              String oldName = Thread.currentThread().getName();        Thread.currentThread().setName(this.name); ​        try {            this.execute();       } finally {            Thread.currentThread().setName(oldName);       } ​   } //可以理解為這是一個模板方法設計模式,將具體的execute()邏輯讓子類實現,自己只負責對執行緒命名    protected abstract void execute(); }

我們注意到這裡的命名很有意思,先把執行緒舊的名字保留,在執行 execute() 期間,執行緒的名字是 this.name,執行結束後,執行緒的名字又變成了原先的名字。

不難理解,線上程複用的場景裡,我們不希望對借用的執行緒產生影響,“原物歸還”是一個好習慣,所以在試用期間,我們改了名字,但是當前執行緒執行完這個任務後,名字需要改回去。避免別人無法通過原先的名字找到它

他們還有什麼區別呢?從設計他們的初衷來看,希望 RealCall 用來執行同步請求,AsyncCall 要用來執行非同步請求,所以 AsyncCall 中自然就有提交任務到執行緒池的步驟,而 RealCall 只有同步執行的步驟。

我們先來看一下 RealCall 的 execute() 方法,執行同步任務。

3. Response response = call.execute() 執行同步任務

由 okhttpclient.newCall() 創建出來的,是RealCall物件,如果你想要的執行同步任務,呼叫它的 execute() 方法即可。

我們來看一下 RealCall 提交同步任務的步驟:

final class RealCall implements Call{    public Response execute() throws IOException{        //臨界區標誌修改,需要加鎖        synchronized(this){            this.executed = true;       }                this.transmitter.timeoutEnter();        this.transmitter.callStart();        Response var1;        try {            //1. 將同步請求記錄在runningSyncCalls集合中,外界可以通過 runningSyncCalls 獲取到當前有多少同步任務被提交            this.client.dispatcher().executed(this);            //2. 直接在當前執行緒環境下,執行網路請求            var1 = this.getResponseWithInterceptorChain();       } finally {            //3. 執行完畢後,將同步請求從記錄中移除            this.client.dispatcher().finished(this);       }        return var1;   } }

在通過 getResponseWithInterceptorChain() 進行攔截器處理和網路請求之前,記錄在 runningSyncCalls 佇列中,執行完成之後,不論是否有異常,都將它從該記錄佇列中移除。我們只需要注意到,getReponseWithInterceptorChian() 在 RealCall.execute() 中,是在當前呼叫者所在的執行緒環境下發起的,所以是同步請求,具體 getResponseWithInterceptorChain() 到底做了什麼我們後面分析。

4. call.enqueue( callback ) 提交非同步任務

由 okhttpclient.newCall() 創建出來的,是RealCall物件,如果你想要的執行同步任務,呼叫它的 execute() 方法即可,如果你想要的執行非同步任務,呼叫它的 enqueue() 方法,RealCall 內部會幫你把任務轉變為 AsyncCall,提交為非同步任務:

Call realCall = okhttpClient.newCall(request);//將request封裝在RealCall物件中 realCall.enqueue(new Callback(){...});//發起非同步請求

為了解耦,我們將非同步任務的執行邏輯寫在了 AsyncCall 這個內部類中,而 request 仍然封裝在 RealCall 這個外部類中。AsyncCall 需要 request 的時候直接在外部類拿就好,程式碼很簡潔,結構很清晰。我們只需要讓 AsyncCall 來執行非同步任務請求的邏輯就好了:

//RealCall.java public void enqueue(Callback callback) {    //修改臨界區資源需要加鎖    synchronized(this) {        this.executed = true;   }    this.transmitter.callStart();    //避免破壞RealCall的結構,新加的Callback回撥物件,寫在了 AsyncCall 裡面,讓AsyncCall去管理    AsyncCall asyncCall = new RealCall.AsyncCall(callback);    this.client.dispatcher().enqueue(asyncCall); }

我們知道框架總是不斷更新的,在更新的過程中,需要增加功能,要符合對拓展開放,對修改關閉的設計原則。我猜測 OkHttp 是後來新增的非同步任務請求的功能,但是原先的 RealCall 已經實現好了,而非同步請求 AsyncCall 的邏輯可以複用很多 RealCall 的邏輯,所以將 AsyncCall 設計為了內部類,和外部類共享成員變數!非同步需要的回撥介面,也不直接設計為 RealCall 的成員變數,因為要避免對原有程式碼的破壞,所以寫在了 AsyncCall 中!

RealCall 建立好一個 AsyncCall 例項後,就交給排程器去發起非同步請求,我們先要注意到 AsyncCall 實現了 Runnable,然後我們看一下 dispatcher.enqueue() 做了什麼:

//Dispatcher.java void enqueue(AsyncCall call) {    synchronized(this) {        //1. 將AsyncCall 放入非同步請求準備佇列中        this.readyAsyncCalls.add(call);   }    //2. 嘗試將準備佇列中的AsyncCall拿去執行    this.promoteAndExecute(); }

Dispatcher 首先將 AsyncCall 放入非同步請求佇列 readyAsyncCalls 中,然後看看是否滿足非同步請求條件,如果滿足就可以嘗試發起非同步請求!我們來看一下 promoteAndExecute() 中做了什麼:

//Dispatcher.java private boolean promoteAndExecute() {    //1. 這個集合存了從 readyAsyncCalls 準備佇列中取出來可以執行的 AsyncCall 非同步任務    List<AsyncCall> executableCalls = new ArrayList();    boolean isRunning;    AsyncCall asyncCall;    //需要注意,runningAsyncCalls類似的這些佇列也是臨界區資源,也需要上鎖使用    synchronized(this) {        Iterator i = this.readyAsyncCalls.iterator();        while(true) {            if (i.hasNext()) {                asyncCall = (AsyncCall)i.next();                //2. OkHttp3預設併發數為64,所以 runningAsyncCalls 當前可以執行非同步任務的佇列長度設定為 64                if (this.runningAsyncCalls.size() < this.maxRequests) {                    //3. 只要還在併發數要求內,就把它取出來,準備執行!                    //放到 executableCalls 中,用於發起執行                    executableCalls.add(asyncCall);                    //4.記錄到 runningAsyncCalls 中,用於記錄!                    this.runningAsyncCalls.add(asyncCall);               }           }            //當前是否有非同步請求被取出,如果有,認為okhttp為執行狀態            isRunning = this.runningCallsCount() > 0;            break;       }   } ​    int i = 0; //不涉及臨界區資源操作的程式碼,放到臨界區之外來執行,不佔用鎖資源!    for(int size = executableCalls.size(); i < size; ++i) {        asyncCall = (AsyncCall)executableCalls.get(i);        //由此發起AsyncCall的非同步任務!!!        asyncCall.executeOn(this.executorService());   } ​    return isRunning; }

想要執行非同步任務,需要滿足併發數小於64。併發數由 runningAsyncCalls 佇列提供,它記錄了從 readyAsyncCalls 佇列中取出來被執行的任務,所以這條“記錄”是個臨界區資源,使用需要上鎖。

其次,臨界區程式碼不應該太多,執行時間儘量要短,所以synchronized修飾的程式碼塊中,只有讓 AsyncCall 在集合中轉移,並沒有進行實際的執行工作。執行工作放到了同步程式碼塊之後執行!我們注意發起非同步任務的程式碼是:

asyncCall.executeOn(this.executorService());

看變數名字,我們可以猜到,Dispatcher將執行緒池的引用交給了AsyncCall,希望 AsyncCall 的非同步任務在提供的執行緒池中執行。具體如何執行,讓 asyncCall.executeOn() 方法來執行。我們回到 AsyncCall 物件中,來看看是如何發起非同步任務的:

//RealCall.AsyncCall void executeOn(ExecutorService executorService) {    assert !Thread.holdsLock(RealCall.this.client.dispatcher()); //傳入了一個執行緒池物件    boolean success = false; ​    try {        //AsyncCall實現了Runnable介面,其run()方法呼叫了AsyncCall.execute()方法,即具體進行網路請求的邏輯        executorService.execute(this);        success = true;   } catch (RejectedExecutionException var8) {        //發生錯誤的回撥        this.responseCallback.onFailure(RealCall.this, ioException);   } finally {        if (!success) {            //失敗的後續處理            RealCall.this.client.dispatcher().finished(this);       }   } }

我們記得,AsyncCall實現了Runnable介面,其run()方法呼叫了AsyncCall.execute()方法,即具體進行網路請求的邏輯。設計者通過 executorService.execute(this) 將 runnable 例項提交給執行緒池去執行!

當執行緒池排程到當前AsyncCall的時候,執行其execute()方法。和 RealCall 一樣,都叫做 execute() 方法,區別是 AsyncCall 的 execute() 方法在內部的執行緒池中執行!而 RealCall 的 execute() 方法則是在呼叫者的執行緒中同步執行。

我們來看 AsyncCall 的 execute() 方法

protected void execute() {    try {        //發起網路請求,阻塞等待response處理並返回        Response response = RealCall.this.getResponseWithInterceptorChain();        //將response回調出去        this.responseCallback.onResponse(RealCall.this, response);   } catch (IOException var8) { //發生異常的回撥        this.responseCallback.onFailure(RealCall.this, var8); ​   } catch (Throwable var9) {        //如果發生了異常,將這個非同步任務取消        RealCall.this.cancel();   } finally {        //執行結束後,將asyncCall從runningAsyncCalls記錄佇列中剔除,對外表示併發數/任務數 -1        RealCall.this.client.dispatcher().finished(this);   } }

AsyncCall 的 execute() 方法執行在內部的執行緒池中!首先通過 getResponseWithInterceptorChain() 發起網路請求,並獲得Response響應,通過callback將response回調出去。任務如果出錯,呼叫 call.cancel() 來告知上層任務失敗並取消,最後將 asyncCall從runningAsyncCalls記錄佇列中剔除,對外表示當前併發數/任務數減少了。

和 RealCall 中的 runningSyncCalls 的設計一樣,是臨界區資源,其用途之一,就是記錄當前任務數。至於為什麼不僅用一個變數來記錄我還沒深入探究,我猜測應該這個佇列還有其他用途,大家可以參考其他部落格分享。

探討完 RealCall 和 AsyncCall 發起同步或者非同步任務的邏輯之後,我們重點關注一下 getResponseWithInterceptorChain() 到底是如何發起網路請求的:

5. getResponseWithInterceptorChain() 真正發起socket通訊

程式碼和邏輯比較多,這裡我先說一下大概邏輯:

  1. 首先把使用者設計的攔截器,以及OkHttp內部實現的攔截器進行一個彙總

  2. 通過責任鏈的設計模式,逐個對 request 進行處理

  3. 其中,有兩個攔截器比較重要:

    1. ConnectInterceptor 攔截器用於維護socket連線池
    2. CallServerInterceptor 攔截器用於傳送處理後的request請求,並接收返回的response。

責任鏈的邏輯我畫了一張圖,大概是這樣子的:

interceptors.jpg

我來解釋一下:過程中,所有 RealInterceptorChain例項都引用著相同的資料,包括 interceptors 集合,call例項,OkHttpClient中設定的超時時間等。唯一變化的是 index。chain.proceed() 方法通過 index 來判斷當前輪到攔截器集合中的哪一個來執行 intercept(next) 方法。這裡的next是下一個chain例項,注意它的 index+1 。也就是下個 chain例項將會呼叫下標為 index+1 的攔截器去執行相關操作。

我們回到程式碼:

//RealCall.java Response getResponseWithInterceptorChain() throws IOException {    //所有攔截器的集合    List<Intetceptor> interceptors = new ArrayList<>();    //RealInterceptorChain責任鏈的主導者,只需要注意到傳入了攔截器集合,以及當前目標攔截器的下標,以及一些基本設定。    Interceptor.Chain chain = new RealInterceptorChain(interceptors,transmitter,null,0,request,call.this,client.connectTimeoutMillis(),client.readTimeoutMillis(),client.writeTimeoutMillis());    //發起呼叫,chain將會逐個地呼叫攔截器,進行request的處理    Response response = chain.proceed(request); }

getResponseWithInterceptorChain() 是整個責任鏈排程的起點,它整合了所有攔截器,放在 interceptors 集合中。並初始化了一個 index=0 的 RealInterceptorChain。chain將會通過 proceed() 來呼叫當前攔截器的intercept()方法:

//RealInterceptorChain.java public Response proceed(Request,Transmitter transmitter,Exchange exchange){    //...    //新的一個chain,注意index為 index+1,說明下一個呼叫的攔截器的下標相對當前的,是+1的    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,index + 1, request, call, connectTimeout, readTimeout, writeTimeout);    //獲得當前攔截器    Interceptor interceptor = interceptors.get(index);    //呼叫當前攔截器的intercept() 方法    Response response = interceptor.intercept(next); }

看到攔截器中責任鏈呼叫部分的程式碼:

//ConnectInterceptor.java @Override public Response intercept(Chain chain) throws IOException {    RealInterceptorChain realChain = (RealInterceptorChain) chain;    Request request = realChain.request();    //攔截器對request做一些處理,然後再交給下一層的chain    return realChain.proceed(request, transmitter, exchange); }

intercept()傳入的chain的index是當前攔截器index+1的值。攔截器先對request做一些自己的處理,然後讓index=index+1的chain去繼續呼叫下一個攔截器。

我們發現,chain不僅僅用來尋找當前是哪個攔截器要執行,還儲存了被之前攔截器處理過的 request物件,以及一些新的變數,例如ConnectionInterceptor攔截器還會建立一個 exchange 交給 chain來保管。

所以 chain 的主要任務就是資料託管,按順序讓interceptor來處理資料。

等到最後一個 interceptor 處理完request之後,就會請求網路,得到 response,再通過逐層的 return 返回,從 getResponseWithInterceptorChain() 中返回!

我們理解了 RealInterceptorChain 的作用,以及 Interceptors 被責任鏈式地呼叫過程。我們重點關注一下 Okhttp 對連線池的優化,以及對 socket 請求發起的執行邏輯:

6. RealConnectionPool 連線池

首先我們來關注一下 Socket 連線池。我們知道,socket 是基於 TCP 協議的網路請求工具,建立連線和釋放連線,分別需要三次握手和四次揮手,這是比較耗時的。通過維持 socekt 的連線,我們可以增大通訊時間的佔比,從而讓建立連線的時間忽略不計,達到效能的優化。

RealConnectionPool 維護了 socket 連線池。Exchange中的ExchangeFinder維護了RealConnectionPool連線池,RealConnection物件中就有socket例項。

我們來看一下 ConnectionInterceptor是如何獲取socekt連線的:

作為攔截器,ConnectionInterceptor 在對 reqeuest 進行處理的同時,獲取了一個Exchange物件:

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();        boolean doExtensiveHealthChecks = !request.method().equals("GET");        //獲得了一個 exchange,可以通過這個物件操作socekt        Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);        return realChain.proceed(request, transmitter, exchange);   } }

通過 transmitter.newExchange() 來獲取一個exchange:

  1. 通過ExchangeFinder獲取一個 ExchangeCodec
  2. 將這個ExchangeCodec連同其他資訊一同包裝在 Exchange 例項中

//Transmitter.java Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {    //通過ExchangeFinder獲取到一個 ExchangeCodec    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);    return result; }

我們注意到 ExchangeFinder 的成員變數中有 RealConnectionPool,明白了,連線池的管理是 ExchangeFinder 來做的,它維護著連線池,如果外界需要使用連線,它將可用的連線通過 ExchangeCodec 物件發揮出去,提供外界使用。而具體的連線邏輯等,都寫在內部。對外人來說,具體實現就是黑盒,外界只要知道,拿到了 exchange,就說明可以通過這個叫做“exchange”的工具去進行網路請求。

final class ExchangeFinder {    private final RealConnectionPool connectionPool; ​    //1. 從 chain 中獲取使用者設定的資料    public ExchangeCodec find(        OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {        //從 chain 中獲取到使用者的設定        int connectTimeout = chain.connectTimeoutMillis();        int readTimeout = chain.readTimeoutMillis();        int writeTimeout = chain.writeTimeoutMillis();        int pingIntervalMillis = client.pingIntervalMillis();        boolean connectionRetryEnabled = client.retryOnConnectionFailure();        //2. 通過這些設定去尋找合適的連線        try {            RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);            //如果找到了,包裝在ExchangeCodec中,返回出去            return resultConnection.newCodec(client, chain);       } catch (RouteException e) {            trackFailure();            throw e;       } catch (IOException e) {            trackFailure();            throw new RouteException(e);       }   }     //2.通過使用者的設定,去尋找合適的連線    private RealConnection findHealthyConnection(...) throws IOException {        while (true) {            RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,pingIntervalMillis, connectionRetryEnabled); //將合適的連線交出去            return candidate;       }   }     }

可以看到,ExchangeFinder通過逐層的檢驗,最後需要通過 findConnection,將RealConnection 以 ExchangeCodec的方式返回出去,再由transmitter包裝進 Exchange 物件返回給使用者使用。findConnection() 是最核心的獲取連線的邏輯,程式碼很長,我們來看看核心。

先來看前半段:

  1. 首先嚐試獲取 transmitter 中當前使用的已連線的 connection
  2. 然後嘗試從realConnectionPool 連線池中,取一個已連線的 connection
  3. 如果前兩步成功取到了一個已連線的connection,就可以返回出去複用了!

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) {        //1. 先嚐試獲取 transmitter 中已連線的 connection        if (transmitter.connection != null) {            // We had an already-allocated connection and it's good.            result = transmitter.connection;            releasedConnection = null;       } //如果沒有已連線的connection        if (result == null) {            //2. 嘗試從連線池中,獲取一個已連線的 connection,由於傳入的router路由集合是空,所以只有可能獲取到那些http2條件下可以多路複用的連結!            if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {                result = transmitter.connection;           }       }   }    //如果我們已經獲取到了可複用的 connection,就直接返回,交給上層使用    if (result != null) {        return result;   } }

再來看後半段:

``` private RealConnection findConnection(){    //如果已經取到了可複用的已連線的 connection,上面就返回出去了    //到這裡說明沒有可用的連結,我們就需要開始建立連線了:    // 如果我們需要路由選擇,就獲取一個(阻塞從routeSelector中獲取)    boolean newRouteSelection = false;    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {        newRouteSelection = true;        routeSelection = routeSelector.next();   }

List routes = null;    synchronized (connectionPool) { //如果需要路由選擇,        if (newRouteSelection) {            //1. 現在我們已經有了一組IP地址,請再次嘗試從池中獲取連線。由於連接合並,這可能匹配。如果連線池中的 connection符合這個路由匹配,就可以使用!            routes = routeSelection.getAll();            if (connectionPool.transmitterAcquirePooledConnection(                address, transmitter, routes, false)) {                foundPooledConnection = true;                result = transmitter.connection;           }       } //如果連線池中沒有獲取到可以路由合併(超網聚合)的連線connection        if (!foundPooledConnection) {            if (selectedRoute == null) {                selectedRoute = routeSelection.next();           } //2. 那麼就主動建立一個新的連結,引用交給result            result = new RealConnection(connectionPool, selectedRoute);            connectingConnection = result;       }   }

//如果從連線池中獲取到了可以路由合併的連線connection    if (foundPooledConnection) {        eventListener.connectionAcquired(call, result);        //將這個connection返回出去        return result;   } ​    //到這裡,說明連線池中無論如何也獲取不到可以用的 connection 了。    //那麼我們就需要讓剛建立的 result,這個 RealConnection 去進行一個建立連線:TCP三次握手,會阻塞在這裡,直到連結完成    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,                   connectionRetryEnabled, call, eventListener);    //連線完成後,將路由新增到連線池中,以備查詢和複用    connectionPool.routeDatabase.connected(result.route()); //到這裡,連線池中一定存在一個可用的已連線的connection 了!    Socket socket = null;    synchronized (connectionPool) {        connectingConnection = null;       //最後一次嘗試連接合並,只有當我們嘗試多個併發連線到同一主機時才會發生連接合並。        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);       }   }    //上面的判斷說明,如果這裡socket不為空,說明剛建立的socket連線是一個不健康的合併連線,我們要把它給關了!    closeQuietly(socket);    eventListener.connectionAcquired(call, result);    //返回連線池(如果成功了,連線池中就有剛建立好的連結,如果失敗了,池中就沒有,需要再次去嘗試)    return result; } ```

簡單來說 ExchangeFinder 管理著連線池,如果有可複用的連線 RealConnection,或者可以合併連線的RealConnection,都是可以的。如果沒有就要通過 TCP+TLS 握手,進行連線的建立。這裡的TCP握手是阻塞的。中間過程程式碼太繁雜,大家可以一路點進去,發現中間配置好了 InetSocketAdress,以及 connectTimeout,然後呼叫到 socket.connect(address,connectTimeout); 進行socekt連線!

連線建立完成後,提交到連線池的路由資料庫中。然後再次嘗試從連線池中獲取可用的連線。

7. CallServerInterceptor

最後我們看到 CallServerInterceptor 這個攔截器對 request的處理,是真正地進行網路請求!利用的是之前 ConnectionInterceptor 中獲取到的可用的socket 連線。

//CallServerInterceptor.java public Response intercept(Chain chain){    RealInterceptorChain realChain = (RealInterceptorChain) chain;    //拿到了從ConnectionInterceptor中拿到的可用的連線,它被裝在了Exchange例項中    Exchange exchange = realChain.exchange();    //被之前的攔截器處理好的,即將用來發送的request    Request request = realChain.request();    //將request的頭寫入outputstream    exchange.writeRequestHeaders(request);    //如果有請求體,寫入bufferedRequestBody,然後再寫入connection,然後傳送    request.body.writeTo(bufferedRequestBody);        //獲得返回內容,生成一個ResponseBuilder    responseBuilder= exchange.readReponseHeaders();    //獲得請求體    responseBody = exchange.openResponseBody(response);    //將response拼裝好後,返回    return response; }

傳送請求頭後,如果收到了100 Continue

  • 客戶端應當繼續傳送請求。這個臨時響應是用來通知客戶端它的部分請求已經被伺服器接收,且仍未被拒絕。客戶端應當繼續傳送請求的剩餘部分,或者如果請求已經完成,忽略這個響應.伺服器必須在請求完成後向客戶端傳送一個最終響應。HTTP/1.1 可用

接收到響應後,如果收到了 100 Continue,同上,說明服務端可能還有資料要發過來,比如除了已經發送的header以外,還要傳送body。

如果收到了 code = 101 Switching Protocal 協議切換

  • 伺服器已經理解了客戶端的請求,並將通過Upgrade訊息頭通知客戶端採用不同的協議來完成這個請求。在傳送完這個響應最後的空行後,伺服器將會切換到 在Upgrade訊息頭中定義的那些協議。只有在切換新的協議更有好處的時候才應該採取類似措施。例如:切換到新的HTTP版本比舊版本更有優勢,或者切換到一個實時且同步的協議以傳送利用此類特性的資源。HTTP/1.1 可用。

如果收到了code=204或者code=205:

  • 204 No Content: 狀態碼204表示請求已經執行成功,但沒有內容
  • 205 Reset Content 表示伺服器成功地處理了客戶端的請求,要求客戶端重置它傳送請求時的文件檢視。這個響應碼跟 204 No Content 類似,也不返回任何內容