Android框架源碼分析-OkHttp3
淺析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通信
代碼和邏輯比較多,這裏我先説一下大概邏輯:
-
首先把用户設計的攔截器,以及OkHttp內部實現的攔截器進行一個彙總
-
通過責任鏈的設計模式,逐個對 request 進行處理
-
其中,有兩個攔截器比較重要:
- ConnectInterceptor 攔截器用於維護socket連接池
- CallServerInterceptor 攔截器用於發送處理後的request請求,並接收返回的response。
責任鏈的邏輯我畫了一張圖,大概是這樣子的:
我來解釋一下:過程中,所有 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:
- 通過ExchangeFinder獲取一個 ExchangeCodec
- 將這個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() 是最核心的獲取連接的邏輯,代碼很長,我們來看看核心。
先來看前半段:
- 首先嚐試獲取 transmitter 中當前使用的已連接的 connection
- 然後嘗試從realConnectionPool 連接池中,取一個已連接的 connection
- 如果前兩步成功取到了一個已連接的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
//如果從連接池中獲取到了可以路由合併的連接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
類似,也不返回任何內容