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,一个client只有一个

DefaultConnectingIOReactor
BaseIOReactor
处理读写的worker reactor,一个client可以有多个

BaseIOReactor
CPool
TCP连接池,不是线程池

CPool
ManagedNHttpClientConnectionImpl
一条TCP连接

ManagedNHttpClientConnectionImpl
IOSessionImpl
一对 HTTP Request/Response 所使用的会话上下文
attributes 中持有 ManagedNHttpClientConnectionImpl 引用等

IOSessionImpl
Class 依赖关系

Class Diagram
常驻线程
Reactor Thread 负责 connect
Worker Thread 负责 read write
时序图

Main Thread Sequence Diagram

Reactor Thread Sequence Diagram

Worker Thread Sequence Diagram
一些默认参数
PoolingNHttpClientConnectionManager
- defaultMaxPerRoute = 2
 每一个 local IP => remoteIP : port 为一个route,在向http服务器单一(ip,port)对发送请求时,这个参数控制了可以建立的tcp连接上限
- maxTotal 20
IOReactorConfig
- selectInterval = 1000
 selector interval (ms)
- soTimeout = 0
 socket上返回response的timeout上限
- soKeepAlive = false
 ??虽然默认为false,但实际效果好像是true
- soReuseAddress = false
 ??虽然默认为false,但实际效果好像是true
Demo测试
前提
- windows 10环境下
- IoThreadCount设为3 (实际环境可默认为CPU核心数量)
- MaxConnPerRoute 设为4
- 发送7个请求
实际情况
- 在发送7个请求,服务器均未回复时。
 共建立4个tcp连接,散列到3线程的3个selector上监听,如图1。
 断点于(execute:340, AbstractMultiworkerIOReactor)
 CPool 中 leasingRequests 为3,leased 为4,如图2。
 断点如上,调用栈为(execute:192, PoolingNHttpClientConnectionManager)
 图1
 图2
- 返回一个回复后,端口号未变,SocketChannelImpl改变
 CPool 中 leasingRequests 为2,leased 为4
- 在server只回复0-1两个请求时,client端同步等待2-5号的response,6号的请求不会发出
部分源码执行过程
- 
HttpGet 写入了IOSessionImpl 的outputBuffer中,具体位置层次如图 
 ByteBuffer 的 pos=0 lim=140 代表有140个字节在buffer中未发出
 其中 OP_WRITE 已注册到 interestOps 中,等待其ready后,后续代码会执行channel.write(this.buffer)。至此,请求已发出
 至于 SelectionKey 是如何ready的,就要去分析nio的源码了
 buffer content
- TCP在连接建立完成后,控制权通过DefaultConnectingIOReactor.addChannel()从BossReactor转入BaseIOReactor。BaseIOReactor 在BaseIOReactor.processNewChannels()中注册OP_READ
- 
BaseIOReactor.processNewChannels()中sessionRequest.completed(session)通过层层回调,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);注册OP_WRITE
 即BaseIOReactor.processNewChannels()函数同时完成了 OP_READ 和 OP_WRITE 的注册
- Http请求发送完毕,即!this.outbuf.hasData(),会将OP_WRITE去注册this.session.clearEvent(EventMask.WRITE);
 虽然OP_WRITE已经ready,但由于不在interestOps中,不会被select()出来
 readyCount = this.selector.select(this.selectTimeout)
 readyCount = 0
 
- TCP在连接建立完成后,控制权通过
结论
- 同一route上的http请求数量受限于 maxPerRoute, 与本地打开的、向同一对端(ip:port)的端口号数量相同。每一请求使用 IOSessionImpl 保存对话上下文,并附到 SelectionKey 上。
- Async HttpClient无法复用socket,由于HTTP/1.1的原生限制,没有特征值用来识别HTTP报文,因此必须占用socket来等待Response。待了解HTTP/2.0是否解决此问题。
Demo 代码地址: https://github.com/ntjsz/http-client-demo/



