Tomcat源码剖析(二)连接器

Connector:

在上一章中,分析了整个Tomcat的结构以及启动流程,在整个框架中,我们分析了StandardServerStandardService等的初始化以及启动,但是对于连接器,却是一笔带过?为什么呢?并不是因为连接器不重要,而是它太重要了,所以这里我们单独说一说它。

首先,在看Tomcat源码的时候,按照Tomcat的命名习惯,我一直在找StandardConnector,后来才发现它的类名就叫Connector,因为Connector并不是接口,而就是一个简单的类。

其次,需要明白的是,Tomcat连接器的作用是监听端口,包装RequestResponse,而在Tomcat的实现中,分别支持两种协议:HttpAjp

  • Http就是常规的超文本传输协议,用来做Web服务器,
  • Ajp是用来与其他服务器通信用的,比如Apache静态服务器,常规下,Tomcat在传输静态资源,比如image,html等比起Apache来说,性能一般,如果一个服务中静态资源比较多的话,可以使用Apache做负载均衡加静态资源服务器,当一个请求到来的时候,Apache会首先拦截到请求,如果是静态资源请求,则Apache会直接处理然后返回,如果是动态资源请求,则Apache会通过Ajp协议发送给Tomcat,从而将请求转发给Tomcat

因此,平时我们如果不使用Apache做前端服务器,是不需要开启Ajp协议的。

最后,需要明白的是Tomcat的连接器监听IO的方式,分别包括BIONIOAPR方法,BIO便是传统阻塞式IO,每个线程独占一个IO,NIO可以翻译为New IO,也可以叫非阻塞式IO,它是一种同步非阻塞式IO,相对BIO,性能大大的提高,在Tomcat 9中,好像已经没有了BIO实现的选项APR是一种通过C语言在操作系统层面实现的IO,性能更加高,不过需要额外的配置.


明白了上面的前提以后,咱们再来看Connector的代码就比较容易明白了。

 protected final ProtocolHandler protocolHandler;
 protected Adapter adapter = null;

以上两个是Connector最主要属性,其中ProtocolHandler是连接器中主要干活的,Adapter是用来适配生成RequestResponse的。

首先看连接器的构造方法代码:

    public Connector(String protocol) {
        boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
                AprLifecycleListener.getUseAprConnector();

        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会根据server.xml的配置初始化ProtocolHandler,分别有以下几种:

  • org.apache.coyote.http11.Http11AprProtocol:支持HTTP协议的AprIO处理
  • org.apache.coyote.http11.Http11NioProtocol:支持HTTP协议的NIOIO处理
  • org.apache.coyote.ajp.AjpAprProtocol:支持Ajp协议的AprIO处理
  • org.apache.coyote.ajp.AjpNioProtocol:支持Ajp协议的NIOIO处理

以及其他协议,可是在org.apache.coyote包中已经完全没有了BIO的影子,可以发现Tocmat 9已经完全完全放弃了BIO

官方中表明8.0开始默认使用NIO,8.5已经废弃了BIO

连接器初始化完成后,会根据指定的类的全路径的初始化对象的ProtocolHandler,由于默认是NIO,因此我们这里以org.apache.coyote.http11.Http11NioProtocol为参考查看源代码。


构造完成后,连接器应该被inti,这里看下连接器的initInternal的代码:

 @Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        if (protocolHandler == null) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"));
        }

        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
        if (service != null) {
            protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
        }

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

        if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
            throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
                    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());
            }
        }

           protocolHandler.init();

    }

删除了一些结构性的代码。

  • 首先,调用父类的initInternal,像JMX注册自己
  • 然后,初始化适配器,并将适配器赋值给protocolHandler
  • 初始化后台任务线程池
  • 判断是否启动Apr,若需要启用,则再初始化Apr
  • 调用protocoHandler的初始化方法

这里最关键的便是 调用protocoHandler的初始化方法


连接器的startInternal()方法比较简单,就是单纯的启动protocolHandler

这里可以看出来,连接器中真正干活的其实是ProtocolHandler


接下来看看org.apache.coyote.http11.Http11NioProtocolstart()方法和init()方法

查看源代码我们可以发现,还存在一个Http11Nio2Protocol这个又是什么呢?

前面说过,一般NIO是同步非阻塞IO,因此Http11NioProtocol是用来实现同步非阻塞IO,而对于Http11Nio2Protocol则是用来实现异步非阻塞IO

Http11NioProtocol的主要代码有很多在其父类:由上向下查看代码:

AbstractProtocol # init

@Override
public void init() throws Exception {
    ...
    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);
    endpoint.init();
}

继续往下看,我们能看到endPoint.init()默认使用了AbstractEndPointinit()方法:

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

删除了部分结构性代码

    private void bindWithCleanup() throws Exception {
        try {
            bind();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            unbind();
            throw t;
        }
    }

可以发现,最终EndPointinit()是调用了bind()方法,

这个方法比较重要


@Override public void bind() throws Exception { initServerSocket(); //设置acceptor线程数量 if (acceptorThreadCount == 0) { //这里有条作者的注释:多线处理acceptor性能不见得好 acceptorThreadCount = 1; } //设置poller的线程数量 if (pollerThreadCount <= 0) { pollerThreadCount = 1; } setStopLatch(new CountDownLatch(pollerThreadCount)); //初始化ssl initialiseSsl(); //打开选择器连接池 selectorPool.open(); } protected void initServerSocket() throws Exception { if (!getUseInheritedChannel()) { //开启socket管道 serverSock = ServerSocketChannel.open(); //设置socket属性 socketProperties.setProperties(serverSock.socket()); //根据配置设置端口等信息 InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset()); //绑定socket监听地址,以及设置最大连接数 serverSock.socket().bind(addr,getAcceptCount()); } else { Channel ic = System.inheritedChannel(); if (ic instanceof ServerSocketChannel) { serverSock = (ServerSocketChannel) ic; } if (serverSock == null) { throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited")); } } //设置IO为阻塞IO serverSock.configureBlocking(true); //mimic APR behavior }
  • 可以看到,bind中调用了initServerSocket()方法,initServerSocket()方法初始化NIO中的管道以及socket

接下来看start()方法:

@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());

        //创建工作线程池
        if ( getExecutor() == null ) {
            createExecutor();
        }

        //设置连接最大数,默认1000
        initializeConnectionLatch();

        // 开启pooler线程
        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();
    }
}
  • 可以看到,start()方法主要就是设置对象缓存池和开启线程
  • 到现在我们还没找到监听到请求后是如何处理的
  • AccptorPooler是干什么的?

带着上面的问题,我们继续看Acceptor的代码:

Acceptor

#run()

@Override
public void run() {

    int errorDelay = 0;

    // 循环处理endpoint
    while (endpoint.isRunning()) {

        // 如果 endpoint 处于 pause 状态,这边 Acceptor 用一个 while 循环将自己也挂起
        while (endpoint.isPaused() && endpoint.isRunning()) {
            state = AcceptorState.PAUSED;
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // Ignore
            }
        }
        // 判断endpoint状态
        if (!endpoint.isRunning()) {
            break;
        }
        state = AcceptorState.RUNNING;

            // 如果此时达到了最大连接数(之前我们说过,默认是10000),就等待
            endpoint.countUpOrAwaitConnection();

            if (endpoint.isPaused()) {
                continue;
            }

            U socket = null;
            try {
                // 这里就是接收下一个进来的 SocketChannel
                // 之前我们设置了 ServerSocketChannel 为阻塞模式,所以这边的 accept 是阻塞的
                socket = endpoint.serverSocketAccept();
            } catch (Exception ioe) {
                // We didn't get a socket
                endpoint.countDownConnection();
                if (endpoint.isRunning()) {
                    // Introduce delay if necessary
                    errorDelay = handleExceptionWithDelay(errorDelay);
                    // re-throw
                    throw ioe;
                } else {
                    break;
                }
            }
            // accept 成功,将 errorDelay 设置为 0
            errorDelay = 0;

            if (endpoint.isRunning() && !endpoint.isPaused()) {
                // setSocketOptions() 是这里的关键方法,也就是说前面千辛万苦都是为了能到这里进行处理
                if (!endpoint.setSocketOptions(socket)) {
                    // 如果上面的方法返回 false,关闭 SocketChannel
                    endpoint.closeSocket(socket);
                }
            } else {
                // 由于 endpoint 不 running 了,或者处于 pause 了,将此 SocketChannel 关闭
                endpoint.destroySocket(socket);
            }
    }
    state = AcceptorState.ENDED;
}

删除了部分结构性的代码

其实,上面的代码是NIO带来的复杂度,其实核心代码就两句:

socket = endpoint.serverSocketAccept();
endpoint.setSocketOptions(socket)

所以上面的代码可以暂时不管,就研究这两句

所以手写NIO还是比较麻烦

EndPoint#serverSocketAccept()

    @Override
    protected SocketChannel serverSocketAccept() throws Exception {
        return serverSock.accept();
    }

可以看到,这就是一个阻塞方法,用来获取serverSocket监听方法,当获取到socket后,就将socket发送给endpointsetSocketOptions()

    @Override
    protected boolean setSocketOptions(SocketChannel socket) { 
            socket.configureBlocking(false);
            Socket sock = socket.socket();
            socketProperties.setProperties(sock);

            NioChannel channel = nioChannels.pop();
            if (channel == null) {
                SocketBufferHandler bufhandler = new SocketBufferHandler(
                        socketProperties.getAppReadBufSize(),
                        socketProperties.getAppWriteBufSize(),
                        socketProperties.getDirectBuffer());
                if (isSSLEnabled()) {
                    channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
                } else {
                    channel = new NioChannel(socket, bufhandler);
                }
            } else {
                channel.setIOChannel(socket);
                channel.reset();
            }
            getPoller0().register(channel);
        return true
    }

删除了一些结构性的代码

可以看到,关键就是getPoller0().register(channel);获取到socket后就将socket交给Poller处理

到这里,我们基本能明白:

  • Acceptor用来循环监听socket
  • Poller用来循环处理socket

Poller的处理socket比较复杂,关系到NIO的具体编程,这里暂时不再分析。

不过在其run方法里,包含一个processSocket()方法,这个方法比较重要,processSocket()会调用createSocketProcessor()创建一个SocketProcessor()对象,这个对象实现了Runable接口,用来包装生成RequestResponse对象以及调用容器(Container)的Pipeline#invoke()方法“

 Pipeline pipeline = connector.getService().getContainer().getPipeline();

在这里,就将连接器和容器串联起来了。


总结

看了上面这么多方法,其实总结起来如下:

  • 首先连接器(Connector)包含一个Protocol对象(协议对象,不同的协议实现不同),连接器负责组合适配器和根据配置生成Protocol对象
  • Protocol对象负责一些配置文件的读取,根据协议封装方法等,以及包含一个EndPoint对象
  • EndPoint对象包含一个AcceptorPoller对象
  • EndPointstart()方法会分别启动AcceptorPoller
  • 当一个连接到来的时候,Acceptor负责接收这个连接,然后将连接传给Poller处理,Poller会调用适配器封装RequestResponse最后调用容器的请求处理方法