十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
本篇文章给大家分享的是有关Tomcat中的连接器是如何设计的,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
创新互联建站专业为企业提供东平网站建设、东平做网站、东平网站设计、东平网站制作等企业网站建设、网页设计与制作、东平企业网站模板建站服务,10多年东平做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
上一篇文章《Tomcat在SpringBoot中是如何启动的》从main方法启动说起,窥探了SpringBoot是如何启动Tomcat的,在分析Tomcat中我们重点提到了,Tomcat主要包括2个组件,连接器(Connector)和容器(Container)以及他们的内部结构图,那么今天我们来分析下Tomcat中的连接器是怎么设计的以及它的作用是什么。
说明:本文tomcat版本是9.0.21,不建议零基础读者阅读。
既然是来解析连接器(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")); }
我们来看看Connector的构造方法,其实只做了一件事情,就是根据协议设置对应的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}. * ** 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 AbstractEndpointendpoint; 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
是用来处理网络IO的,而且必须匹配到指定的子类(比如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* 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支持的I/O模型:
APR:采用 Apache 可移植运行库实现,它根据不同操作系统,分别用c重写了大部分IO和系统线程操作模块,据说性能要比其他模式要好(未实测)。
NIO:非阻塞 I/O
NIO.2:异步 I/O
上述代码主要是开启两个线程,一个是Poller,一个是开启Acceptor,既然是线程,核心的代码肯定是run方法
,我们来查看源码,代码如下:
//4.类:Acceptor 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) { //省略部分代码 Iteratoriterator = 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); } } //省略部分代码 }
这个就是通过selector把之前注册的事件取出来,从而完成了调用。
//9.类: NioEndPoint内部类 Poller implements Runnable protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) { //省略大部分代码 processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true) } //10.类:AbstractEndPoint public boolean processSocket(SocketWrapperBasesocketWrapper, 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 AbstractJsseEndpointprotected 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.Handlerpublic SocketState process(SocketWrapperBasewrapper, 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; }
这部分源码表明最终调用的process是通过一个Processor
接口的实现类来完成的,这里最终也是会调用到各个子类中,那么这里的处理器其实就是处理应用协议,我们可以查看AbstractProcessorLight
的实现类,分别有AjpProcessor
、Http11Processor
、StreamProcessor
,分别代表tomcat支持三种应用层协议,分别是:
AJP协议
HTTP.1协议
HTTP2.0协议
这里我们以常用的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
根据具体的应用协议(HTTP/AJP)来生成Tomcat Request对象。
Adapter
把Request对象转换成Servlet标准的Request对象,调用容器。
我们从连接器的源码,一步一步解析,分析了连接器主要包含了两大模块,ProtocolHandler
和Adapter
。ProtocolHandler
主要包含了Endpoint
模块和Processor
模块。Endpoint
模块主要的作用是连接的处理,它委托了Acceptor
子模块进行连接的监听和注册,委托子模块Poller
进行连接的处理;而Processor
模块主要是应用协议的处理,最后提交给Adapter
进行对象的转换,以便可以调用容器(Container)。
以上就是Tomcat中的连接器是如何设计的,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注创新互联行业资讯频道。