Обзор последнего номера
Предыдущая статья《Как 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.
Заявление об авторских правах: Оригинальная статья, пожалуйста, укажите источник для перепечатки.