当前端发起一次请求,我想了解tomcat是如何处理的。这里写一些文章做一些简短的记录,方便后续复习。

Connector的创建:当实例化一个Connector,构造器函数会通过反射的方式创建一个ProtocolHandler。这里的protocolHandlerClassName实际上是:"org.apache.coyote.http11.Http11NioProtocol";

   public Connector(String protocol) {
        setProtocol(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;
        }
        ...
    }

为什么要在创建Connector的时候绑定一个ProtocolHandler呢?其实道理很简单,当客户端和服务器发送网络请求时,必须约定协议,tomcat常常作为web端服务器,因此默认提供处理http协议的ProtocolHandler。

而在创建Http11NioProtocol过程中,又创建了下面的内容:

 public Http11NioProtocol() {
        // 创建一个Nio模型的端点
        super(new NioEndpoint());
    }

 public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
        super(endpoint);
        setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        // 创建一个ConnectionHandler来处理链接
        ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
        setHandler(cHandler);
        getEndpoint().setHandler(cHandler);
    }

Connector的初始化:
Tomcat核心的组件都实现了Lifecycle接口,被管理的接口,大概都是经历
init()->start()->stop()的生命周期。
tomcat为了统一管理Lifecycle的组件,提供了抽象类LifecycleBase(实现了Lifecycle接口方法),每一个继承LifecycleBase的组件都会有一个状态,它的默认值是:

private volatile LifecycleState state = LifecycleState.NEW;

下面是LifecycleBase提供init()的模板方法:

  @Override
    public final synchronized void init() throws LifecycleException {
        // 对于任何一个组件来说,一定是在新生状态才能够被初始化,如果不是则抛出异常
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }
        try {
            // init前的状态
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            // 真正的初始化方法,每个子类必须实现它
            initInternal();
            // init后的状态
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        }
    }

这个方法有两个要学习的地方:
第一在方法上使用了synchronized+状态判断,比较简单的方式就保证一个了组件,只能初始化。而状态的使用也是很有用的,当我们有一堆组件,组合成某种功能时,只有当所有组件都处于某种正常的状态时,才能够提供服务的。
第二是利用了抽象类,统一规范了初始化的流程。这里也可以看出接口和抽象类的区别,接口更像是定义了一些系列协议。而抽象类表达的是,你想要实现某种功能,你的标准流程是什么?

因此Connector继承了LifecycleBase,所以要看Connector在初始化,都干了什么事,就找到对应的initInternal():

  @Override
    protected void initInternal() throws LifecycleException {
        super.initInternal();
        // 指定了适配器
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
        ....
        try {
            //  Http11NioProtocol init
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }
    }

下面看下protocolHandler.init(),它实际上要调用AbstractProtocol的init(),最后调用NioEndpoint的init()-> bindWithCleanup()->bind():

 public void init() throws Exception {
        if (bindOnInit) {
            // 初始化服务器
            bindWithCleanup();
            bindState = BindState.BOUND_ON_INIT;
        }
        ....
    }

    @Override
    public void bind() throws Exception {
        // 重点!
        initServerSocket();

        setStopLatch(new CountDownLatch(1));

        // Initialize SSL if needed
        initialiseSsl();
    }

    protected void initServerSocket() throws Exception {
        if (getUseInheritedChannel()) {
            // 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"));
            }
        } else {
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            // 获取地址,绑定服务器端口
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            serverSock.socket().bind(addr,getAcceptCount());
        }
        // 服务主线程,主要负责链接的请求,为了控制请求的大小
        // 所以这里采用了阻塞模式
        serverSock.configureBlocking(true); //mimic(模仿) APR behavior
    }

其中getAcceptCount()默认值为:100
在这里我们可以看到,我们平时配置的端口时如何生效的,就在addr中
tomcat中的NIO并不全部都是NIO,对于每个客户端的请求,任然使用的时阻塞模式。
到了这里Connector完成了它的初始化工作,注意并没有启动!
Connector开始运行:这里按照Lifecycle的思想,直接看Connector的startInternal()

   @Override
    public void startInternal() throws Exception {

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

            if (socketProperties.getProcessorCache() != 0) {
                processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getProcessorCache());
            }
            if (socketProperties.getEventCache() != 0) {
                eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
            }
            if (socketProperties.getBufferPool() != 0) {
                nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getBufferPool());
            }

            // Create worker collection
            // endpoint获取请求后,会交给worker去执行,主线程仅仅是获取请求
            // 另外这里采用了非阻塞io,等待链接是阻塞式的,处理其他读写事件是用了非阻塞IO
            if (getExecutor() == null) {
                createExecutor();
            }

            // 设置最大的链接数:8192。这里需要注意,这里是如何工作的,
            // 由于使用了Latch,因此每来一个链接,maxConnection就减少1.直到为0
            // 当为0的时候,不能再接受多的链接请求了
            initializeConnectionLatch();

            // Start poller thread
            poller = new Poller();// 打开选择器,Poller其实是一个Runnable
            Thread pollerThread = new Thread(poller, getName() + "-Poller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();// poller 开始工作。Poller 那么这里去看一下run()

            // 开启Acceptor Thread专门负责接收socket链接,注意它并不进行socket处理
            startAcceptorThread();
        }
    }

wokers和Poller在后面说

接着我们看下startAcceptorThread()

protected void startAcceptorThread() {
        acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor";
        acceptor.setThreadName(threadName);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }

Poller和Acceptor都是线程,因此想要明白它们工作内容,之间看对应的run();
首先是Acceptor:

 @Override
    public void run() {

        int errorDelay = 0;
        long pauseStart = 0;

        try {
            // Loop until we receive a shutdown command
            while (!stopCalled) {

                // Loop if endpoint is paused.
                // There are two likely scenarios here.
                // The first scenario is that Tomcat is shutting down. In this
                // case - and particularly for the unit tests - we want to exit
                // this loop as quickly as possible. The second scenario is a
                // genuine pause of the connector. In this case we want to avoid
                // excessive CPU usage.
                // Therefore, we start with a tight loop but if there isn't a
                // rapid transition to stop then sleeps are introduced.
                // < 1ms       - tight loop
                // 1ms to 10ms - 1ms sleep
                // > 10ms      - 10ms sleep
                while (endpoint.isPaused() && !stopCalled) {
                    if (state != AcceptorState.PAUSED) {
                        pauseStart = System.nanoTime();
                        // Entered pause state
                        state = AcceptorState.PAUSED;
                    }
                    if ((System.nanoTime() - pauseStart) > 1_000_000) {
                        // Paused for more than 1ms
                        try {
                            if ((System.nanoTime() - pauseStart) > 10_000_000) {
                                Thread.sleep(10);
                            } else {
                                Thread.sleep(1);
                            }
                        } catch (InterruptedException e) {
                            // Ignore
                        }
                    }
                }

                if (stopCalled) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    // if we have reached max connections, wait
                    // 限制了最大的请求数。
                    endpoint.countUpOrAwaitConnection();

                    // Endpoint might have been paused while waiting for latch
                    // If that is the case, don't accept new connections
                    if (endpoint.isPaused()) {
                        continue;
                    }

                    U socket = null;
                    try {
                        // Accept the next incoming connection from the server socket
                        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;
                        }
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Configure the socket
                    if (!stopCalled && !endpoint.isPaused()) {
                        // setSocketOptions() will hand the socket off to
                        // an appropriate processor if successful
                        // 处理的socket
                        if (!endpoint.processSocket(socket)) {
                            endpoint.closeSocket(socket);
                        }
                    } else {
                        endpoint.destroySocket(socket);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    String msg = sm.getString("endpoint.accept.fail");
                    // APR specific.
                    // Could push this down but not sure it is worth the trouble.
                    if (t instanceof org.apache.tomcat.jni.Error) {
                        org.apache.tomcat.jni.Error e = (org.apache.tomcat.jni.Error) t;
                        if (e.getError() == 233) {
                            // Not an error on HP-UX so log as a warning
                            // so it can be filtered out on that platform
                            // See bug 50273
                            log.warn(msg, t);
                        } else {
                            log.error(msg, t);
                        }
                    } else {
                            log.error(msg, t);
                    }
                }
            }
        } finally {
            stopLatch.countDown();
        }
        state = AcceptorState.ENDED;
    }

Acceptor作为一个线程一直while循环,直到stop。通过代码也大概看得出
,它的主要工作就是负责接受请求。
在请求到来后,Acceptor会分派给Poller去处理。那么有一个问题是,如果不限制请求量大小,服务器会崩溃,那么接着就会产生一个疑问,既然tomcat有并发限制,为什么我们还要对接口做性能调优?
在这个方法中,有一行很重要的代码endpoint.processSocket(socket)。最初源代码叫endpoint.setSocketOptions(socket),我为了阅读方便,调整成processSocket,后面整体理解了下processSocket确实不太好,因为Acceptor仅仅负责接受请求,它并不处理请求。
由于现在仅仅是start,并没有请求到来,因此线程被阻塞在socket = endpoint.serverSocketAccept();这个方法实际上是:

 protected SocketChannel serverSocketAccept() throws Exception {
        // 由于是阻塞模式,因此没有新的请求到来时,线程被阻塞在这里了
        SocketChannel result = serverSock.accept();
        return result;
    }

好了,到了这里,Connector已经启动好了,可以接受客户端的请求了
Poller呢?由于它和链接的处理,非常紧密,在后续请求到来时说。
Connector的启动主要目的就是提供好服务端的serverSocket,指定好当有socket请求时,要按照协议去处理请求(Http11NioProtocol)


Martin
5 声望0 粉丝

后端