Tomcat的Connector启动过程分析

一. 前言

前面分析了tomcat的整体架构tomcat的启动过程,在分析启动过程的时候只讲了整体的启动过程,本篇来重点分析一下tomcat的Connector(连接器)组件的启动过程。

二.从Connector的构造开始

那么org.apache.catalina.connector.Connector是在什么时候被创建出来的呢?在上一篇tomcat的启动过程中我们跟进过了Digester解析tomcat的server.xml配置文件,在org.apache.catalina.startup.Catalina#createStartDigester方法中有一个段代码是:

// 此处使用的tomcat版本为8.0
digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());

所以当解析到server.xml配置文件中的Connector标签之后,就会创建一个ConnectorCreateRule来执行Connector的相关操作。那么这个org.apache.catalina.startup.ConnectorCreateRule其实是一个org.apache.tomcat.util.digester.Rule,这个Rule接口主要是实现了在匹配相应的XML元素嵌套模式时要采取的操作,简单说就是他是规定XML解析的元素是如何被加载处理的,Rule有一个方法org.apache.tomcat.util.digester.Rule#begin,就是用来处理XML配置文件中的标签开头部分的,也就初始化之类的。
在这里插入图片描述
默认是空实现,所以我们来看org.apache.catalina.startup.ConnectorCreateRulebegin方法。

public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
    Service svc = (Service)digester.peek();
    Executor ex = null;
    if ( attributes.getValue("executor")!=null ) {
        ex = svc.getExecutor(attributes.getValue("executor"));
    }
    // 从配置文件中的Connector标签中获取protocol属性的值,那么默认我们的protocol值就是HTTP/1.1
    Connector con = new Connector(attributes.getValue("protocol"));
    if (ex != null) {
        setExecutor(con, ex);
    }
    String sslImplementationName = attributes.getValue("sslImplementationName");
    if (sslImplementationName != null) {
        setSSLImplementationName(con, sslImplementationName);
    }
    digester.push(con);
}

由上面这段代码注释可以知道是通过xml的配置内容进行Connector的创建的,那么跟进到org.apache.catalina.connector.Connector#Connector(java.lang.String)

public Connector(String protocol) {
	// 从上面的跟进可以知道入参的protocol值为 HTTP/1.1
	// 这个setProtocol就是根据入参去选择对应的Protocol  
	// 那么由此可知最终是protocolHandlerClassName的值为org.apache.coyote.http11.Http11NioProtocol
	// 也就是http1.1的nio方式
    setProtocol(protocol);
    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        // 此处通过反射创建Http11NioProtocol
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    if (Globals.STRICT_SERVLET_COMPLIANCE) {
        uriCharset = StandardCharsets.ISO_8859_1;
    } else {
        uriCharset = StandardCharsets.UTF_8;
    }

    // Default for Connector depends on this (deprecated) system property
    if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
        encodedSolidusHandling = EncodedSolidusHandling.DECODE;
    }
}

通过这段代码我们跟进到org.apache.coyote.http11.Http11NioProtocol的创建,来看Http11NioProtocol的继承结构
在这里插入图片描述

public Http11NioProtocol() {
	//创建了一个NioEndpoint
    super(new NioEndpoint());
}

Http11NioProtocol在构造时创建了一个org.apache.tomcat.util.net.NioEndpoint,这个其实就是NIO的默认实现。​Endpoint​​​负责网络通信,监听一个端口,循环接收​​socket​​​请求,读取网络字节流等
在这里插入图片描述
NioEndpoint的父类org.apache.tomcat.util.net.AbstractEndpoint中有一个抽象内部类:org.apache.tomcat.util.net.AbstractEndpoint.Acceptor,而NioEndpoint中有一个org.apache.tomcat.util.net.NioEndpoint.Acceptor继承自它,并实现了他的run()方法,在这个run方法中就是在循环的调用serverSock.accept()接收请求到tomcat的链接,先有个大概了解,后续在详细分析接收请求的部分。

回到Http11NioProtocol的创建

public Http11NioProtocol() {
	//创建了一个NioEndpoint
    super(new NioEndpoint());
}

跟进到父类的构造函数可以知道,最终是把这个new出来的 NioEndpoint赋值给了org.apache.coyote.AbstractProtocol#AbstractProtocolorg.apache.coyote.AbstractProtocol#endpoint属性。在父类org.apache.coyote.http11.AbstractHttp11Protocol构造器中:

public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
    super(endpoint);
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    getEndpoint().setHandler(cHandler);
}

创建了一个org.apache.coyote.AbstractProtocol.ConnectionHandler,然后把这个ConnectionHandler设置到前面创建的NioEndpoint中。

然后就完成了Connector的创建。在Connector的创建过程中,我们要注意几个点:

  1. Http11NioProtocol 继承自AbstractProtocolAbstractHttp11Protocol,他持有NioEndpoint
  2. NioEndpoint 继承自AbstractEndpoint,持有ConnectionHandlerNioEndpoint的内部类Acceptor是真正的接收请求的地方
  3. ConnectionHandlerNioEndpoint的一个属性,它在后续会进行NioEndpoint接收的请求处理时使用

到目前为止只是创建了ConnectorConnector也是一个Lifecycle,所以接下来就到了他的initInternal,所以我们直接来到:org.apache.catalina.connector.Connector#initInternal,不了解为啥会来到这里的同学请先看tomcat的启动过程中对Lifecycle相关的内容。

三. Connector的init

Connector实现了Lifecycle,所以init肯定是在org.apache.catalina.connector.Connector#initInternal

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize adapter
    // 初始化一个CoyoteAdapter  CoyoteAdapter是Adapter接口的实现
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);

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

    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
                getProtocolHandlerClassName()));
    }
    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
                getProtocolHandlerClassName()));
    }
    if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
            protocolHandler instanceof AbstractHttp11JsseProtocol) {
        AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
                (AbstractHttp11JsseProtocol<?>) protocolHandler;
        if (jsseProtocolHandler.isSSLEnabled() &&
                jsseProtocolHandler.getSslImplementationName() == null) {
            // OpenSSL is compatible with the JSSE configuration, so use it if APR is available
            jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
        }
    }

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

跟进到protocolHandler.init();执行里面去,由于Http11NioProtocol集成了AbstractHttp11Protocol,这个init()方法就在AbstractHttp11Protocol中:

public void init() throws Exception {
    // Upgrade protocols have to be configured first since the endpoint
    // init (triggered via super.init() below) uses this list to configure
    // the list of ALPN protocols to advertise
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        configureUpgradeProtocol(upgradeProtocol);
    }

	// 又调用了父类的init,也就是AbstractProtocol的init方法
    super.init();

    // Set the Http11Protocol (i.e. this) for any upgrade protocols once
    // this has completed initialisation as the upgrade protocols may expect this
    // to be initialised when the call is made
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        if (upgradeProtocol instanceof Http2Protocol) {
            ((Http2Protocol) upgradeProtocol).setHttp11Protocol(this);
        }
    }
}

跟进到org.apache.coyote.AbstractProtocol#init

public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
    }

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }

    if (this.domain != null) {
        ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        this.rgOname = rgOname;
        Registry.getRegistry(null, null).registerComponent(
                getHandler().getGlobal(), rgOname, null);
    }

    String endpointName = getName();
    // 这里的endpoint就是NioEndpoint  此处给NioEndpoint设置了名字:http-nio-8080  和  domain:Catalina
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);
	// 然后继续执行自组件的init  也就是NioEndpoint的init
    endpoint.init();
}

跟进这个endpoint.init();最终会来到:org.apache.tomcat.util.net.AbstractEndpoint#init,这个方法中我们重点关注其中的bind();方法,其他的代码这里就不贴出来了。
bind()方法是个抽象方法,留给子类实现了,所以来到:org.apache.tomcat.util.net.NioEndpoint#bind

public void bind() throws Exception {
    /*默认执行*/
    if (!getUseInheritedChannel()) {
    	// 开启 ServerSocketChannel
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        // addr是:0.0.0.0/0.0.0.0:8080   其实就是绑定8080端口
        // getAcceptCount()获取的是acceptCount 默认值为100 表示的是socket上请求的最大挂起连接数
        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"));
        }
    }
    // 设置 ServerSocketChannel 为阻塞模式
    serverSock.configureBlocking(true); //mimic APR behavior

    // Initialize thread count defaults for acceptor, poller
    //  设置了Poller和Acceptor的线程数量 在start中根据数量启动相对应数量的线程
    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;
    }
    // 设置
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

	// 打开NioSelectorPool
    selectorPool.open();
}

然后Connector的init大致就完成了。
在这个过程中主要是有:

  1. 创建了CoyoteAdapter
  2. 执行Http11NioProtocol的init,在执行endpoint的init
  3. NioEndpoint中开启了ServerSocketChannel并绑定端口
  4. 打开了一个NioSelectorPool

四. Connector的start

大致的了解了的一遍Connectorinit之后,再来看Connectorstart,从org.apache.catalina.connector.Connector#startInternal开始

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 {
    	// Http11NioProtocol的start
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

根据代码跟进到:org.apache.coyote.AbstractProtocol#start

public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }
	// NioEndpoint的start
    endpoint.start();

    // Start timeout thread
    // 启动超时线程
    asyncTimeout = new AsyncTimeout();
    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
    int priority = endpoint.getThreadPriority();
    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
        priority = Thread.NORM_PRIORITY;
    }
    timeoutThread.setPriority(priority);
    timeoutThread.setDaemon(true);
    timeoutThread.start();
}

跟进endpoint.start();会先来到org.apache.tomcat.util.net.AbstractEndpoint#start

public final void start() throws Exception {
	// 这个bindState在org.apache.tomcat.util.net.AbstractEndpoint#init中已经设置为BOUND_ON_INIT了  所以这里不会再进入这个if
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    // 跟进这个
    startInternal();
}

跟进startInternal()org.apache.tomcat.util.net.NioEndpoint#startInternal

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) {
        	// tomcat自定义了线程池实现,并自定义了线程池队列  这个后续再分析
            createExecutor();
        }
         //限流控制  tomcat自定义了一个LimitLatch用来实现限流控制
        initializeConnectionLatch();

        // Start poller threads
        // 启动Poller线程 
        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();
        }

		// 启动Acceptor线程
        startAcceptorThreads();
    }
}

org.apache.tomcat.util.net.NioEndpoint#bind中设置了PollerAcceptor的数量

那么至此start也就大致完成了,在这个过程中几个重点:

  1. 创建了工作线程池org.apache.tomcat.util.threads.ThreadPoolExecutor
  2. 创建了限流LimitLatch
  3. 启动了Poller线程
  4. 启动了Acceptor线程

到此位置,Connector的启动过程就大致分析完成了。在过程中涉及到了Http11NioProtocolNioEndpointConnectionHandlerCoyoteAdapterAcceptorPollerLimitLatchorg.apache.tomcat.util.threads.ThreadPoolExecutor这些重要的类。这些都在后续的文章中详细分析吧,由于篇幅问题,本篇就先了解一个过程,算是抛砖引玉

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值