tomcat+spring mvc 原理(三):tomcat网络请求的监控与处理1

  • 前言:
  • 请求处理的准备
  • Connector处理请求
  • Connector内部结构
  • 请求处理的动态实现

前言:

    tomcat + spring mvc 原理(一):tomcat原理综述总结了tomcat的整体运作原理、静态容器架构和容器配置,tomcat + spring mvc 原理(二):tomcat容器的初始加载与启动详细介绍了tomcat动态容器初始化和启动过程。本文主要介绍tomcat网络请求的监控与处理逻辑。
    利用tomcat + spring mvc架构体系构建服务器,很重要的一个部分就是网络请求的监控、接收、解包、封包、请求传递等功能的实现,这样在建服务器时就只需要关注业务参数的传入和处理,而不用关心网络协议等和业务无关的细节。 一般服务的问题都出现在请求和应答处理的过程,一旦是因为框架内部导致的问题,这时候充分了解tomcat+spring mvc的请求处理逻辑就很重要了。

请求处理的准备

    网络请求相关的处理集中在Connector容器中。由原理一中介绍的总体容器架构可知,Connector是被包含在Service容器中,而且Connector容器的配置也是包含在Service的配置中:

<Service name="Catalina">
  <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
      maxThreads="150" minSpareThreads="4"/>

  <Connector port="8080" protocol="HTTP/1.1"
             connectionTimeout="20000"
             redirectPort="8443" />

  <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
             maxThreads="150" SSLEnabled="true">
      <SSLHostConfig>
          <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                       type="RSA" />
      </SSLHostConfig>
  </Connector>

  <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
             maxThreads="150" SSLEnabled="true" >
      <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
      <SSLHostConfig>
          <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                       certificateFile="conf/localhost-rsa-cert.pem"
                       certificateChainFile="conf/localhost-rsa-chain.pem"
                       type="RSA" />
      </SSLHostConfig>
  </Connector>
  ......
</Service>

以上是我从原理(一)介绍的tomcat的默认配置文件server.xml中捞出来的配置。可以看到,<Connector>标签是包含在标签中的<Service>中的,一个Service可以有多个Connector,可以配置线程池来处理请求。tomcat在启动过程中依据这个配置创建Service和Connector,然后根据原理(二)中介绍的流程初始化和启动Service和Connector。

Connector处理请求

Connector内部结构

spring actuator 添加自定义监控指标_apache

    一个Connector中会有一个ProtocolHandler,不同的ProtocolHandler接口的实现代表不同的协议,可以看到上文的配置中包括了协议的类的设置,比如protocol="HTTP/1.1"或者protocol=“org.apache.coyote.http11.Http11NioProtocol”。默认实现的协议主要有两种,Ajp或者HTTP。HTTP协议不用说,Ajp是Apache JServ Protocol的缩写,主要用于Apache前端服务器的通讯,特点是长连接,不需要每次断开连接再建立连接,开销比较小(偷偷查了下资料)。

    ProtocolHandler中会包含Endpoint、Processor和Adapter的实现。Endpoint主要用来管理Socket,监听TCP的请求和发送应答。Processor用来实现HTTP协议。Adapter,顾名思义,是作为适配器将请求适配到Container容器。

请求处理的动态实现

    Connector的构造函数中,会根据配置选择ProtocolHandler的实现:

public Connector(String protocol) {
    boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
            AprLifecycleListener.getUseAprConnector();
/**
 *下面blabla一堆是用来判断使用哪一个ProtocolHandler的实现
 **/
    if ("HTTP/1.1".equals(protocol) || protocol == null) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
        }
    } else if ("AJP/1.3".equals(protocol)) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
        }
    } else {
        protocolHandlerClassName = protocol;
    }

    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {

        this.protocolHandler = p;  //在这里给引用赋值

    }

    // Default for Connector depends on this system property
    setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}

然后在Connector的生命周期方法initInternal()和startInternal()中(原理(二)有讲到)都调用了ProtocolHandler相应的init()和start()生命周期方法。需要注意的是,在initInternal()中调用protocolHandler.init()之前,先设置了protocolHanler的Adpter

@Override
    protected void initInternal() throws LifecycleException {
        ······
        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
        ······
        try {
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }
    }

    同样的故事也发生在Endpoint身上。Endpoint构造发生在ProtocolHandler的实现类构造函数中:

public Http11NioProtocol() {
    super(new NioEndpoint());
}

在ProtocolHandler的init()和start()生命周期方法中调用了Endpoint的相应生命周期的init()和start()方法(由此可见生命周期这一抽象的应用在tomcat中随处可见)。
    Endpoint工作在连接和传输数据的最前线。父类AbstractEndpoint的init()方法中调用了bind()方法,start()方法调用了Endpoint的startInternal()方法。

//init()方法
public final void init() throws Exception {
    if (bindOnInit) {
        bindWithCleanup();
        bindState = BindState.BOUND_ON_INIT;
    }
    ......
}
//bindWithCleanup()调用bind
private void bindWithCleanup() throws Exception {
    try {
        bind();
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        unbind();
        throw t;
    }
}
//start()中调用startInternal()同原理(二)中所述,不再列出

其中bind()方法和startInternal()方法都是在子类NioEndpoint中实现。
    NioEndpoint使用的套接字是JDK1.4之后新增的NioSocket,相对于普通Socket效率要更高。NioSocket中的实现关键是Selector和Channel,Channel需要注册到关注的Selector(具体可以参看NioSocket的相关讲解)。在bind()方法中依次创建 ServerSocketChannel和Selector的线程池。需要注意的线程池中的基本对象是Poller,Poller继承自Thread,内部包含了一个Selector,实现了Selector的select()的持续监听。

public void bind() throws Exception {
    //见下。创建Channel
    initServerSocket();

    setStopLatch(new CountDownLatch(1));

    initialiseSsl();
    //Selector线程池
    selectorPool.open(getName());
}
//initServerSocket()
protected void initServerSocket() throws Exception {
    if (!getUseInheritedChannel()) {
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    serverSock.configureBlocking(true); //mimic APR behavior
}

startInternal()中启动了Acceptor,Acceptor主要的作用是调用Endpoint中方法实现Poller和Channel的注册和请求连接的监听,然后获取请求的SocketChannel。

public void startInternal() throws Exception {
        ······
        startAcceptorThread();
        ······
    }
}

标准Poller的实现是继承自Thread,其中的run()方法主要是Selector.select(),等待请求的到来,然后循环遍历SelectKey
(这部分中的实现和NioSocket的使用强相关,没有了解过一定要提前看下),在循环中调用processKey()方法。

public class Poller implements Runnable {
    @Override
    public void run() {
        // Loop until destroy() is called
        while (true) {

            boolean hasEvents = false;

            try {
                if (!close) {
                    hasEvents = events();
                    if (wakeupCounter.getAndSet(-1) > 0) {
                      /**
                       *Selector.select()监听到来的请求
                       **/
                        keyCount = selector.selectNow();
                    } else {
                        keyCount = selector.select(selectorTimeout);
                    }
                    wakeupCounter.set(0);
                }
            ......
            Iterator<SelectionKey> iterator =
                keyCount > 0 ? selector.selectedKeys().iterator() : null;

            while (iterator != null &&  iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                if (socketWrapper == null) {
                    iterator.remove();
                } else {
                    iterator.remove();
                    ////
                    processKey(sk, socketWrapper);
                    ////
                }
            }
            timeout(keyCount,hasEvents);
        }
        getStopLatch().countDown();
    }
}

SocketWrapper可以说是一个大杂烩,里面包括了各种和连接有关的东西,比如Endpoint、Selector的pool、 NioChannel的栈、Poller、interestOps(与SelectorKey相关的操作码)、SendfileDataSocketBufferHandler、IOException等等。
    processKey()调用了SocketWrraper对象中Channel的读操作,将消息读入到Buffer中,然后调用processSocket()方法,进一步处理消息.processSocket()中获取了一个SocketProcessor类对象(注意并不是图上的Processor),并调用了它的run()方法。这个类的父类SocketProcessorBase实现了Runnable,在run()中调用了doRun()方法,SocketProcessor类中实现了doRun()方法。SocketProcessor是Endpoint的内部类,它的doRun()方法调用Endpoint的Handler的process方法(终于绕回到我的图了)。
    Handler的process()方法主要就是处理和Processor选用相关的逻辑,Processor用来对TCP的消息进行解包,因此和ProtocolHandler一样,有不同协议的实现。其中,Http11Processor的实现就主要对Http的request和response进行解包和封包。
Handler的process获取了Processor对象,然后调用了AbstractProcessorLight(是实现了Processor的抽象类)的process()方法,process()中调用了两个比较重要的方法:service()和dispatch()。service()对于不同协议有不同的实现,Http11Processor的service实现就是对接收的request进行HTTP解包,如果有错误会对response进行设置。dispatch()方法中调用:

getAdapter().asyncDispatch(request, response, status)

利用Adapter的asyncDispatch()方法先将org.apache.coyote.Request request和org.apache.coyote.Response response分别转换为org.apache.catalina.connector.Request和org.apache.catalina.connector.Response,然后利用自身包含的Connector对象(还记得上文说的“需要注意的是,在initInternal()中调用protocolHandler.init()之前,先设置了protocolHanler的Adpter”吗?Connector也在这个时候将自己的实例设置到了Adapter唯一实现CoyoteAdapter实例中):

connector.getService().getContainer().getPipeline()
           .getFirst().invoke(request, response);

将request和response传递给了Container(原理一和原理二中提到了Engine、Host、Context和Wrapper等)。
    由于本文章的内容已经过多,特别是在在Endpoint部分中纠缠往复,很难理清。关于后续Container中请求的处理和调用Filter对请求拦截的内容就放在下期来讲。
相关文章:
tomcat + spring mvc原理(一):tomcat原理综述和静态架构tomcat + spring mvc原理(二):tomcat容器初始化加载和启动tomcat + spring mvc原理(四):tomcat网络请求的监控与处理2tomcat + spring mvc原理(五):tomcat Filter组件实现原理