Как устроены коннекторы в Tomcat

Java Tomcat

Обзор последнего номера

Предыдущая статья《Как Tomcat запускается в SpringBoot«Начиная с основного метода, мы подглядываем за тем, как SpringBoot запускает Tomcat. При анализе Tomcat мы выделили, что Tomcat в основном включает в себя два компонента, Connector и Container, а также схемы их внутренней структуры. Поэтому сегодня мы проанализируем, как работает коннектор в Tomcat разработан и какова его роль.

Примечание. Версия этой статьи для Tomcat — 9.0.21, и ее не рекомендуется читать читателям с отсчетом от нуля.

Из исходного кода соединителя (Connector)

Поскольку речь идет о разборе коннектора (Connector), то мы начнем непосредственно с исходного кода. Неважные части всего исходного кода я уберу позже, поэтому большую часть деталей исходного кода я проигнорирую и сосредоточусь только на процессе . Исходный код выглядит следующим образом (высокоэнергетическое предупреждение, много кода):

public class Connector extends LifecycleMBeanBase  {
    public Connector() {
        this("org.apache.coyote.http11.Http11NioProtocol");
    }


    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"));
    }
    
  

Давайте посмотрим на метод построения коннектора, На самом деле делается только одно, а именно устанавливается соответствующий протокол.ProtocolHandler, По названию мы знаем, что это класс обработки протокола, поэтому важным подмодулем внутри коннектора являетсяProtocolHandler.

О жизненном цикле

Мы виделиConnectorнаследоватьLifecycleMBeanBase, Давайте посмотримConnectorОкончательные отношения наследования:

То, что мы видим, наконец, достигнуто,LifecycleИнтерфейс, давайте посмотрим, насколько свят этот интерфейс. Я снял аннотацию его интерфейса и объяснил его.

/**
 * Common interface for component life cycle methods.  Catalina components
 * may implement this interface (as well as the appropriate interface(s) for
 * the functionality they support) in order to provide a consistent mechanism
 * to start and stop the component.
 *            start()
 *  -----------------------------
 *  |                           |
 *  | init()                    |
 * NEW -»-- INITIALIZING        |
 * | |           |              |     ------------------«-----------------------
 * | |           |auto          |     |                                        |
 * | |          \|/    start() \|/   \|/     auto          auto         stop() |
 * | |      INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»---  |
 * | |         |                                                            |  |
 * | |destroy()|                                                            |  |
 * | --»-----«--    ------------------------«--------------------------------  ^
 * |     |          |                                                          |
 * |     |         \|/          auto                 auto              start() |
 * |     |     STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----
 * |    \|/                               ^                     |  ^
 * |     |               stop()           |                     |  |
 * |     |       --------------------------                     |  |
 * |     |       |                                              |  |
 * |     |       |    destroy()                       destroy() |  |
 * |     |    FAILED ----»------ DESTROYING ---«-----------------  |
 * |     |                        ^     |                          |
 * |     |     destroy()          |     |auto                      |
 * |     --------»-----------------    \|/                         |
 * |                                 DESTROYED                     |
 * |                                                               |
 * |                            stop()                             |
 * ----»-----------------------------»------------------------------
 *
 * Any state can transition to FAILED.
 *
 * Calling start() while a component is in states STARTING_PREP, STARTING or
 * STARTED has no effect.
 *
 * Calling start() while a component is in state NEW will cause init() to be
 * called immediately after the start() method is entered.
 *
 * Calling stop() while a component is in states STOPPING_PREP, STOPPING or
 * STOPPED has no effect.
 *
 * Calling stop() while a component is in state NEW transitions the component
 * to STOPPED. This is typically encountered when a component fails to start and
 * does not start all its sub-components. When the component is stopped, it will
 * try to stop all sub-components - even those it didn't start.
 *
 * Attempting any other transition will throw {@link LifecycleException}.
 *
 * </pre>
 * The {@link LifecycleEvent}s fired during state changes are defined in the
 * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the
 * attempted transition is not valid.

Перевод этого комментария заключается в том, что этот интерфейс предоставляется для управления циклом объявления компонентов и предоставляет блок-схему цикла объявления. Здесь нам просто нужно знать нормальный процесс:

New--->Init()---->Start()---->Stop()--->Destory()

Исследуйте соединители из жизненного цикла

Согласно приведенному выше описанию жизненного цикла, мы можем знать, что разъем (Connector) управляется в соответствии с циклом объявления, таким образом, мы нашли подсказку, поэтому компоновщик определенно инициализируется, а затем запускается. мы смотрим на это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.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.init();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }
    }
}

Согласно приведенному выше исходному коду, мы обнаружили, что основная обработкаprotocolHandlerи инициализируем его, пока замечаемprotocolHandlerАдаптер настроен. Давайте посмотрим, что делает этот адаптер. Исходный код отслеживания выглядит следующим образом:

   /**
     * The adapter, used to call the connector.
     *
     * @param adapter The adapter to associate
     */
    public void setAdapter(Adapter adapter);

Этот комментарий был очень простым, этот адаптер используется для вызова коннектора. продолжим смотретьprotocolHandlerметод инициализации

 /**
     * Endpoint that provides low-level network I/O - must be matched to the
     * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO
     * Endpoint etc.).
     */
private final AbstractEndpoint<S,?> endpoint;

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

        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) {
            rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
            Registry.getRegistry(null, null).registerComponent(
                    getHandler().getGlobal(), rgOname, null);
        }

        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length()-1));
        endpoint.setDomain(domain);

        endpoint.init();
    }

А вот и новый объектendpoint, по аннотациям мы можем знать, чтоendpointОн используется для обработки сетевого ввода-вывода и должен соответствовать указанному подклассу (например, Nio, который является обработкой NioEndPoint).endpoint.init()На самом деле, это сделать некоторую настройку сети, а затем инициализация завершена. Из приведенного выше управления циклами мы знаемinit()после этогоstart(), так что проверяемConnectorизstart()Исходный код:

 protected void startInternal() throws LifecycleException {

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

        setState(LifecycleState.STARTING);

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

По сути, главный призывprotocolHandler.start()метод, продолжайте отслеживать, для удобства выражения я соберу следующий код, чтобы объяснить, код выглядит следующим образом:

//1.类:AbstractProtocol implements ProtocolHandler,
        MBeanRegistration
 public void start() throws Exception {
     // 省略部分代码
    endpoint.start();
    }

//2. 类:AbstractEndPoint   
public final void start() throws Exception {
       // 省略部分代码
        startInternal();
    }
 /**3.类:NioEndPoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>
     * Start the NIO endpoint, creating acceptor, poller threads.
     */
    @Override
    public void startInternal() throws Exception {
        //省略部分代码
       
            // Start poller thread
            poller = new Poller();
            Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();

            startAcceptorThread();
        }
    }

На этом, собственно, весь код запуска завершен, мы видим, что в концеNioEndPointсоздалPoller, и запустить его.Здесь нужно дополнить.Вот как раз NioEndPoint как пример.На самом деле Tomcat в основном предоставляет три реализации,а именно:AprEndPoint,NioEndPoint,Nio2EndPoint, который представляет модель ввода-вывода, поддерживаемую tomcat:

APR: Он реализован переносимой библиотекой времени выполнения Apache. Он переписывает большинство модулей операций ввода-вывода и системных потоков на c в соответствии с различными операционными системами. Говорят, что производительность лучше, чем в других режимах (не измерялось).

NIO: неблокирующий ввод-вывод

NIO.2: Асинхронный ввод-вывод

Вышеприведенный код в основном предназначен для открытия двух потоков, один из которых является Poller, а другой — для открытия Acceptor, поскольку это поток, основной код должен бытьrun方法, давайте посмотрим на исходный код, код выглядит следующим образом:

//4.类:Acceptor<U> implements Runnable
 public void run() {
 //省略了部分代码
                U socket = null;
                    socket = endpoint.serverSocketAccept();
                // Configure the socket
                if (endpoint.isRunning() && !endpoint.isPaused()) {
                    // setSocketOptions() will hand the socket off to
                    // an appropriate processor if successful
                    //核心逻辑
                    if (!endpoint.setSocketOptions(socket)) {
                        endpoint.closeSocket(socket);
                    }
                } else {
                    endpoint.destroySocket(socket);
                }
            
        state = AcceptorState.ENDED;
}
//5.类:NioEndpoint
protected boolean setSocketOptions(SocketChannel socket) {
        // Process the connection
        //省略部分代码
        try {
            // Disable blocking, polling will be used
            socket.configureBlocking(false);
            Socket sock = socket.socket();
            socketProperties.setProperties(sock);


            NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this);
            channel.setSocketWrapper(socketWrapper);
            socketWrapper.setReadTimeout(getConnectionTimeout());
            socketWrapper.setWriteTimeout(getConnectionTimeout());
            socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            socketWrapper.setSecure(isSSLEnabled());
            //核心逻辑
            poller.register(channel, socketWrapper);
            return true;
  
    }

можно найти здесьAcceptorГлавное принятьsocket, затем зарегистрируйте его с помощьюpoller, мы продолжаем видеть, как зарегистрироваться.

/**6.类NioEndpoint
         * Registers a newly created socket with the poller.
         *
         * @param socket    The newly created socket
         * @param socketWrapper The socket wrapper
         */
        public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) {
            socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            PollerEvent r = null;
            if (eventCache != null) {
                r = eventCache.pop();
            }
            if (r == null) {
                r = new PollerEvent(socket, OP_REGISTER);
            } else {
                r.reset(socket, OP_REGISTER);
            }
            addEvent(r);
        }
/** 7.类:PollerEvent implements Runnable
 public void run() {
    //省略部分代码
    socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper());
        }

Здесь обнаружено, что для регистрации в канале используется модель NIO. (Это включает в себя знание сетевого программирования NIO, учащиеся, которые не понимают, могут отправитьздесь). Итак, после регистрации посмотрим, что делает Poller.

*/        
  /**8.类:NioEndPoint内部类 Poller implements Runnable
  **/  
  @Override
        public void run() {
            // Loop until destroy() is called
            while (true) {
                //省略部分代码

                Iterator<SelectionKey> iterator =
                    keyCount > 0 ? selector.selectedKeys().iterator() : null;
                // Walk through the collection of ready keys and dispatch
                // any active event.
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    // Attachment may be null if another thread has called
                    // cancelledKey()
                    if (socketWrapper == null) {
                        iterator.remove();
                    } else {
                        iterator.remove();
                        //sock处理
                        processKey(sk, socketWrapper);
                    }
                }
        //省略部分代码
        }    

Это для того, чтобы вынуть ранее зарегистрированное событие через селектор, тем самым завершив вызов.

//9.类: NioEndPoint内部类 Poller  implements Runnable     
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
         //省略大部分代码
           processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)
    
}
       
//10.类:AbstractEndPoint        
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
           SocketEvent event, boolean dispatch) {
       //省略部分代码
           Executor executor = getExecutor();
           if (dispatch && executor != null) {
               executor.execute(sc);
           } else {
               sc.run();
           }
      
       return true;
   }  
//11.类:SocketProcessorBase  implements Runnable   
public final void run() {
       synchronized (socketWrapper) {
           // It is possible that processing may be triggered for read and
           // write at the same time. The sync above makes sure that processing
           // does not occur in parallel. The test below ensures that if the
           // first event to be processed results in the socket being closed,
           // the subsequent events are not processed.
           if (socketWrapper.isClosed()) {
               return;
           }
           doRun();
       }
   }
   
//类:12.NioEndPoint   extends AbstractJsseEndpoint<NioChannel,SocketChannel> 
protected void doRun() {
       //省略部分代码
               if (handshake == 0) {
                   SocketState state = SocketState.OPEN;
                   // Process the request from this socket
                   if (event == null) {
                       state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                   } else {
                       state = getHandler().process(socketWrapper, event);
                   }
                   if (state == SocketState.CLOSED) {
                       poller.cancelledKey(key, socketWrapper);
                   }
               }

       } 
       

Pollerназываетсяrunметод или используйте пул потоков Executor для выполненияrun(), последний звонок - каждый ребенокEndPointсерединаdoRun()метод, который в конечном итоге займетHandlerиметь дело сsocketWrapper. Продолжайте смотреть исходный код:

//类:13.AbstractProtocol内部类ConnectionHandler implements AbstractEndpoint.Handler<S>
 public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
            //省略部分代码
    
            state = processor.process(wrapper, status);
      
            return SocketState.CLOSED;
        }
        
//类:14.AbstractProcessorLight implements Processor 
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
            throws IOException {
            //省略部分代码
           
            state = service(socketWrapper);
            
        return state;
    }

Эта часть исходного кода указывает, что процесс окончательного вызова выполняется черезProcessorКласс реализации интерфейса завершен, и в конечном итоге он будет вызываться для каждого подкласса, поэтому процессор здесь фактически обрабатывает протокол приложения, мы можем просмотретьAbstractProcessorLightКлассы реализации соответственно имеютAjpProcessor,Http11Processor,StreamProcessor, которые означают, что tomcat поддерживает три протокола прикладного уровня, а именно:

Здесь мы возьмем в качестве примера широко используемый HTTP1.1 и продолжим рассмотрение исходного кода:

//类:15. Http11Processor extends AbstractProcessor
public SocketState service(SocketWrapperBase<?> socketWrapper)
        throws IOException {
        //省略大部分代码
             getAdapter().service(request, response);
        //省略大部分代码   
        } 
//类:16   CoyoteAdapter implements Adapter
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
        postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(
                        connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            
    }

Здесь мы обнаруживаем, что обработчик протокола в конечном итоге вызовет адаптер (CoyoteAdapter), а последней задачей адаптера является преобразованиеRequestа такжеResponseобъектHttpServletRequestа такжеHttpServletResponse, чтобы можно было вызвать контейнер, а здесь мы разобрали процесс и функцию всего коннектора.

резюме

Итак, давайте вспомним весь процесс, для иллюстрации я нарисовал диаграмму последовательности:

Эта картинка содержит два процесса, один из которых — инициализация компонента, а другой — вызывающий процесс. Коннектор (Connector) в основном инициализирует два компонента,ProtcoHandlerа такжеEndPoint, но из структуры кода мы обнаружили, что эти два отношения являются отношениями родитель-потомок, то естьProtcoHandlerсодержитEndPoint. Следующий процесс представляет собой отношение цепочки вызовов каждого подкомпонента.AcceptorОтвечает за получение запросов и последующую регистрацию наPoller,Pollerответственный за обработку запроса и последующий вызовprocessorПроцессор обрабатывает его и, наконец, преобразует запрос в соответствующийServletнормативныйrequestа такжеresponseвызвать контейнер (Container).

Наш процесс разобрался четко, теперь давайте разберем его структурированно:

вернуться к разъему (Connector) является исходным кодом, мы обнаружили, что упомянутые выше модули имеют толькоProtocolHandlerа такжеAdapterДва принадлежат коннектору, то есть коннектор содержит только эти два подмодуля, затемEndPoint,Acceptor,Poller,ProcessorобеProtocolHandlerподмодуль. а такжеAcceptorа такжеPollerОсновные функции обоих модулей находятся вEndPointсделано в , то и его подмодуль, аProcessorотносительно самостоятельна, поэтому она иEndPointявляется уровнем подмодулей.

Мы используем диаграмму, чтобы проиллюстрировать вышеуказанную связь:

Согласно приведенному выше рисунку мы можем знать, что коннектор в основном отвечает за обработку запросов на подключение, а затем за вызов контейнера через адаптер. Тогда конкретный процесс можно уточнить следующим образом:

  • AcceptorСлушайте сетевые запросы и получайте запросы.
  • PollerПолучите отслеживаемый запрос и отправьте его в пул потоков для обработки.
  • ProcessorСоздайте объект запроса Tomcat в соответствии с конкретным протоколом приложения (HTTP/AJP).
  • AdapterПреобразуйте объект запроса в стандартный объект запроса сервлета и вызовите контейнер.

Суммировать

Из исходного кода соединителя мы проанализировали его шаг за шагом и проанализировали, что соединитель в основном включает в себя два модуля.ProtocolHandlerа такжеAdapter.ProtocolHandlerв основном включаетEndpointмодули иProcessorмодуль.EndpointОсновная роль модуля — обработка соединения, которое делегируетAcceptorПодмодули отслеживают и регистрируют соединения, а также делегируют подмодули.Pollerобработать соединение; иProcessorМодуль в основном обрабатывает протокол приложения и, наконец, отправляетсяAdapterПреобразуйте объект, чтобы можно было вызывать контейнер. Кроме того, мы также добавили некоторые дополнительные знания в процессе анализа исходного кода:

  • Модели ввода-вывода, поддерживаемые текущей версией Tomcat: модель APR, модель NIO, модель NIO.2.
  • Tomcat поддерживает протоколы AJP и HTTP, из которых HTTP далее делится на HTTP1.1 и HTTP2.0.

Заявление об авторских правах: Оригинальная статья, пожалуйста, укажите источник для перепечатки.