Tomcat---Connector 分析


5 Connector 分析

Connector 用于接收请求并将请求封装成Request 和Response 来具体处理,最底层是使用Socket 来进行连接的, Request 和Response 是按照HTTP 协议来封装的,所以Connector 同时实现了TCP/IP 协议和HTTP 协议, Request 和Response 封装完之后交给Container 进行处理,Container 就是Servlet 的容器, Container 处理完之后返回给Connector,最后Connector 使用Socket 将处理结果返回给客户端,这样整个请求就处理完了。

5.1 Connector 的结构

Connector 中具体是用ProtocolHandler 来处理请求的,不同的ProtocolHandler 代表不同的连接类型,比如, Http11Protocol 使用的是普通Socket 来连接的, Http 11 NioProtocol 使用的是NioSocket 来连接的。
ProtocolHandler 里面有3 个非常重要的组件: Endpoint 、Processor 和Adapter。

  • Endpoint用于处理底层Socket 的网络连接,
  • Processor 用于将Endpoint 接收到的Socket 封装成Request,
  • Adapter 用于将封装好的Request 交给Container 进行具体处理。

也就是说Endpoint用来实现TCP/IP 协议, Processor 用来实现HTTP 协议, Adapter 将请求适配到Servlet 容器进行具体处理。
Endpoint 的抽象实现AbstractEndpoint 里面定义的Acceptor 和AsyncTimeout 两个内部类和一个Handler 接口。Acceptor 用于监昕请求, AsyncTimeout 用于检查异步request 的超时,Handler 用于处理接收到的Socket,在内部调用了Processor 进行处理。
Connector 的结构如下:

5.2 Connector 自身类

Connector 类本身的作用主要是在其创建时创建ProtocolHandler,然后在生命周期的相关方法中调用了ProtocolHandler 的相关生命周期方法。Connector 的使用方法是通过Connector 标签配置在conf/server.xml 文件中,所以Connector 是在Catalina 的load 方法中根据conf/server.xml 配置文件创建Server对象时创建的。Connector 的生命周期方法是在Service 中调用的。

Connector 的创建
Connector 的创建过程主要是初始化ProtocolHandler。server.xrnl 配置文件中Connector 标签的protocol 属性会设置到Connector 构造函数的参数中,它用于指定ProtocolHandler 的类型, Connector 的构造函数代码如下:

public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            this.protocolHandler = (ProtocolHandler) clazz.newInstance();
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        }
    }

这里首先根据传人的protocol 参数调用setProtocol 方法设置了protocolHandlet℃lassName 属性,接着用protoco!HandlerClassName 所代表的类创建了Protoco旧andI er 并赋值给了protocolHandler属性。
设置protoco!HandlerClassName 属性的setProtocol 方法代码如下:

/**
     * Set the Coyote protocol which will be used by the connector.
     *
     * @param protocol The Coyote protocol name
     */
    public void setProtocol(String protocol) {

        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11Protocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            }
        }

    }

Apr 是Apache Portable Runtime 的缩写,是Apache 提供的一个运行时环境,如果要使用Apr 需要先安装,安装后Tomcat 可以自己检测出来。如果安装了Apr, setProtocol 方法会根据配置的HTTP/1.1 属性对应地将protocolHandlerClassName 设置为org.apache.coyote.http11.Http11.AprProtocol ,如果没有安装Apr,会根据配置的HTTP/1.1 属性将protocoHandlerClassName设置为com..apache.coyote.http11.Http11NioProtocol,然后就会根据protocolHandlerClassName 来创建ProtocolHandler。

Connector 生命周期处理方法
Connector 的生命周期处理方法中主要调用了ProtocolHandler 的相应生命周期方法,代码如下:

@Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);

        // Make sure parseBodyMethodsSet has a default
        if( null == parseBodyMethodsSet ) {
            setParseBodyMethods(getParseBodyMethods());
        }

        if (protocolHandler.isAprRequired() &&
                !AprLifecycleListener.isAprAvailable()) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerNoApr",
                            getProtocolHandlerClassName()));
        }

        try {
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException
                (sm.getString
                 ("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }

        // Initialize mapper listener
        mapperListener.init();
    }


 @Override
    protected void startInternal() throws LifecycleException {

        // Validate settings before starting
        if (getPort() < 0) {
            throw new LifecycleException(sm.getString(
                    "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
        }

        setState(LifecycleState.STARTING);

        try {
            protocolHandler.start();
        } catch (Exception e) {
            String errPrefix = "";
            if(this.service != null) {
                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
            }

            throw new LifecycleException
                (errPrefix + " " + sm.getString
                 ("coyoteConnector.protocolHandlerStartFailed"), e);
        }

        mapperListener.start();
    }


@Override
    protected void stopInternal() throws LifecycleException {

        setState(LifecycleState.STOPPING);

        try {
            protocolHandler.stop();
        } catch (Exception e) {
            throw new LifecycleException
                (sm.getString
                 ("coyoteConnector.protocolHandlerStopFailed"), e);
        }

        mapperListener.stop();
    }


@Override
    protected void destroyInternal() throws LifecycleException {
        mapperListener.destroy();

        try {
            protocolHandler.destroy();
        } catch (Exception e) {
            throw new LifecycleException
                (sm.getString
                 ("coyoteConnector.protocolHandlerDestroyFailed"), e);
        }

        if (getService() != null) {
            getService().removeConnector(this);
        }

        super.destroyInternal();
    }
  • 在initlntemal 方法中首先新建了一个Adapter 并设置到ProtocolHandler 中,然后对ProtocolHandler 进行初始化;
  • 在startIntemal 方法中首先判断设置的端口是否小于0 ,如果小于0 就抛出异常,开则就调用ProtocolHandler 的start 方法来启动;
  • 在stoplntemal 方法巾先设置了生命周期状态,然后调用了ProtocoIHandler 的stop 方法;
  • 在destroylntemal 方法中除了调用ProtocoIHandler 的destroy 方法,还会将当前的Connector 从Service 中删除井调用父类的destroyInternal 方法。

5.3 ProtocolHandler

Tomcat 中ProtocolHandler 的继承结构如图

ProtocolHandler 有一个抽象实现类AbstractProtocol, AbstractProtocol 下面分了三种类型: Ajp 、HTTP 和Spdy 。Ajp是Apache JServ Protocol 的缩写, Apache 的定向包协议,主要用于与前端服务器(如Apache )进行通信,它是长连接,不需要每次通信都重新建立连接,这样就节省了开销; Spdy 协议Google开发的协议,作用类似HTTP ,比HTTP 效率高,不过这只是Google 制定的企业级协议,使用并不广泛,而且在HTTP/2 协议中已经包含了Spdy 所提供的优势,所以Spdy 协议平常很少使用,不过Tomcat 提供了支持。
这里的ProtocolHandler 以默认配置中的org.apache.coyote.http11 .Http11.NioProtocol 为例来分析,它使用HTTP11协议, TCP 层使用NioSocket 来传输数据。
Http11NioProtocol 的构造雨数中创建了NioEndpoint 类型的Endpoint,井新建了Http11ConnectionHandler 类型的Handler 然后设置到了Endpoint 中,代码如下:

public Http11NioProtocol() {
        endpoint=new NioEndpoint();
        cHandler = new Http11ConnectionHandler(this);
        ((NioEndpoint) endpoint).setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }

四个生命周期方法是在父类AbstractProtocol 中实现的,其中主要调用了Endpoint 的生命周期方法。

5.4 处理TCP/IP 协议的Endpoint

Endpoint 用于处理具体连接和传输数据, NioEndpoint 继承自org.apache.tomcat. util.net.AbstractEndpoint,在NioEndpoint 中新增了Poller 和SocketProcessor 内部类, NioEndpoint 中处理请求的具体流程如图:

NioEndpoint 的init 和start 方法在父类AbstractEndpoin 中,代码如下:

public final void init() throws Exception {
        testServerCipherSuitesOrderSupport();
        if (bindOnInit) {
            bind();
            bindState = BindState.BOUND_ON_INIT;
        }
    }


public final void start() throws Exception {
        if (bindState == BindState.UNBOUND) {
            bind();
            bindState = BindState.BOUND_ON_START;
        }
        startInternal();
    }

这两个方法主要调用bind 和startlntemal 方法,它们是模板方法,在NioEndpoint 中实现, bind 方法代码如下:

/**
     * Initialize the endpoint.
     */
    @Override
    public void bind() throws Exception {

        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        serverSock.socket().bind(addr,getBacklog());
        serverSock.configureBlocking(true); //mimic APR behavior
        serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());

        // Initialize thread count defaults for acceptor, poller
        if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
        if (pollerThreadCount <= 0) {
            //minimum one poller thread
            pollerThreadCount = 1;
        }
        stopLatch = new CountDownLatch(pollerThreadCount);

        // Initialize SSL if needed
        if (isSSLEnabled()) {
            SSLUtil sslUtil = handler.getSslImplementation().getSSLUtil(this);

            sslContext = sslUtil.createSSLContext();
            sslContext.init(wrap(sslUtil.getKeyManagers()),
                    sslUtil.getTrustManagers(), null);

            SSLSessionContext sessionContext =
                sslContext.getServerSessionContext();
            if (sessionContext != null) {
                sslUtil.configureSessionContext(sessionContext);
            }
            // Determine which cipher suites and protocols to enable
            enabledCiphers = sslUtil.getEnableableCiphers(sslContext);
            enabledProtocols = sslUtil.getEnableableProtocols(sslContext);
        }

        if (oomParachute>0) reclaimParachute(true);
        selectorPool.open();
    }

这里的bind 方法中首先初始化了ServerSocketChannel,然后检查了代表Acceptor 和Poller 初始化的线程数量的acceptor’threadCount 属性和pollerηtreadCount 属性,它们至少为1, Acceptor 用于接收请求,接收到请求后交给Poller 处理,它们都是启动线程来处理的。另外还处理了初始化SSL 等内容。NioEndpoint 的startlntemal 方法代码如下:

@Override
    public void startInternal() throws Exception {

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

            // 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();
        }
    }

这里首先初始化了一些属性, 然后启动了Poller 和Acceptor 来处理请求,初始化的属性中的processorCache 属性是SynchronizedStack类型, SocketProcessor 并不是前面介绍的Processor ,而是NioEndpoint 的一个内部类, Poller 接收到请求后就会交给它处理, SocketProcessor 又会将请求传递到Handler。 启动Acceptor 的startAcceptorThreads 方法在AbstractEndpoint 中,代码如下:

protected final void startAcceptorThreads() {
        int count = getAcceptorThreadCount();
        acceptors = new Acceptor[count];

        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon());
            t.start();
        }
    }

这里的getAcceptorThreadCount 方法就是获取的init 方法中处理过的acceptorThreadCount属性,获取到后就会启动相应数量的Acceptor 线程来接收请求。

5.5 处理HTTP 协议的Processor

Processor 用于处理应用层协议(如HTTP ),它的继承结构如图:

Processor 有两个AbstractProtocol 抽象继承类。正常处理协议使用的是下面的AbstractProtocol 及其实现类, 上面的AbstractProtocol 是Servlet3. 1之后才新增的,用于处理HTTP 的升级协议, 当正常(下面) 的Processor 处理之后如果Socket 的状态是UPGADING ,那么Endpoint 中的Handler 将会接着创建并调用org.apache.coyote.http11. upgrade 包中的Processor 进行处理,这里的HTTP 升级协议指的是WebSocket 协议。
具体实现应用层协议处理请求的是AbstractAjpProcessor 和AbstractHttp11Processor 中的process方法,这个方法中首先封装了Request 和Response , 然后调用Adapter 将请求传递到了Container 中,最后对处理的结果进行了处理,如有没有启动异步处理、处理过程巾有没有抛出异常等。

5.6 适配器Adapter

Adapter 只有一个实现类,那就是org.apache.catalina.connector 包下的CoyoteAdapter 类。Processor 在其process 方法中会调用Adapter 的service 方法来处理请求, Adapter 的service 方法主要是调用Container 管道中的invoke方法来处理请求,在处理之前对Request和Response做了处理,将原来创建的org.apache.coyote 包下的Request 和Response 封装成了org.apache.catal ina.connector 的Request 和Response ,并在处理完成后判断再启动了Comet(长连接推模式)和是否启动了异步请求,并作出相应处理。调用Container 管道的相应代码片段如下:

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

这里首先从Connector 中获取到Service ( Connector 在initInternal 方法中创建CoyoteAdapter的时候已经将自己设置到了CoyoteAdapter 中),然后从Service 中获取Container ,接着获取管道,再获取管道的第一个Value,最后调用invoke 方法执行请求。Service 中保存的是最顶层的容器,当调用最顶层容器管道的invoke 方法时,管道将逐层调用各层容器的管道中Value 的invoke 方法,直到最后调用Wrapper 的管道中的BaseValue ( StandardWrapperValve)来处理Filter 和Servlet。


原文链接:https://blog.csdn.net/gchd19921992/article/details/79076926