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í)際情況
- 在發(fā)送7個(gè)請(qǐng)求,服務(wù)器均未回復(fù)時(shí)。
共建立4個(gè)tcp連接,散列到3線程的3個(gè)selector上監(jiān)聽,如圖1。
斷點(diǎn)于(execute:340, AbstractMultiworkerIOReactor)
CPool 中 leasingRequests 為3,leased 為4,如圖2。
斷點(diǎn)如上,調(diào)用棧為(execute:192, PoolingNHttpClientConnectionManager)
圖1
圖2 - 返回一個(gè)回復(fù)后,端口號(hào)未變,SocketChannelImpl改變
CPool 中 leasingRequests 為2,leased 為4 - 在server只回復(fù)0-1兩個(gè)請(qǐng)求時(shí),client端同步等待2-5號(hào)的response,6號(hào)的請(qǐng)求不會(huì)發(fā)出
部分源碼執(zhí)行過程
-
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。BaseIOReactor 在BaseIOReactor.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_READ 和 OP_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
- TCP在連接建立完成后,控制權(quán)通過
結(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/



