Apache HttpAsyncClient 4.1.2
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.2</version>
</dependency>
Class 继承图
InternalHttpAsyncClient
api使用者使用的 HttpClient
HttpAsyncRequestExecutor
InternalIODispatch
PoolingNHttpClientConnectionManager
DefaultConnectingIOReactor
建立连接用的boss reactor,一个client只有一个
BaseIOReactor
处理读写的worker reactor,一个client可以有多个
CPool
TCP连接池,不是线程池
ManagedNHttpClientConnectionImpl
一条TCP连接
IOSessionImpl
一对 HTTP Request/Response 所使用的会话上下文
attributes 中持有 ManagedNHttpClientConnectionImpl 引用等
Class 依赖关系
常驻线程
Reactor Thread 负责 connect
Worker Thread 负责 read write
时序图
一些默认参数
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)
- 返回一个回复后,端口号未变,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的源码了
- 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)
- 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/