OkHttp3源碼解析(二)——網(wǎng)絡(luò)連接的管理(多路復(fù)用,連接池)

目錄


目錄

一臣镣、提出問(wèn)題

1.OkHttp底層也是通過(guò)Socket發(fā)送和接收請(qǐng)求辅愿,是如何支持http/https請(qǐng)求的?
2.連接池的實(shí)現(xiàn)原理忆某,如何支持多路復(fù)用点待?怎樣從連接池選擇復(fù)用連接?
3.如何處理代理弃舒?
4.Route癞埠、ConnectionPool、RealConnection聋呢、steamAllocation苗踪、HttpCodec分別的作用,如何協(xié)作削锰?
5.重定向請(qǐng)求或重試的處理流程通铲?
6.如何支持http2協(xié)議?

如果剛開(kāi)始學(xué)習(xí)OkHttp源碼或?qū)Υ聿涣私獾钠鞣罚梢韵群雎源聿糠值倪壿嬄幔雀闱宄边B請(qǐng)求的流程。OkHttp源碼之所以復(fù)雜一部分原因是處理了代理和路由蛹稍,但代理部分實(shí)際項(xiàng)目可能用不上吧黄。如果想深入了解OkHttp的代理,可以閱讀:OkHttp源碼解析 (三)——代理和路由(http://www.reibang.com/p/63ba15d8877a)稳摄。

二、網(wǎng)絡(luò)管理涉及的角色及作用

網(wǎng)絡(luò)管理涉及的角色及作用

外部發(fā)起的一次請(qǐng)求封裝為一個(gè)RealCall饲宿,一個(gè)RealCall可能對(duì)應(yīng)多個(gè)Request厦酬,如初始請(qǐng)求及后續(xù)的重定向請(qǐng)求,而每一個(gè)Request會(huì)創(chuàng)建一個(gè)StreamAllocation來(lái)管理連接瘫想,尋找合適的RealConnection仗阅,一個(gè)Call的所有Request偏向用同一個(gè)RealConnection,對(duì)于HTTP/1.x的請(qǐng)求国夜,RealConnection同時(shí)只持有一個(gè)StreamAllocation减噪,對(duì)于HTTP/2可以同時(shí)持有多個(gè)StreamAllocation。角色的對(duì)應(yīng)關(guān)系如下圖。


角色對(duì)應(yīng)關(guān)系.png

三筹裕、各個(gè)角色的協(xié)作

各個(gè)角色如何協(xié)作完成網(wǎng)絡(luò)請(qǐng)求

1醋闭、在RetryAndFollowUpInterceptor攔截器中,為新請(qǐng)求創(chuàng)建流StreamAllocation朝卒,如果請(qǐng)求返回需要重定向证逻,創(chuàng)建重定向Request及新的StreamAllocation,繼續(xù)上面的邏輯抗斤。

2囚企、在ConnectInterceptor攔截器中,StreamAllocation選擇連接RealConnection:
第一步:優(yōu)先從連接池(connectionPool)中尋找瑞眼,有合適的則直接復(fù)用龙宏;
第二步:如果沒(méi)有則創(chuàng)建新的RealConnection,并加入到連接池中伤疙,新創(chuàng)建的連接通過(guò)connect方法完成socket的三次握手银酗,與服務(wù)器建立連接;
第三步:建立連接后獲得網(wǎng)絡(luò)寫(xiě)入流(BufferedSink掩浙,封裝了InputStream)和讀取流(BufferedSource花吟,封裝了outputStream);
第四步:最后創(chuàng)建HttpCodec厨姚,持有BufferedSink和BufferedSource衅澈,后續(xù)寫(xiě)入請(qǐng)求和讀取響應(yīng)通過(guò)HttpCodec操作。

3谬墙、在CallServerInterceptor攔截器中今布,通過(guò)HttpCodec實(shí)現(xiàn)真正發(fā)送請(qǐng)求和讀取服務(wù)器響應(yīng),最后構(gòu)造Response并沿鏈路返回給上一級(jí)的攔截器拭抬。

各攔截器的關(guān)鍵代碼:
RetryAndFollowUpInteceptor:

public final class RetryAndFollowUpInterceptor implements Interceptor {
 @Override public Response intercept(Chain chain) throws IOException {
    ...
    //原始請(qǐng)求
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    ...
    while (true) {
             ...
             //原始請(qǐng)求返回
             response = realChain.proceed(request, streamAllocation, null, null);
             ...
             //創(chuàng)建重定向請(qǐng)求
             Request followUp = followUpRequest(response, streamAllocation.route());
             ... 
             //重定向StreamAllocation
             streamAllocation = new StreamAllocation(client.connectionPool(),createAddress(followUp.url()), call, eventListener, callStackTrace);
    }
}

ConnectInterceptor:

public final class ConnectInterceptor implements Interceptor {
  public Response intercept(Chain chain) throws IOException {
      ...
     //從攔截器鏈里得到StreamAllocation對(duì)象
     StreamAllocation streamAllocation = realChain.streamAllocation();
     //尋找合適的連接并返回讀寫(xiě)流
     HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
     //獲取realConnetion
     RealConnection connection = streamAllocation.connection();
     //執(zhí)行下一個(gè)攔截器
     return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

CallServerInterceptor:

public final class CallServerInterceptor implements Interceptor {
  public Response intercept(Chain chain) throws IOException {
         HttpCodec httpCodec = realChain.httpStream();
         Request request = realChain.request();
         //發(fā)送請(qǐng)求頭和請(qǐng)求行(寫(xiě)入到緩沖區(qū))
         httpCodec.writeRequestHeaders(request);
          if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
                ...//發(fā)送body部分部默,post請(qǐng)求和"100-continue"請(qǐng)求
          }
          //flush正在發(fā)送
          httpCodec.finishRequest();
          //讀取響應(yīng)
          if (responseBuilder == null) {
                   realChain.eventListener().responseHeadersStart(realChain.call());
                   //讀取頭部
                    responseBuilder = httpCodec.readResponseHeaders(false);
           }
          //構(gòu)造響應(yīng)Response
          Response response = responseBuilder.request(request)
                                              .handshake(streamAllocation.connection().handshake())
                                              .sentRequestAtMillis(sentRequestMillis)
                                              .receivedResponseAtMillis(System.currentTimeMillis())
                                              .build();
            ...
           //返回Response 
           return response;
  }
}

四、詳解各個(gè)角色的邏輯

(一) StreamAllocation

解釋?zhuān)毫鞣峙淦髟旎ⅲ饕δ苁枪芾硪淮芜B接上的流傅蹂,為Request尋找合適的Realconnection,并獲取網(wǎng)絡(luò)讀寫(xiě)流算凿。重定向會(huì)創(chuàng)建新的StreamAllocation份蝴。每個(gè)Connection 有個(gè)變量allocationLimit,用于定義可以承載的并發(fā)的 streams 的數(shù)量氓轰。HTTP/1.x 的 Connection 一次只能有一個(gè)stream婚夫, HTTP/2 一般可以有多個(gè)。

public final class StreamAllocation {
   public final Address address;//請(qǐng)求地址
   private RouteSelector.Selection routeSelection;//可選路由列表
   private Route route;//選中的路由
   private final ConnectionPool connectionPool;//連接池
   public final Call call;//請(qǐng)求call
   // State guarded by connectionPool.
   private final RouteSelector routeSelector;//路由選擇器
   private HttpCodec codec;//編碼網(wǎng)絡(luò)請(qǐng)求和響應(yīng)

   public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
         EventListener eventListener, Object callStackTrace) {
      this.connectionPool = connectionPool;
      this.address = address;
      this.call = call;
      this.eventListener = eventListener;
      this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
      this.callStackTrace = callStackTrace;
      }
      /*
       * 獲取流署鸡,通過(guò)findConnection得到連接案糙,再獲取讀寫(xiě)流
       */
      public HttpCodec newStream(...) {}
      /*
       * 尋找可復(fù)用連接及判斷是否"健康"限嫌,如果不“健康”則繼續(xù)循環(huán)直至找到“健康”連接
       */
      private RealConnection findHealthyConnection(...) throws IOException {}
      /*
       * 為新stream尋找可復(fù)用連接,可能來(lái)自連接池时捌,如果沒(méi)有則新建
       */
      private RealConnection findConnection(...){}
      /*
       * 釋放當(dāng)前持有的連接怒医,如果連接是限制分配給新流的(noNewSteam為true),則返回socket進(jìn)行關(guān)閉匣椰。
       * 對(duì)于HTTP/2裆熙,多個(gè)請(qǐng)求共享一個(gè)連接,所以對(duì)于follow-up請(qǐng)求期間可能被限制分配新流
       */
      private Socket releaseIfNoNewStreams(){}
      /*
       * 請(qǐng)求已經(jīng)完成禽笑,從連接中移除正在當(dāng)前執(zhí)行的流入录,只有移除了連接才能被復(fù)用
       */
      public void streamFinished(){}
      /*
       * 禁止在承載此分配的連接上創(chuàng)建新流
       */
      public void noNewStreams(){} 
      /*
       * 取消或拋異常,釋放連接
       */
      public void release() {}
      /*
       * 釋放此流所持有的資源佳镜。如果分配了足夠的資源僚稿,連接將被分離或關(guān)閉。調(diào)用者必須在連接池上同步蟀伸。
       */
      private Socket deallocate(){}
}

(1)newStream方法
下面看下為新請(qǐng)求尋找連接獲取讀寫(xiě)流的邏輯蚀同。

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

方法比較簡(jiǎn)單,第一步尋找連接RealConnection啊掏,第二步獲取網(wǎng)絡(luò)讀寫(xiě)流HttpCodec蠢络,有點(diǎn)小疑問(wèn),為什么要先同步連接池再返回HttpCodec迟蜜。

(2)findHealthyConnection方法

/**
    * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
    * until a healthy connection is found.
    * 尋找連接刹孔,如果是“健康”的則返回,如果不是繼續(xù)循環(huán)尋找娜睛。
    */
   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.
         //如果是新創(chuàng)建的連接髓霞,則不需判斷是否“健康”,直接返回
         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.
         //檢查連接是否良好畦戒,如果不是方库,則從連接池移除,然后繼續(xù)尋找
         if (!candidate.isHealthy(doExtensiveHealthChecks)) {
            noNewStreams();
            continue;
         }
         return candidate;
      }
   }

1障斋、先調(diào)用findConnectoion方法返回連接對(duì)象RealConnection纵潦;
2、根據(jù)RealConnection的屬性successCount=0判斷連接是新創(chuàng)建的垃环,新創(chuàng)建的連接不需要判斷是否“健康”邀层,直接返回;
3晴裹、如果successCount大于0被济,表示連接早已經(jīng)創(chuàng)建救赐,是從連接池中獲取得到涧团,這時(shí)需要判斷連接是否“健康”只磷;
4、如果非“健康”連接泌绣,則設(shè)置該連接不允許承載新的流钮追,繼續(xù)第一步;
findConnection及isHealthy的邏輯后面會(huì)分析阿迈。

(3)findConnection方法

/**
    * Returns a connection to host a new stream. This prefers the existing connection if it exists,
    * then the pool, finally building a new connection.
    * 為新流返回連接元媚,優(yōu)先從連接池中尋找復(fù)用,如果沒(méi)有最終會(huì)創(chuàng)建新的連接
    */
   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;//需要關(guān)閉的socket
      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.
       //苗沧?刊棕?沒(méi)想懂什么場(chǎng)景這個(gè)地方已經(jīng)分配了連接
         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.
            //從連接池中獲取一個(gè)連接,通過(guò)傳入this對(duì)象待逞,尋找到合適連接會(huì)賦值this.connection
            Internal.instance.get(connectionPool, address, this, null);
            if (connection != null) {
               //尋找到合適的連接
               foundPooledConnection = true;
               result = connection;
            } else {
               //連接池沒(méi)有合適的連接甥角,可能已經(jīng)有路由信息(什么場(chǎng)景)
               selectedRoute = route;
            }
         }
      }
      //?识樱?什么場(chǎng)景
      closeQuietly(toClose);
      ...
      if (result != null) {
         // If we found an already-allocated or pooled connection, we're done.
       //如果已經(jīng)分配連接或者從連接池中尋找到合適的嗤无,則返回
         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.
           // 現(xiàn)在已經(jīng)有一個(gè)ip地址集合,再次嘗試重連接池中尋找可復(fù)用連接
            List 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) {//連接池中沒(méi)有找到合適的連接
            if (selectedRoute == null) {
               selectedRoute = routeSelection.next();//即將根據(jù)該路由創(chuàng)建新的連接
            }

            // 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.
          //創(chuàng)建新的連接并立即分配給當(dāng)前的流怜庸,這樣允許異步調(diào)用取消方法來(lái)打斷即將進(jìn)行的握手当犯。
            route = selectedRoute;
            refusedStreamCount = 0;
            result = new RealConnection(connectionPool, selectedRoute);
            acquire(result, false);//分配連接給當(dāng)前的流
         }
      }

      // 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.
      //新創(chuàng)建的連接割疾,建立與服務(wù)器的連接嚎卫,方法內(nèi)會(huì)根據(jù)平臺(tái)調(diào)用socket.connnect()
      result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);
      //路由庫(kù)記錄新連接的路由
      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;
   }

總結(jié)findConnection的流程如下:
1杈曲、先判斷是否已經(jīng)分配了連接驰凛,有則返回(沒(méi)想懂是什么場(chǎng)景);
2担扑、沒(méi)有則根據(jù)address從連接池中找可重用的連接Internal.intance.get(connectionPool,address,this,null)恰响,找到則返回;
3涌献、如果沒(méi)有確定路由胚宦,則需要嘗試新的路由,通過(guò)路由選擇器返回路由集合燕垃,這時(shí)得到一個(gè)ip地址集合枢劝;
4、遍歷路由集合再次從連接池中尋找可復(fù)用的連接卜壕,有則設(shè)為待返回的連接您旁;
5、如最終從連接池中沒(méi)有找到合適的連接轴捎,則新建連接new RealConnection(connectionPool,selectedRoute)鹤盒,并馬上分配給當(dāng)前的流蚕脏;
6、新建立的連接侦锯,建立與服務(wù)器連接result.connect(即sockect.connect)驼鞭,并把路由記錄到路由庫(kù)中,把創(chuàng)建的連接添加到連接池Internal.intance.put(connectionPool ,result).

(二) RealConnection

解析:建立在Socket之上的物理通信信道尺碰,持有StreamAllocation隊(duì)列挣棕。

public final class RealConnection extends Http2Connection.Listenerimplements Connection {
private final ConnectionPool connectionPool;//連接池
private final Route route; //當(dāng)前連接到路由
private Socket rawSocket; //底層socket
private Socket socket; //應(yīng)用層socket
private Handshake handshake; //https的握手
private Protocol protocol; //協(xié)議
private Http2Connection http2Connection; //HTTP/2的鏈接
private BufferedSource source; //網(wǎng)絡(luò)讀取流
private BufferedSink sink; //網(wǎng)絡(luò)寫(xiě)入流
public boolean noNewStreams;  //標(biāo)識(shí)是否能繼續(xù)添加流,一但設(shè)為true亲桥,則一直為true洛心,不能再添加流
public int allocationLimit =1; //承載流(allocationStream)的最大的并發(fā)數(shù)
public final List> allocations =new ArrayList<>(); //當(dāng)前承載流的集合

public RealConnection(ConnectionPool connectionPool, Route route){}
/*
 * 連接,中創(chuàng)建新RealConnection對(duì)象后調(diào)用题篷,完成socket連接
 */
public void connect(int connectTimeout, int readTimeout, int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled, Call call,EventListener eventListener){}
/*
 * 連接隧道
 */
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,EventListener eventListener)throws IOException{}
/*
 * socket連接皂甘,完成tcp三次握手
 */
private void connectSocket(int connectTimeout, int readTimeout, Call call,EventListener eventListener)throws IOException{}
/*
 * 建立協(xié)議
 */
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,int pingIntervalMillis, Call call, EventListener eventListener)throws IOException{}
/*
 *https請(qǐng)求,建立tls連接
 */
private void connectTls(ConnectionSpecSelector connectionSpecSelector)throws IOException{}
/*
 * 創(chuàng)建通道
 */
private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest,HttpUrl url)throws IOException{}
/*
 * 構(gòu)造創(chuàng)建通道的請(qǐng)求
 */
private Request createTunnelRequest(){}
/*
 *  是否符合條件悼凑,如果能分配新流則返回true偿枕,
 * /
public boolean isEligible(Address address, @Nullable Route route){}
/*
 * 將io流BufferedSource,BufferedSink封裝為HttpCodec 
 */
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,StreamAllocation streamAllocation)throws SocketException{}
/*
 * 是否“健康”户辫,如果準(zhǔn)備好建立新流則返回true
 */
public boolean isHealthy(boolean doExtensiveChecks){}
}

(1)connect方法
新創(chuàng)建RealConnection后渐夸,通過(guò)connect方法服務(wù)器建立連接,完成tcp三次握手渔欢,下面介紹下connect的方法墓塌。

public void connect(int connectTimeout, int readTimeout, int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled, Call call,EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");//只能調(diào)用一次

      RouteException routeException = null;
      List connectionSpecs = route.address().connectionSpecs();
      ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

      if (route.address().sslSocketFactory() == null) {
         if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
            throw new RouteException(new UnknownServiceException(
                  "CLEARTEXT communication not enabled for client"));
         }
         String host = route.address().url().host();
         if (!Platform.get().isCleartextTrafficPermitted(host)) {
            throw new RouteException(new UnknownServiceException(
                  "CLEARTEXT communication to " + host + " not permitted by network security policy"));
         }
      }

      while (true) { //對(duì)應(yīng)SSLHandshakeException/SSLProtocolException的拋錯(cuò),會(huì)重試
         try {
            //判斷是否需要隧道奥额,如果是通過(guò)HTTP代理完成https請(qǐng)求苫幢,則返回true
            //true的條件:address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP
            if (route.requiresTunnel()) {
               //建立隧道,與http代理之間建立socket連接
               connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
               if (rawSocket == null) {
                  // We were unable to connect the tunnel but properly closed down our resources.
                  //無(wú)法與代理建立連接
                  break;
               }
            } else {
               //建立socket連接垫挨,不需要代理
               connectSocket(connectTimeout, readTimeout, call, eventListener);
            }
           //創(chuàng)建協(xié)議韩肝,完成連接
            establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
            eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
            break;
         } catch (IOException e) {
            ...
            //connectionRetryEnabled :是否運(yùn)行重試連接,在okhttpclient的builder設(shè)置九榔,默認(rèn)true
            //connectionSpecSelector.connectionFailed(e)針對(duì)不同的報(bào)錯(cuò)有不同的策略哀峻,返回true則重試
            if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
                   throw routeException;
            }
         }
      }
      ...
      if (http2Connection != null) { //http2運(yùn)行一個(gè)RealConnection建立多個(gè)流
         synchronized (connectionPool) {
            allocationLimit = http2Connection.maxConcurrentStreams();
         }
      }

總結(jié)connect的方法如下:
1、判斷是否需要隧道(隧道代理)哲泊,如果需要?jiǎng)t建立與代理服務(wù)器的sockect連接剩蟀;
2、不需要隧道則直接建立與服務(wù)器的sockect連接切威;
3育特、確定網(wǎng)絡(luò)協(xié)議,如果是https請(qǐng)求則進(jìn)行tls握手先朦;
4缰冤、如果是HTTP/2槽袄,則新建http2Connection來(lái)處理請(qǐng)求,完成HTTP/2的協(xié)議協(xié)商锋谐。

(2)connectSocket方法
解析了連接的整理流程,下面對(duì)其中調(diào)用的方法進(jìn)行分析截酷,首先看下connectSocket方法:

private void connectSocket(int connectTimeout, int readTimeout, Call call,EventListener eventListener) throws IOException {
      Proxy proxy = route.proxy();
      Address address = route.address();
      //如果是直連或者HTTP代理涮拗,則通過(guò)socketFactory創(chuàng)建socket,如果是socket代理迂苛,則直接new Socket
      rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket(): new Socket(proxy);
      eventListener.connectStart(call, route.socketAddress(), proxy);
      rawSocket.setSoTimeout(readTimeout);
      try {
         //根據(jù)不同的平臺(tái)三热,完成socket連接,實(shí)際是socket.connect(address, connectTimeout);
         Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
      } catch (ConnectException e) {
         ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
         ce.initCause(e);
         throw ce;
      }
      try {
         //okio封裝sockect讀寫(xiě)流
         source = Okio.buffer(Okio.source(rawSocket));
         sink = Okio.buffer(Okio.sink(rawSocket));
      } catch (NullPointerException npe) {
         if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
            throw new IOException(npe);
         }
      }
   }

流程比較簡(jiǎn)單三幻,總結(jié)如下:
1就漾、對(duì)于直連及http代理請(qǐng)求,通過(guò)SocketFactory創(chuàng)建socket念搬,對(duì)于SOCKET代理傳入proxy創(chuàng)建socket抑堡;
2、設(shè)置socket超時(shí)時(shí)間朗徊;
3首妖、完成特定平臺(tái)的socket連接,實(shí)際是socket.connect(address, connectTimeout)掉丽;
4遇伞、創(chuàng)建用于I/O的讀寫(xiě)流source 兰珍、sink
這里可以發(fā)現(xiàn),代理請(qǐng)求處理的不同:
· SOCKET代理:傳入代理對(duì)象proxy手動(dòng)創(chuàng)建socket棚壁,其他沒(méi)有什么特別的處理,都交由java標(biāo)準(zhǔn)庫(kù)的socket去處理栈虚,route的socketAddress包含目標(biāo)http服務(wù)器的域名袖外,對(duì)外界而言,不需要做處理魂务。
· HTTP代理:對(duì)于明文的HTTP代理在刺, 也不需要特別的處理,route的socketAddress包含著代理服務(wù)器的IP地址头镊,會(huì)自動(dòng)建立與代理服務(wù)器的連接蚣驼,代理服務(wù)器解析后再轉(zhuǎn)發(fā)請(qǐng)求內(nèi)容。

(3)connectTunnel方法
通過(guò)HTTP代理發(fā)送https請(qǐng)求需要用到隧道代理相艇,也是一種協(xié)定方式颖杏,總結(jié)建立隧道的流程:
1、客戶端發(fā)送CONNECT請(qǐng)求到代理服務(wù)器坛芽,請(qǐng)求建立通道留储;請(qǐng)求會(huì)包含目標(biāo)服務(wù)器的主機(jī)名和端口
2翼抠、代理服務(wù)器與目標(biāo)服務(wù)器建立TCP連接;
3获讳、代理服務(wù)器回應(yīng)客戶端阴颖;
4、客戶端向代理服務(wù)器發(fā)送請(qǐng)求丐膝,代理服務(wù)器原封不動(dòng)轉(zhuǎn)發(fā)客戶端請(qǐng)求(原生TCP packet)量愧;
5、響應(yīng)過(guò)程同請(qǐng)求過(guò)程帅矗。

了解隧道代理的基本流程偎肃,就好理解OkHttp中有關(guān)隧道代理的代碼邏輯了。下面介紹connectTunnel方法:

private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,EventListener eventListener) throws IOException {
      //構(gòu)建連接請(qǐng)求
      Request tunnelRequest = createTunnelRequest();
      HttpUrl url = tunnelRequest.url();
      for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
         //建立與代理服務(wù)器的socket連接
         connectSocket(connectTimeout, readTimeout, call, eventListener);
         //建立隧道浑此,發(fā)送不加密的代理請(qǐng)求并獲取返回結(jié)果
         tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
         if (tunnelRequest == null) break; // 返回null表示已經(jīng)與代理服務(wù)器建立隧道累颂,退出循環(huán)
         
         // The proxy decided to close the connection after an auth challenge. We need to create a new
         // connection, but this time with the auth credentials.
         //如果代理服務(wù)器因身份認(rèn)證問(wèn)題關(guān)閉了連接,需要?jiǎng)?chuàng)建新的連接凛俱,帶上身份憑證
         closeQuietly(rawSocket);
         rawSocket = null;
         sink = null;
         source = null;
         eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null);
      }
}

先創(chuàng)建與代理服務(wù)器的socket連接紊馏,然后再發(fā)送代理請(qǐng)求建立隧道(按照隧道代理的協(xié)議方式)。

(4)createTunnelRequest方法
建立隧道的需要構(gòu)建代理請(qǐng)求蒲犬, 那代理的請(qǐng)求發(fā)了什么瘦棋,下面介紹createTunnelRequest方法:

private Request createTunnelRequest() {
     return new Request.Builder()
           .url(route.address().url())
           .header("Host", Util.hostHeader(route.address().url(), true))
           .header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
           .header("User-Agent", Version.userAgent())
           .build();
}

從代碼可以看到通道請(qǐng)求包括很少的頭部信息,是因?yàn)榕c服務(wù)器代理之間的連接是不加密的暖哨,避免發(fā)生如cookies等敏感信息到代理服務(wù)器赌朋。

(5)createTunnel方法
下面看下再看下如何創(chuàng)建隧道:

private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest,
         HttpUrl url) throws IOException {
      // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
      // 建立隧道的協(xié)議請(qǐng)求內(nèi)容
      String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
      while (true) {//?篇裁?swtich各種分支都會(huì)退出循環(huán)沛慢,沒(méi)想懂循環(huán)的場(chǎng)景
         Http1Codec tunnelConnection = new Http1Codec(null, null, source, sink);
         source.timeout().timeout(readTimeout, MILLISECONDS);
         sink.timeout().timeout(writeTimeout, MILLISECONDS);
         // 發(fā)送代理請(qǐng)求
         tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
         tunnelConnection.finishRequest();
        // 讀取響應(yīng)
         Response response = tunnelConnection.readResponseHeaders(false)
               .request(tunnelRequest)
               .build();
         // The response body from a CONNECT should be empty, but if it is not then we should consume
         // it before proceeding.
         long contentLength = HttpHeaders.contentLength(response);
         if (contentLength == -1L) {
            contentLength = 0L;
         }
         Source body = tunnelConnection.newFixedLengthSource(contentLength);
         Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
         body.close();

         switch (response.code()) {
            case HTTP_OK:
               // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
               // that happens, then we will have buffered bytes that are needed by the SSLSocket!
               // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
               // that it will almost certainly fail because the proxy has sent unexpected data.
               if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
                  throw new IOException("TLS tunnel buffered too many bytes!");
               }
               return null;

            case HTTP_PROXY_AUTH:
               tunnelRequest = route.address().proxyAuthenticator().authenticate(route, response);
               if (tunnelRequest == null) throw new IOException("Failed to authenticate with proxy");

               if ("close".equalsIgnoreCase(response.header("Connection"))) {
                  return tunnelRequest;
               }
               break;

            default:
               throw new IOException(
                     "Unexpected response code for CONNECT: " + response.code());
         }
      }
   }

(6)establishProtocol方法
上面的方法只是建立了socket連接,無(wú)論是與目標(biāo)服務(wù)器的連接达布,還是與代理服務(wù)器的团甲,下面的方法是確定網(wǎng)絡(luò)協(xié)議,如果是http請(qǐng)求黍聂,協(xié)議為http/1.1躺苦,可以直接返回,如果是https請(qǐng)求产还, 則建立ssl連接匹厘,如果HTTP/2則需要進(jìn)行協(xié)議協(xié)商。這里就解析了OkHttp如何在socket連接之上實(shí)現(xiàn)http脐区、https愈诚、HTTP/2等協(xié)議。

private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    if (route.address().sslSocketFactory() == null) {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }
    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);
    if (protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
      http2Connection = new Http2Connection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .listener(this)
          .pingIntervalMillis(pingIntervalMillis)
          .build();
      http2Connection.start();
    }
}

從代碼可以看出:
1、如果sslSocketFactory為空炕柔,說(shuō)明是http請(qǐng)求酌泰,協(xié)議為HTTP_1_1,返回匕累;
2陵刹、如果sslSocketFactory非空,需要進(jìn)行TLS握手欢嘿;
3衰琐、如果是協(xié)議是HTTP_2,則構(gòu)建Http2Connection际插,完成與服務(wù)器的協(xié)商。

(7)connectTls方法
再看下如何建立TLS連接:

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;
    try {
      // Create the wrapper over the connected socket.
      //在原來(lái)的已經(jīng) 建立連接的socket上加一層ssl显设,java中傳入原始socket構(gòu)造SSLSocket
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      //配置socket的加解密器 框弛,TLS版本及擴(kuò)展內(nèi)容
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      if (connectionSpec.supportsTlsExtensions()) {
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      //ssl握手
      sslSocket.startHandshake();
      // block for session establishment
      SSLSession sslSocketSession = sslSocket.getSession();
      if (!isValid(sslSocketSession)) {
        throw new IOException("a valid ssl session was not established");
      }
      Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

      // Verify that the socket's certificates are acceptable for the target host.
      //驗(yàn)證socket的證書(shū)是否被服務(wù)器接受
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) { 
         //獲取X509Certificate證書(shū)對(duì)象
        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
            + "\n    certificate: " + CertificatePinner.pin(cert)
            + "\n    DN: " + cert.getSubjectDN().getName()
            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } finally {
      if (sslSocket != null) {
        Platform.get().afterHandshake(sslSocket);
      }
      if (!success) {
        closeQuietly(sslSocket);
      }
    }
}

TLS連接是對(duì)原始的TCP連接的一個(gè)封裝,以提供TLS握手捕捂,及數(shù)據(jù)收發(fā)過(guò)程中的加密解密等功能瑟枫。在Java中,用SSLSocket來(lái)描述指攒。建立TLS連接的大致流程可總結(jié)為:
1慷妙、在原始已經(jīng)建立連接的socket的基礎(chǔ)上,用SSLSocketFactory構(gòu)建SSLSocket允悦;
2膝擂、配置SSLSocket,包括加解密器隙弛,TLS協(xié)議版本架馋,如果ConnectionSpec支持TLS擴(kuò)展參數(shù),配置TLS擴(kuò)展參數(shù)全闷;
3叉寂、開(kāi)始TLS握手sslSocket.startHandshake();
4总珠、握手完后屏鳍,獲取服務(wù)器返回的證書(shū)信息SSLSession;
5局服、對(duì)握手過(guò)程返回證書(shū)新息SSLSession進(jìn)行驗(yàn)證hostnameVerifier().verify()钓瞭;
6、驗(yàn)證遠(yuǎn)程主機(jī)證書(shū)淫奔;
7降淮、如果ConnectionSpec支持TLS擴(kuò)展參數(shù),獲取握手過(guò)程完成的協(xié)議協(xié)商所選擇的協(xié)議,主要用于http2的ALPN擴(kuò)展佳鳖;
8霍殴、獲取I/O操作的讀寫(xiě)流,okio的BufferedSource和BufferedSink系吩,保存協(xié)議及握手信息来庭。

(8)isEligible方法
對(duì)于一個(gè)請(qǐng)求,優(yōu)先從連接池尋找可復(fù)用的連接穿挨,如何判斷連接是否能否被復(fù)用月弛,下面解釋判斷能否復(fù)用的方法:

 /**
   * Returns true if this connection can carry a stream allocation to {@code address}. If non-null
   * {@code route} is the resolved route for a connection.
   */
  public boolean isEligible(Address address, @Nullable Route route) {
    // If this connection is not accepting new streams, we're done.
    // 當(dāng)前分配的流已達(dá)到上限或者已經(jīng)設(shè)為不允許再分配流
    if (allocations.size() >= allocationLimit || noNewStreams) return false;

    // If the non-host fields of the address don't overlap, we're done.
    // host之外的配置要匹配,包括協(xié)議版本科盛、代理帽衙、ssl、端口等
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

    // If the host exactly matches, we're done: this connection can carry the address.
    // 如果host也完全匹配贞绵,則可放心復(fù)用
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }

    // At this point we don't have a hostname match. But we still be able to carry the request if
    // our connection coalescing requirements are met. See also:
    // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
    // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

    // 1. This connection must be HTTP/2.
    // 運(yùn)行到這里說(shuō)明多個(gè)host指向一個(gè)ip的特殊情況厉萝,只允許HTTP/2復(fù)用,條件比較嚴(yán)格榨崩,要滿足后續(xù)4點(diǎn)
    if (http2Connection == null) return false;

    // 2. The routes must share an IP address. This requires us to have a DNS address for both
    // hosts, which only happens after route planning. We can't coalesce connections that use a
    // proxy, since proxies don't tell us the origin server's IP address.
    if (route == null) return false;
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;

    // 3. This connection's server certificate's must cover the new host.
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    // 4. Certificate pinning must match the host.
    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }

    return true; // The caller's address can be carried by this connection.
  }

判斷連接是否可復(fù)用條件:
1谴垫、先要滿足流分配上限數(shù)(HTTP/1.x 1個(gè),HTTP/2 多個(gè))母蛛;
2翩剪、Address的配置完全相同,如SSL彩郊、代理前弯、端口、主機(jī)名都要匹配秫逝;
3博杖、如果Address不匹配也可能有復(fù)用,主要是同一個(gè)主機(jī)配置了多個(gè)域名筷登,且新請(qǐng)求已經(jīng)選擇的路由剃根,條件必須同時(shí)滿足:HTTP/2請(qǐng)求,新請(qǐng)求不是代理請(qǐng)求前方、當(dāng)前連接也不是代理連接狈醉,路由ip、端口匹配惠险,證書(shū)匹配苗傅。

(三) ConnectionPool

解析:我們都知道,在復(fù)雜的網(wǎng)絡(luò)環(huán)境下班巩,頻繁創(chuàng)建和斷開(kāi)Socket連接是非常浪費(fèi)資源和耗時(shí)的(需要3次握手4次揮手)渣慕,如果是https連接還要進(jìn)行ssl握手嘶炭,http協(xié)議的keepalive對(duì)于解決這一問(wèn)題有重要的作用。
連接空閑后存活一段時(shí)間及連接復(fù)用需就要對(duì)連接進(jìn)行管理逊桦,這里引入了連接池的概念眨猎。okhttp支持單個(gè)地址最多5個(gè)空閑連接(keepalive狀態(tài)),鼻烤活時(shí)間是5分鐘睡陪,超出時(shí)間的連接會(huì)被回收。okhttp用ConnectionPool實(shí)現(xiàn)連接池的功能匿情,對(duì)連接進(jìn)行管理和回收兰迫。
ConnectionPool內(nèi)部維護(hù)一個(gè)隊(duì)列存放連接,一個(gè)線程池清理連接炬称。

/*
 * 連接池汁果,實(shí)現(xiàn)連接的復(fù)用。通過(guò)一個(gè)隊(duì)列維護(hù)當(dāng)前所有的連接(RealConnection)
 * 最多同時(shí)持有5個(gè)空閑連接玲躯,本莸拢活時(shí)間為5分鐘
 */
public final class ConnectionPool {
   /**
    * Background threads are used to cleanup expired connections. There will be at most a single
    * thread running per connection pool. The thread pool executor permits the pool itself to be
    * garbage collected.用于清理失效連接的后臺(tái)線程池。線程池允許自身進(jìn)行垃圾回收府蔗。
    */
   private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
         Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
         new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
   /** The maximum number of idle connections for each address. */
   private final int maxIdleConnections;//每個(gè)地址最多空閑連接數(shù)
   private final long keepAliveDurationNs;
   //清理連接線程晋控,在線程池executor中調(diào)用
   private final Runnable cleanupRunnable = new Runnable() {
      @Override public void run() {
         while (true) {
            //執(zhí)行清理汞窗,并返回下次清理的時(shí)間
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos>0) {
               long waitMillis = waitNanos / 1000000L;
               waitNanos -= (waitMillis * 1000000L);
               synchronized (ConnectionPool.this) {
                  try {
                     //阻塞等待姓赤,等時(shí)間到后繼續(xù)循環(huán)清理
                     ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                  } catch (InterruptedException ignored) {
                  }
               }
            }
         }
      }
   };

   private final Deque connections = new ArrayDeque<>();//存放連接的隊(duì)列
   final RouteDatabase routeDatabase = new RouteDatabase();//路由庫(kù)
   boolean cleanupRunning;

   /**
    * Create a new connection pool with tuning parameters appropriate for a single-user application.
    * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
    * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
    */
   public ConnectionPool() {
      this(5, 5, TimeUnit.MINUTES);
   }

    public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
      this.maxIdleConnections = maxIdleConnections;
      this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
      // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
      if (keepAliveDuration<= 0) {
         throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
      }
    }
    /*
     * 獲取可復(fù)用連接,如果沒(méi)有則返回null
     */
    @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {}
     /*
      * 將新創(chuàng)建的連接加入連接池
      */
     void put(RealConnection connection){}
}

(1)get方法

/**
  * Returns a recycled connection to {@code address}, or null if no such connection exists. The
  * route is null if the address has not yet been routed.
  * 根據(jù)address獲取可重用的連接仲吏,如果沒(méi)有返回null不铆。
  * 如果沒(méi)有選擇路由,入?yún)oute是null裹唆,StreamAllocation的findConnection方法誓斥,第一次調(diào)用get方法
  * 的入?yún)oute就是null
  */
  @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;
   }

方法比較簡(jiǎn)單,遍歷隊(duì)列中每個(gè)連接许帐,調(diào)用isEligible方法判斷是否適合復(fù)用劳坑,能則分配給streamAllocation,判斷是否適合的方法isEligible前面有分析成畦。

(2)put方法

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

新創(chuàng)建的連接需要通過(guò)put方法加入到連接池距芬,先執(zhí)行清理,再添加到隊(duì)列循帐。

(3)cleanUp方法

/**
   * Performs maintenance on this pool, evicting the connection that has been idle the longest if
   * either it has exceeded the keep alive limit or the idle connections limit.
   *
   * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
   * -1 if no further cleanups are required.
   */
  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;
        }
      }
      // 找出空閑時(shí)間最長(zhǎng)的連接
      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;
  }

清理的邏輯不復(fù)雜框仔,就是遍歷隊(duì)列中的連接,調(diào)用pruneAndGetAllocationCount方法返回引用數(shù)拄养,判斷當(dāng)前連接是否空閑离斩,跳過(guò)正在被用的連接,對(duì)于空閑的連接,更新空閑持續(xù)的時(shí)間跛梗,通過(guò)遍歷得到空閑時(shí)間最長(zhǎng)的連接寻馏,如果超過(guò)了設(shè)定的保活時(shí)間或者空閑連接超過(guò)最大數(shù)量茄袖,則移除并關(guān)閉該連接操软,繼續(xù)執(zhí)行清除,如果沒(méi)有需要移除的宪祥,返回下次清理時(shí)間聂薪,即最快達(dá)到設(shè)定保活的時(shí)間蝗羊。

(4)pruneAndGetAllocationCount方法

   * Prunes any leaked allocations and then returns the number of remaining live allocations on
   * {@code connection}. Allocations are leaked if the connection is tracking them but the
   * application code has abandoned them. Leak detection is imprecise and relies on garbage
   * collection.
   */
  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List<Reference<StreamAllocation>> references = connection.allocations;//弱引用列表
    for (int i = 0; i < references.size(); ) {
      Reference<StreamAllocation> reference = references.get(i);
      if (reference.get() != null) {
         i++;
         continue;
      }
      // We've discovered a leaked allocation. This is an application bug.
      StreamAllocation.StreamAllocationReference streamAllocRef =
          (StreamAllocation.StreamAllocationReference) reference;
      String message = "A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?";
      Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);

      references.remove(i);
      connection.noNewStreams = true;

      // If this was the last allocation, the connection is eligible for immediate eviction.
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }

    return references.size();
  }

RealConnection通過(guò)一個(gè)列表記錄當(dāng)前建立的流List<Reference<StreamAllocation>>藏澳,這是一個(gè)弱引用列表,主要是為了防止內(nèi)存泄漏耀找,pruneAndGetAllocationCount方法主要是遍歷該列表翔悠,如果發(fā)現(xiàn)引用的StreamAllocatin已經(jīng)為空(程序出現(xiàn)bug,正常是不會(huì)出現(xiàn)的)野芒,則將該引用移出列表蓄愁,最后返回當(dāng)前持有引用的計(jì)數(shù)。

(4)小結(jié)
由上面的分析可總結(jié)連接池復(fù)用的原理:
· OkHttp通過(guò)ConnectionPool維護(hù)線程池狞悲;
· ConnectionPool通過(guò)隊(duì)列Deque<RealConnection>持有當(dāng)前所有的連接撮抓;
· 新創(chuàng)建的連接通過(guò)put方法加入到隊(duì)列,加入隊(duì)列前先執(zhí)行一遍清理摇锋;
· get方法會(huì)根據(jù)傳入的Address和Route遍歷連接隊(duì)列丹拯,返回可以復(fù)用的連接,復(fù)用的條件既要滿足分配流的上限原則荸恕,也需protocol乖酬、ssl、host等配置匹配融求;
· ConnectionPool通過(guò)一個(gè)專(zhuān)門(mén)的線程清理失效的連接咬像,該線程每執(zhí)行完一次清理都會(huì)根據(jù)返回的等待時(shí)間阻塞等待;
· 清理的邏輯即遍歷每個(gè)連接生宛,通過(guò)連接對(duì)StreamAlloction的弱引用計(jì)數(shù)器來(lái)判斷是否空閑(計(jì)數(shù)為0則說(shuō)明空閑)县昂,通過(guò)遍歷隊(duì)列,找出空閑時(shí)長(zhǎng)最長(zhǎng)的連接茅糜,再根據(jù)已到逼甙牛活的時(shí)長(zhǎng)(keepalive)或空閑連接數(shù)的上限進(jìn)行清理回收。

五蔑赘、總結(jié)

至此基本解析了OkHttp網(wǎng)絡(luò)連接管理的流程狸驳,由于篇幅及時(shí)間有限的原因预明,中間有些細(xì)節(jié)沒(méi)有展開(kāi)細(xì)分析。在分析的過(guò)程也解答了文章開(kāi)頭提出的疑問(wèn)耙箍。

參考

http://www.reibang.com/p/6166d28983a2
https://blog.csdn.net/yueaini10000/article/details/83305787
https://blog.csdn.net/FrancisHe/article/details/84667562#_HTTP__2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撰糠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辩昆,更是在濱河造成了極大的恐慌阅酪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汁针,死亡現(xiàn)場(chǎng)離奇詭異术辐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)施无,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)辉词,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人猾骡,你說(shuō)我怎么就攤上這事瑞躺。” “怎么了兴想?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵幢哨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嫂便,道長(zhǎng)捞镰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任顽悼,我火速辦了婚禮曼振,結(jié)果婚禮上几迄,老公的妹妹穿的比我還像新娘蔚龙。我一直安慰自己,他們只是感情好映胁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布木羹。 她就那樣靜靜地躺著,像睡著了一般解孙。 火紅的嫁衣襯著肌膚如雪坑填。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天弛姜,我揣著相機(jī)與錄音脐瑰,去河邊找鬼。 笑死廷臼,一個(gè)胖子當(dāng)著我的面吹牛苍在,可吹牛的內(nèi)容都是我干的绝页。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼寂恬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼续誉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起初肉,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤酷鸦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后牙咏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體臼隔,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年妄壶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躬翁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盯拱,死狀恐怖盒发,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狡逢,我是刑警寧澤宁舰,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奢浑,受9級(jí)特大地震影響蛮艰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雀彼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一壤蚜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧徊哑,春花似錦袜刷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至梢莽,卻和暖如春萧豆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昏名。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工涮雷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人轻局。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓洪鸭,卻偏偏與公主長(zhǎng)得像膜钓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卿嘲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容