阅读 151

Tomcat9的Connector组件(一)


浏览器发送http请求->建立Socket连接->通过Socket读取数据->根据http协议解析数据->调用后台服务完成响应。

Tomcat既是一个HttpServer,也是一个Servlet 容器,首先根据HTTP协议规范解析请求数据,然后将请求转发给Servlet进行处理。 当用户请求服务器的时候,Connector会接受请求,从Socket连接中根据http协议解析出对应的数据,CoyoteAdapter构造Request和Response对象,然后传递给后面的顶层容器StandardEngine处理,StandardEngine处理请求其实是通过容器的Pipeline进行的,而Pipeline其实最终是通过管道上的各个阀门进行的,当请求到达StandardEngineValve的时候,此阀门会将请求转发给对应StandardHost的Pipeline的第一个阀门处理,然后以此最终到达StandardHostValve阀门,它又会将请求转发给StandardContext的Pipeline的第一个阀门,这样以此类推,最后到达StandardWrapperValve,此阀门会根据Request来构建对应的Servelt,并将请求转发给对应的HttpServlet处理。其实Tomcat核心处理流程就是通过责任链一步步的组装起来的。

Service可以同时有多个Connector对象


  • 当Tomcat启动后,Connector组件的接收器(Acceptor)将会监听是否有客户套接字连接并接收Socket。
  • 一旦监听到客户端连接,则将连接交由线程池Executor处理,开始执行请求响应任务。
  • Http11Processor组件负责从客户端连接中读取消息报文,然后开始解析HTTP的请求行、请求头部、请求体。将解析后的报文封装成Request对象,方便后面处理时通过Request对象,方便后面处理时通过Request对象获取HTTP协议的相关值。
  • Mapper组件根据HTTP协议请求行的URL属性值和请求头部的Host属性值匹配由哪个Host容器、哪个Context容器、哪个Wrapper容器处理请求,这个过程其实就是根据请求从Tomcat中找到对应的Servlet,然后将路由的结果封装到Request对象中,方便后面处理时通过Request对象选择容器。
  • CoyoteAdapter组件负责将Connector组件和Engine容器连接起来,把前面处理过程中生成的请求对象Request和响应对象Response传递到Engine容器,调用它的管道。
  • Engine容器的管道开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些处理逻辑。这里xxxValve代表某阀门,我们可以根据自己的需要往这个管道中添加多个阀门,首先执行这个xxxValve,然后才执行基础阀门EngineValve,它会负责调用Host容器的管道。
  • Host容器的管道开始处理请求,它同样也包含若干阀门,首先执行这些阀门,然后执行基础阀门HostValve,它继续往下调用Context容器的管道。
  • Context容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门ContextValve,它负责调用Wrapper容器的管道。
  • Wrapper容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门WrapperValve,它会执行该Wrapper容器对应的Servlet对象的处理方法,对请求进行逻辑处理,并将结果输出到客户端。

连接器Connector是Tomcat的连接器,主要的目的是监听外围网络访问请求,而连接器在启动相关监听进程后,是通过NIO方式进行请求的监听-响应-处理。


1. Connector在创建时,会根据Connector的协议创建对应的ProtocolHandler处理类


public AbstractAjpProtocol(AbstractEndpoint<S,?> endpoint) {
    super(endpoint);
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    // AJP does not use Send File
    getEndpoint().setUseSendfile(false);
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    getEndpoint().setHandler(cHandler);
}复制代码
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
    super(endpoint);
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    getEndpoint().setHandler(cHandler);
}复制代码

ProtocolHandler接口是所有protocol类的顶层接口。Protocol根据请求协议分为HTTPAJP(TCP/IP),共有六个具体实现类,每种协议都能选择三种endpoint(nio、nio2、apr)。

每个protocol的生命周期操作(init、start、pause、resume、stop、destroy)是定义在他们

的公共顶层抽象类AbstractProtocol中,所以每一个protocol的生命周期运行方式是一样的。

AJP(Apache JServ Protocol)是定向包协议,因为性能原因,使用二进制格式来传输可读性文本。

AJP可用于Tomcat的负载均衡,例如Nginx可以通过AJP协议向tomcat发送请求:

AJP优点:

  • 发送的内容都是高度压缩的,所以消耗流量很少
  • WEB服务器和SERVLET容器建立的是持久性tcp连接
  • 性能比http更好(cpu使用率更低,时间略微更快)

AJP缺点:

  • 由于是持久连接,可能会导致连接数过多

每种ProtocolHandler包含3个重要组件来处理请求: 

  • Adapter:用于将封装好的Request交给Container,将请求适配到servlet容器。请求响应对象从连接器传送到容器需要一个桥梁CoyoteAdapter 。
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
        request, response);}复制代码
  • Endpoint:用于处理底层Socket连接(nio和nio2实现的是TCP/IP协议,Apr实现的是SSL/TLS协议) 
  • Processer:用于将Endpoint接收到的Socket封装成Request(实现HTTP协议或websocket协议或AJP协议)


2. NioEndpoint对应一个ServerSocketChannel通道,在启动过程中,会分别初始化SocketProcessor、PollerEvent、NioChannel缓存堆结构以及请求处理的线程池Executor,最后将会初始化两组用于请求处理线程Acceptor、Poller。


Apr(Apache portable Run-time libraries)简单理解就是从操作系统级别解决异步IO问题,大幅度的提高服务器的处理和响应性能, 也是Tomcat运行高并发应用的首选模式。需要额外安装apr和native。使用了tomcat native技术之后,tomcat在跟操作系统级别的交互方面可以做得更好。apr中socket的接收实际上是调用的native。 Tomcat-native是tomcat的一个子项目,一部分是用java写的,一部分是c写的。 Tomcat-native可以看做是一个集成包,里面集成了两个东西:openssl,这个是ssl信道的实现,另外一个是高性能的apr网络库(都是c写的)。   

/**
 * Start the NIO endpoint, creating acceptor, poller threads.
 */
@Override
public void startInternal() throws Exception {

    if (!running) {
        running = true;
        paused = false;

        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getProcessorCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getBufferPool());

        // Create worker collection
        if ( getExecutor() == null ) {
            createExecutor();
        }

        initializeConnectionLatch();

        // Start poller threads
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

        startAcceptorThreads();
    }
}复制代码



Acceptor默认只有一个线程,用于接收请求,并将请求信息封装为PollEvent对象放入PollEvent待处理队列中。如果端点处于暂停状态,50s探测一次,否则连接数+1,并判断是否超最大连接数(LimitLantch实现),如果超了,阻塞等待通过。ServerSocketChannel.accept()等待新的请求将SocketChannel请求信息封装为NioChannel(NioChannel从缓存中读取,没有就新生成),将NioChannel封装为PollerEvent(PollerEvent从缓存中读取,没有就新生成)。

"http-nio-8080-Acceptor-0" #30 daemon prio=5 os_prio=0 tid=0x0000000058ee4000 nid=0x1b54 runnable [0x0000000057c8f000] 

java.lang.Thread.State: RUNNABLE 

at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) 

at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422) 

at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250) - locked <0x00000000e264e6c0> (a java.lang.Object) 

at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:417) 

at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:69) 

at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95) 

at java.lang.Thread.run(Thread.java:748) 

Locked ownable synchronizers: 

    - None


"ajp-nio-8009-Acceptor-0" #44 daemon prio=5 os_prio=0 tid=0x0000000059402000 nid=0x1aac runnable [0x000000005d69f000] 

java.lang.Thread.State: RUNNABLE 

at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) 

at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422) 

at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250) - locked <0x00000000e26a6db8> (a java.lang.Object) 

at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:417) 

at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:69) 

at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95) 

at java.lang.Thread.run(Thread.java:748) 

 Locked ownable synchronizers: 

 - None


Poller实现了Runnable接口,Socket内容的读写是通过Poller来实现的。NioEndpoint.startInternal(),初始化pollers数组,启动pollers数组中的线程,让pollers开始工作。


Poller默认两个线程扫描PollEvent队列,进行处理,启动业务处理线程SocketProcessor循环扫描,PollEvent队列中是否存在待处理事件。从SelectionKey中获取NioSocketWrapper对象((NioSocketWrapper)sk.attachment())将NioSockectWrapper对象封装成SocketProcessor(从缓存中读取,没有就新生成)线程池启动SocketProcessor线程处理SocketProcessor线程调用ConnectionHandler进行处理,ConnectionHandler获取一个Processor进行处理(Processor也存在一个对象堆缓存,Stack实现)

"http-nio-8080-ClientPoller-0" #28 daemon prio=5 os_prio=0 tid=0x0000000058ee2800 nid=0x16ac runnable [0x000000005bf9e000] 

java.lang.Thread.State: RUNNABLE 

at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method) 

at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296) 

at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278) 

at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159) 

at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86) 

 - locked <0x00000000e2652948> (a sun.nio.ch.Util$3) 

 - locked <0x00000000e2652938> (a java.util.Collections$UnmodifiableSet) 

 - locked <0x00000000e26527c8> (a sun.nio.ch.WindowsSelectorImpl) 

at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97) 

at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:702) 

at java.lang.Thread.run(Thread.java:748) 

 Locked ownable synchronizers: 

 - None


类图:www.processon.com/view/link/5…

启动停止:www.processon.com/view/link/5…