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 類似,也不返回任何內容