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
HttpAsyncRequestExecutorInternalIODispatch
InternalIODispatchPoolingNHttpClientConnectionManager
PoolingNHttpClientConnectionManagerDefaultConnectingIOReactor
建立连接用的boss reactor,一个client只有一个
DefaultConnectingIOReactor
BaseIOReactor
处理读写的worker reactor,一个client可以有多个
BaseIOReactor
CPool
TCP连接池,不是线程池
CPool
ManagedNHttpClientConnectionImpl
一条TCP连接
ManagedNHttpClientConnectionImpl
IOSessionImpl
一对 HTTP Request/Response 所使用的会话上下文
attributes 中持有 ManagedNHttpClientConnectionImpl 引用等
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中,具体位置层次如图
buffer content
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)
readyCount = 0
- TCP在连接建立完成后,控制权通过
结论
- 同一route上的http请求数量受限于 maxPerRoute, 与本地打开的、向同一对端(ip:port)的端口号数量相同。每一请求使用 IOSessionImpl 保存对话上下文,并附到 SelectionKey 上。
- Async HttpClient无法做到全异步,无法完全复用socket,由于HTTP/1.1的原生限制,没有特征值用来识别HTTP报文,因此必须同步等待Response。
- 可以通过HTTP/2.0
或者自行编写HttpClient,将特征值注入HTTP头或Body中来修复此缺陷。
Demo 代码地址: https://github.com/ntjsz/http-client-demo/
网友评论