okhttp之StreamAllocation

轉(zhuǎn)載請(qǐng)以鏈接形式標(biāo)明出處:
本文出自:103style的博客


base on 3.12.0


目錄

  • 背景
  • 簡(jiǎn)介
  • StreamAllocation 的成員變量
  • StreamAllocation 的構(gòu)造函數(shù)
  • StreamAllocation 的相關(guān)方法
  • 小結(jié)

背景

HTTP 的版本從最初的 1.0版本桑寨,到后續(xù)的 1.1版本,再到后續(xù)的 google 推出的SPDY铆遭,后來(lái)再推出 2.0版本屉来,HTTP協(xié)議越來(lái)越完善走越。okhttp也是根據(jù)2.0和1.1/1.0作為區(qū)分浩淘,實(shí)現(xiàn)了兩種連接機(jī)制.

http2.0解決了老版本(1.1和1.0)最重要兩個(gè)問(wèn)題:

http2.0 使用 多路復(fù)用 的技術(shù)痘绎,多個(gè) stream 可以共用一個(gè) socket 連接。每個(gè) tcp連接都是通過(guò)一個(gè) socket 來(lái)完成的蛉幸,socket 對(duì)應(yīng)一個(gè) hostport,如果有多個(gè)stream(即多個(gè) Request) 都是連接在一個(gè) hostport上丛晦,那么它們就可以共同使用同一個(gè) socket ,這樣做的好處就是 可以減少TCP的一個(gè)三次握手的時(shí)間奕纫。
OKHttp里面,負(fù)責(zé)連接的是 RealConnection 烫沙。


簡(jiǎn)介

官方注釋是 StreamAllocation是用來(lái)協(xié)調(diào)Connections匹层、StreamsCalls這三個(gè)實(shí)體的。

HTTP通信 執(zhí)行 網(wǎng)絡(luò)請(qǐng)求Call 需要在 連接Connection 上建立一個(gè)新的 Stream锌蓄,我們將 StreamAllocation 稱之 的橋梁升筏,它負(fù)責(zé)為一次 請(qǐng)求 尋找 連接 并建立 ,從而完成遠(yuǎn)程通信瘸爽。

然后我們先來(lái)看看 StreamAllocation 這個(gè)類是在什么地方初始化的您访,以及在哪些地方用到?

選中StreamAllocation 的構(gòu)造方法剪决,在 AndroidStudio 中按 Alt + F7灵汪,我們發(fā)現(xiàn)是在 RetryAndFollowUpInterceptor這個(gè)攔截器intercept(Chain chain)中創(chuàng)建的.
然后往下層攔截器傳遞,直到 ConnectInterceptor 以及 CallServerInterceptor才繼續(xù)用到柑潦。

public final class RetryAndFollowUpInterceptor implements Interceptor {
    ...
  @Override public Response intercept(Chain chain) throws IOException {
    ...
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    ...
}
public final class ConnectInterceptor implements Interceptor {
  ...
  @Override public Response intercept(Chain chain) throws IOException {
    ...
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}
public final class CallServerInterceptor implements Interceptor {
  ...
  @Override public Response intercept(Chain chain) throws IOException {
    ...
        streamAllocation.noNewStreams();
    ...
  }
 ...
}

StreamAllocation 的成員變量

  • 由構(gòu)造方法中傳入的變量:
    • public final Address address; 地址
    • private final ConnectionPool connectionPool; 連接池
    • public final Call call; 請(qǐng)求對(duì)象
    • public final EventListener eventListener; 事件回調(diào)
    • private final Object callStackTrace; 日志
    • private final RouteSelector routeSelector; 路由選擇器
  • private RouteSelector.Selection routeSelection; 選中的路由集合
  • private Route route; 路由
  • private RealConnection connection; HTTP連接
  • private HttpCodec codec; HTTP請(qǐng)求的編碼和響應(yīng)的解碼

StreamAllocation 的構(gòu)造函數(shù)

RetryAndFollowUpInterceptor 中傳入了 :

  • OkHttpClient 配置的 連接池.
  • 根據(jù) Request 構(gòu)建了 Address.
  • 構(gòu)建的 RealCall 對(duì)象.
  • OkHttpClient 配置的 eventListener.
  • RealCallcaptureCallStackTrace()中配置的 callStackTrace
  • 根據(jù)相關(guān)參數(shù)構(gòu)建的 RouteSelector.
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;
}

StreamAllocation 的相關(guān)方法

我們?cè)诤?jiǎn)介中介紹 StreamAllocation 在哪里創(chuàng)建的時(shí)候享言,發(fā)現(xiàn)攔截器中主要調(diào)用的就是 newStream(...)noNewStreams() 這兩個(gè)方法。那我們先來(lái)看看這兩個(gè)方法吧渗鬼。

  • newStream(...)
    主要就是獲取一個(gè) 可用的連接 和 對(duì)應(yīng)連接協(xié)議的編解碼實(shí)例览露,并賦值給變量 connectioncodec.
    public HttpCodec newStream(...) {
      ...
      try {
        //在連接池中找到一個(gè)可用的連接  沒(méi)有則創(chuàng)建一個(gè)
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
            writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        //獲取對(duì)應(yīng)協(xié)議的 編解碼類 Http1Codec or Http2Codec
        HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
        synchronized (connectionPool) {
          codec = resultCodec;
          return resultCodec;
        }
      } catch (IOException e) {
        throw new RouteException(e);
      }
    }
    
  • noNewStreams()
    主要就是設(shè)置當(dāng)前的 connection 不能再創(chuàng)建新的流。
    public void noNewStreams() {
      Socket socket;
      Connection releasedConnection;
      synchronized (connectionPool) {
        releasedConnection = connection;
        socket = deallocate(true, false, false);
        if (connection != null) releasedConnection = null;
      }
      closeQuietly(socket);
      if (releasedConnection != null) {
        eventListener.connectionReleased(call, releasedConnection);
      }
    }
    

繼續(xù)看下 newStream(...) 獲取可用連接的流程譬胎。

  • findHealthyConnection(...)
    通過(guò)findConnection(...)得到一個(gè)連接差牛,如果是新創(chuàng)建的連接,則直接返回银择,否則檢查 連接 是否已經(jīng)可以開(kāi)始承載新的流多糠,不行則繼續(xù)findConnection(...).

    private RealConnection findHealthyConnection(...) throws IOException {
      while (true) {
        RealConnection candidate = findConnection(...);
        //如果是一個(gè)新創(chuàng)建的連接 則直接返回
        synchronized (connectionPool) {
          if (candidate.successCount == 0) {
            return candidate;
          }
        }
        //否則檢查 連接 是否已經(jīng)可以開(kāi)始承載新的流
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
          noNewStreams();
          continue;
        }
        return candidate;
      }
    }
    
  • findConnection(...)

    • 1.0 第一個(gè) synchronized (connectionPool) 這一段首先嘗試使用當(dāng)前的變量connection. 如果當(dāng)前的connectionnull, 則通過(guò)Internal.instance.get(connectionPool, address, this, null);連接池 中獲取對(duì)應(yīng) address 的連接 賦值給 connection,如果當(dāng)前的 connection不為null浩考,則直接返回.
    • 2.0 如果上一步?jīng)]有找到可用連接夹孔,則看是否有其他可用路由。
    • 3.0 第二個(gè) synchronized (connectionPool) 這一段,3.1如果有其他路由則先去連接池查詢看是否有對(duì)應(yīng)連接搭伤, 3.2沒(méi)有的話則創(chuàng)建一個(gè)新的 RealConnection只怎,并賦值給變量 connection. 3.3在連接池中找到的話則直接返回。
    • 4.0 然后新創(chuàng)建的連接開(kāi)始握手連接怜俐,然后放入連接池身堡,然后返回。
    private RealConnection findConnection(...) throws IOException {
      ...
      //1.0 嘗試使用當(dāng)前的 connection拍鲤,或者查找 連接池
      synchronized (connectionPool) {
        ...
        releasedConnection = this.connection;
        if (this.connection != null) {
          result = this.connection;
          releasedConnection = null;
        }
        if (!reportedAcquired) {
          releasedConnection = null;
        }
        if (result == null) {
          Internal.instance.get(connectionPool, address, this, null);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
          } else {
            selectedRoute = route;
          }
        }
      }
      ...
      if (result != null) {
        return result;
      }
    
      //2.0 看是否有其他的路由
      boolean newRouteSelection = false;
      if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        routeSelection = routeSelector.next();
      }
      //3.0 
      synchronized (connectionPool) {
        if (newRouteSelection) {
          //3.1 有其他路由則先去連接池查詢看是否有對(duì)應(yīng)連接
        }
        if (!foundPooledConnection) {
          ...
          //3.2 沒(méi)有的話則創(chuàng)建一個(gè)新的RealConnection贴谎,并賦值給變量 connection
          result = new RealConnection(connectionPool, selectedRoute);
          acquire(result, false);
        }
      }
      // 3.3 在連接池中找到的話則直接返回
      if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
        return result;
      }
      //4.0 新創(chuàng)建的連接開(kāi)始握手連接
      result.connect(...);
      synchronized (connectionPool) {
        reportedAcquired = true;
        Internal.instance.put(connectionPool, result);
        ...
      }
      ...
      return result;
    }
    

    流程圖大概如下:


    okhttp之StreamAllocation.findHealthyConnection(...)
  • acquire(...)從連接池找到對(duì)應(yīng)連接 賦值給StreamAllocation

    public void acquire(RealConnection connection, boolean reportedAcquired) {
      assert (Thread.holdsLock(connectionPool));
      if (this.connection != null) throw new IllegalStateException();
      this.connection = connection;
      this.reportedAcquired = reportedAcquired;
      connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
    }
    
  • 獲取成員變量的一些方法.

    public HttpCodec codec() {
      synchronized (connectionPool) {
        return codec;
      }
    }
    public Route route() {
      return route;
    }
    public synchronized RealConnection connection() {
      return connection;
    }
    
  • release() 資源釋放

    public void release() {
      Socket socket;
      Connection releasedConnection;
      synchronized (connectionPool) {
        releasedConnection = connection;
        socket = deallocate(false, true, false);
        if (connection != null) releasedConnection = null;
      }
      closeQuietly(socket);
      if (releasedConnection != null) {
        Internal.instance.timeoutExit(call, null);
        eventListener.connectionReleased(call, releasedConnection);
        eventListener.callEnd(call);
      }
    }
    

小結(jié)

  • 介紹了HTTP2HTTP1 的區(qū)別,HTTP2使用 多路復(fù)用 的技術(shù)季稳,減少了同地址的TCP握手時(shí)間擅这。

  • 介紹了 StreamAllocation 的主要方法newStream(...),以及其中獲取可用 Connection 的具體邏輯.

  • newStream(...) 返回的 HttpCodec 這個(gè) 編解碼的示例后面也會(huì)介紹景鼠。


參考文章

如果覺(jué)得不錯(cuò)的話仲翎,請(qǐng)幫忙點(diǎn)個(gè)贊唄。

以上


掃描下面的二維碼铛漓,關(guān)注我的公眾號(hào) Android1024溯香, 點(diǎn)關(guān)注,不迷路浓恶。

Android1024

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玫坛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子问顷,更是在濱河造成了極大的恐慌井誉,老刑警劉巖邓深,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愈涩,死亡現(xiàn)場(chǎng)離奇詭異蚌本,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)塞耕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蚀腿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人扫外,你說(shuō)我怎么就攤上這事莉钙。” “怎么了筛谚?”我有些...
    開(kāi)封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵磁玉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我驾讲,道長(zhǎng)蚊伞,這世上最難降的妖魔是什么席赂? 我笑而不...
    開(kāi)封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮时迫,結(jié)果婚禮上颅停,老公的妹妹穿的比我還像新娘。我一直安慰自己掠拳,他們只是感情好癞揉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著溺欧,像睡著了一般喊熟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姐刁,一...
    開(kāi)封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天逊移,我揣著相機(jī)與錄音,去河邊找鬼龙填。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拐叉,可吹牛的內(nèi)容都是我干的岩遗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼凤瘦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宿礁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蔬芥,我...
    開(kāi)封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤梆靖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后笔诵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體返吻,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年乎婿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了测僵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谢翎,死狀恐怖捍靠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情森逮,我是刑警寧澤榨婆,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站褒侧,受9級(jí)特大地震影響良风,放射性物質(zhì)發(fā)生泄漏谊迄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一拖吼、第九天 我趴在偏房一處隱蔽的房頂上張望鳞上。 院中可真熱鬧,春花似錦吊档、人聲如沸篙议。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鬼贱。三九已至,卻和暖如春香璃,著一層夾襖步出監(jiān)牢的瞬間这难,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工葡秒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姻乓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓眯牧,卻偏偏與公主長(zhǎng)得像蹋岩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子学少,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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