Apache HttpAsyncClient 源码分析

Apache HttpAsyncClient 4.1.2


    org.apache.httpcomponents
    httpasyncclient
    4.1.2

Class 继承图

InternalHttpAsyncClient

api使用者使用的 HttpClient

Apache HttpAsyncClient 源码分析_第1张图片
InternalHttpAsyncClient

HttpAsyncRequestExecutor
Apache HttpAsyncClient 源码分析_第2张图片
HttpAsyncRequestExecutor
InternalIODispatch
Apache HttpAsyncClient 源码分析_第3张图片
InternalIODispatch
PoolingNHttpClientConnectionManager
Apache HttpAsyncClient 源码分析_第4张图片
PoolingNHttpClientConnectionManager
DefaultConnectingIOReactor

建立连接用的boss reactor,一个client只有一个


Apache HttpAsyncClient 源码分析_第5张图片
DefaultConnectingIOReactor
BaseIOReactor

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


Apache HttpAsyncClient 源码分析_第6张图片
BaseIOReactor
CPool

TCP连接池,不是线程池


Apache HttpAsyncClient 源码分析_第7张图片
CPool
ManagedNHttpClientConnectionImpl

一条TCP连接


Apache HttpAsyncClient 源码分析_第8张图片
ManagedNHttpClientConnectionImpl
IOSessionImpl

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

Apache HttpAsyncClient 源码分析_第9张图片
IOSessionImpl

Class 依赖关系

Apache HttpAsyncClient 源码分析_第10张图片
Class Diagram

常驻线程

Reactor Thread 负责 connect
Worker Thread 负责 read write

时序图

Apache HttpAsyncClient 源码分析_第11张图片
Main Thread Sequence Diagram
Apache HttpAsyncClient 源码分析_第12张图片
Reactor Thread Sequence Diagram
Apache HttpAsyncClient 源码分析_第13张图片
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个请求
实际情况
  1. 在发送7个请求,服务器均未回复时。
    共建立4个tcp连接,散列到3线程的3个selector上监听,如图1。
    断点于(execute:340, AbstractMultiworkerIOReactor)
    CPoolleasingRequests 为3,leased 为4,如图2。
    断点如上,调用栈为(execute:192, PoolingNHttpClientConnectionManager)
    Apache HttpAsyncClient 源码分析_第14张图片
    图1

    Apache HttpAsyncClient 源码分析_第15张图片
    图2
  2. 返回一个回复后,端口号未变,SocketChannelImpl改变
    CPoolleasingRequests 为2,leased 为4
  3. 在server只回复0-1两个请求时,client端同步等待2-5号的response,6号的请求不会发出
部分源码执行过程
  1. HttpGet 写入了IOSessionImpl 的outputBuffer中,具体位置层次如图
    ByteBuffer 的 pos=0 lim=140 代表有140个字节在buffer中未发出
    其中 OP_WRITE 已注册到 interestOps 中,等待其ready后,后续代码会执行channel.write(this.buffer)。至此,请求已发出
    至于 SelectionKey 是如何ready的,就要去分析nio的源码了

    Apache HttpAsyncClient 源码分析_第16张图片
    buffer content

    • TCP在连接建立完成后,控制权通过DefaultConnectingIOReactor.addChannel()从BossReactor转入BaseIOReactorBaseIOReactorBaseIOReactor.processNewChannels()中注册OP_READ
    • BaseIOReactor.processNewChannels()sessionRequest.completed(session)通过层层回调,AbstractClientExchangeHandler.requestConnection()方法中定义的匿名类中的completed()
      new FutureCallback() {
      
                     @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_READOP_WRITE 的注册
    • Http请求发送完毕,即!this.outbuf.hasData(),会将OP_WRITE去注册this.session.clearEvent(EventMask.WRITE);
      虽然OP_WRITE已经ready,但由于不在interestOps中,不会被select()出来
      readyCount = this.selector.select(this.selectTimeout)
      Apache HttpAsyncClient 源码分析_第17张图片
      readyCount = 0
结论
  • 同一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/

你可能感兴趣的:(Apache HttpAsyncClient 源码分析)