Apache HttpAsyncClient 源碼分析

Apache HttpAsyncClient 4.1.2

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpasyncclient</artifactId>
    <version>4.1.2</version>
</dependency>

Class 繼承圖

InternalHttpAsyncClient

api使用者使用的 HttpClient

InternalHttpAsyncClient

HttpAsyncRequestExecutor
HttpAsyncRequestExecutor
InternalIODispatch
InternalIODispatch
PoolingNHttpClientConnectionManager
PoolingNHttpClientConnectionManager
DefaultConnectingIOReactor

建立連接用的boss reactor,一個(gè)client只有一個(gè)


DefaultConnectingIOReactor
BaseIOReactor

處理讀寫的worker reactor,一個(gè)client可以有多個(gè)


BaseIOReactor
CPool

TCP連接池,不是線程池


CPool
ManagedNHttpClientConnectionImpl

一條TCP連接


ManagedNHttpClientConnectionImpl
IOSessionImpl

一對(duì) HTTP Request/Response 所使用的會(huì)話上下文
attributes 中持有 ManagedNHttpClientConnectionImpl 引用等

IOSessionImpl

Class 依賴關(guān)系

Class Diagram

常駐線程

Reactor Thread 負(fù)責(zé) connect
Worker Thread 負(fù)責(zé) read write

時(shí)序圖

Main Thread Sequence Diagram
Reactor Thread Sequence Diagram
Worker Thread Sequence Diagram

一些默認(rèn)參數(shù)

PoolingNHttpClientConnectionManager
  • defaultMaxPerRoute = 2
    每一個(gè) local IP => remoteIP : port 為一個(gè)route,在向http服務(wù)器單一(ip,port)對(duì)發(fā)送請(qǐng)求時(shí),這個(gè)參數(shù)控制了可以建立的tcp連接上限
  • maxTotal 20
IOReactorConfig
  • selectInterval = 1000
    selector interval (ms)
  • soTimeout = 0
    socket上返回response的timeout上限
  • soKeepAlive = false
    ??雖然默認(rèn)為false,但實(shí)際效果好像是true
  • soReuseAddress = false
    ??雖然默認(rèn)為false,但實(shí)際效果好像是true

Demo測(cè)試

前提
  • windows 10環(huán)境下
  • IoThreadCount設(shè)為3 (實(shí)際環(huán)境可默認(rèn)為CPU核心數(shù)量)
  • MaxConnPerRoute 設(shè)為4
  • 發(fā)送7個(gè)請(qǐng)求
實(shí)際情況
  1. 在發(fā)送7個(gè)請(qǐng)求,服務(wù)器均未回復(fù)時(shí)。
    共建立4個(gè)tcp連接,散列到3線程的3個(gè)selector上監(jiān)聽,如圖1。
    斷點(diǎn)于(execute:340, AbstractMultiworkerIOReactor)
    CPoolleasingRequests 為3,leased 為4,如圖2。
    斷點(diǎn)如上,調(diào)用棧為(execute:192, PoolingNHttpClientConnectionManager)
    圖1

    圖2
  2. 返回一個(gè)回復(fù)后,端口號(hào)未變,SocketChannelImpl改變
    CPoolleasingRequests 為2,leased 為4
  3. 在server只回復(fù)0-1兩個(gè)請(qǐng)求時(shí),client端同步等待2-5號(hào)的response,6號(hào)的請(qǐng)求不會(huì)發(fā)出
部分源碼執(zhí)行過程
  1. HttpGet 寫入了IOSessionImpl 的outputBuffer中,具體位置層次如圖
    ByteBuffer 的 pos=0 lim=140 代表有140個(gè)字節(jié)在buffer中未發(fā)出
    其中 OP_WRITE 已注冊(cè)到 interestOps 中,等待其ready后,后續(xù)代碼會(huì)執(zhí)行channel.write(this.buffer)。至此,請(qǐng)求已發(fā)出
    至于 SelectionKey 是如何ready的,就要去分析nio的源碼了

    buffer content

    • TCP在連接建立完成后,控制權(quán)通過DefaultConnectingIOReactor.addChannel()從BossReactor轉(zhuǎn)入BaseIOReactor。BaseIOReactorBaseIOReactor.processNewChannels()中注冊(cè)OP_READ
    • BaseIOReactor.processNewChannels()sessionRequest.completed(session)通過層層回調(diào),AbstractClientExchangeHandler.requestConnection()方法中定義的匿名類中的completed()
      new FutureCallback<NHttpClientConnection>() {
      
                     @Override
                     public void completed(final NHttpClientConnection managedConn) {
                         connectionAllocated(managedConn);
                     }
      
                     @Override
                     public void failed(final Exception ex) {
                         connectionRequestFailed(ex);
                     }
      
                     @Override
                     public void cancelled() {
                         connectionRequestCancelled();
                     }
      
                 });
      
      CPoolProxy.requestOutput();=>NHttpConnectionBase.requestOutput()
      最終通過this.session.setEvent(EventMask.WRITE);注冊(cè)OP_WRITE
      BaseIOReactor.processNewChannels()函數(shù)同時(shí)完成了 OP_READOP_WRITE 的注冊(cè)
    • Http請(qǐng)求發(fā)送完畢,即!this.outbuf.hasData(),會(huì)將OP_WRITE去注冊(cè)this.session.clearEvent(EventMask.WRITE);
      雖然OP_WRITE已經(jīng)ready,但由于不在interestOps中,不會(huì)被select()出來
      readyCount = this.selector.select(this.selectTimeout)
      readyCount = 0
結(jié)論
  • 同一route上的http請(qǐng)求數(shù)量受限于 maxPerRoute, 與本地打開的、向同一對(duì)端(ip:port)的端口號(hào)數(shù)量相同。每一請(qǐng)求使用 IOSessionImpl 保存對(duì)話上下文,并附到 SelectionKey 上。
  • Async HttpClient無法復(fù)用socket,由于HTTP/1.1的原生限制,沒有特征值用來識(shí)別HTTP報(bào)文,因此必須占用socket來等待Response。待了解HTTP/2.0是否解決此問題。

Demo 代碼地址: https://github.com/ntjsz/http-client-demo/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 1 Servlet&網(wǎng)絡(luò)訪問 1.1網(wǎng)路基礎(chǔ) B/S的S Serverhttp網(wǎng)絡(luò)應(yīng)用服務(wù)端軟件 httpHy...
    征程_Journey閱讀 794評(píng)論 0 1
  • 原文https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html...
    梁行之閱讀 1,373評(píng)論 0 0
  • 昨晚意外的夢(mèng)到了父母 我走進(jìn)房子看見父母被關(guān)在一個(gè)鐵柵欄里 還有十來個(gè)人的樣子 我慌張的找來老虎鉗 錘子 遞給他們...
    小小面閱讀 328評(píng)論 0 0
  • 第二十章到二十一章 第二十章不輕易取悅:有說不的勇氣 文章中說“覺得自己有義務(wù)讓孩子高興,這是個(gè)錯(cuò)誤,這樣容易完成...
    芳芳的讀書時(shí)光閱讀 113評(píng)論 0 0

友情鏈接更多精彩內(nèi)容