本篇文章給大家分享的是有關(guān)Tomcat中的連接器是如何設(shè)計的,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供東平網(wǎng)站建設(shè)、東平做網(wǎng)站、東平網(wǎng)站設(shè)計、東平網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、東平企業(yè)網(wǎng)站模板建站服務(wù),10多年東平做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
上一篇文章《Tomcat在SpringBoot中是如何啟動的》從main方法啟動說起,窺探了SpringBoot是如何啟動Tomcat的,在分析Tomcat中我們重點提到了,Tomcat主要包括2個組件,連接器(Connector)和容器(Container)以及他們的內(nèi)部結(jié)構(gòu)圖,那么今天我們來分析下Tomcat中的連接器是怎么設(shè)計的以及它的作用是什么。
說明:本文tomcat版本是9.0.21,不建議零基礎(chǔ)讀者閱讀。
既然是來解析連接器(Connector),那么我們直接從源碼入手,后面所有源碼我會剔除不重要部分,所以會忽略大部分源碼細節(jié),只關(guān)注流程。源碼如下(高能預(yù)警,大量代碼):
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的構(gòu)造方法,其實只做了一件事情,就是根據(jù)協(xié)議設(shè)置對應(yīng)的ProtocolHandler
,根據(jù)名稱我們知道,這是協(xié)議處理類,所以連接器內(nèi)部的一個重要子模塊就是ProtocolHandler
。
我們看到Connector
繼承了LifecycleMBeanBase
,我們來看看Connector
的最終繼承關(guān)系:
我們看到最終實現(xiàn)的是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.
這段注釋翻譯就是,這個接口是提供給組件聲明周期管理的,并且提供了聲明周期流轉(zhuǎn)圖。這里我們只需要知道正常流程即可:
New--->Init()---->Start()---->Stop()--->Destory()
根據(jù)上面的生命周期說明,我們可以知道連接器(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); } } }
根據(jù)上面源碼,我們發(fā)現(xiàn)主要是處理protocolHandler
并初始化它,同時我們注意到了protocolHandler
設(shè)置了一個適配器,我們看看這個適配器是做啥的,跟蹤源碼如下:
/** * The adapter, used to call the connector. * * @param adapter The adapter to associate */ public void setAdapter(Adapter adapter);
這個注釋已經(jīng)說的很直白了,這個適配器就是用來調(diào)用連接器的。我們再繼續(xù)看看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(); }
這里出現(xiàn)了一個新的對象,endpoint
,根據(jù)注釋我們可以知道endpoint
是用來處理網(wǎng)絡(luò)IO的,而且必須匹配到指定的子類(比如Nio,就是NioEndPoint處理)。endpoint.init()
實際上就是做一些網(wǎng)絡(luò)的配置,然后就是初始化完畢了。根據(jù)我們上面的周期管理,我們知道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); } }
其實就是主要調(diào)用 protocolHandler.start()
方法,繼續(xù)跟蹤,為了方便表述,我會把接下來的代碼統(tǒng)一放在一起說明,代碼如下:
//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
創(chuàng)建了一個Poller
,并且啟動它,這里需要補充說明下,這里只是以NioEndPoint為示列,其實Tomcat 主要提供了三種實現(xiàn),分別是AprEndPoint
,NioEndPoint
,Nio2EndPoint
,這里表示了tomcat支持的I/O模型:
APR:采用 Apache 可移植運行庫實現(xiàn),它根據(jù)不同操作系統(tǒng),分別用c重寫了大部分IO和系統(tǒng)線程操作模塊,據(jù)說性能要比其他模式要好(未實測)。
NIO:非阻塞 I/O
NIO.2:異步 I/O
上述代碼主要是開啟兩個線程,一個是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; }
這里可以發(fā)現(xiàn)Acceptor
主要就是接受socket
,然后把它注冊到poller
中,我們繼續(xù)看看是如何注冊的。
/**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()); }
這里發(fā)現(xiàn)最終就是采用NIO模型把其注冊到通道中。(這里涉及NIO網(wǎng)絡(luò)編程知識,不了解的同學(xué)可以傳送這里)。那么注冊完畢后,我們看看Poller做了什么事情。
*/ /**8.類:NioEndPoint內(nèi)部類 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); } } //省略部分代碼 }
這個就是通過selector把之前注冊的事件取出來,從而完成了調(diào)用。
//9.類: NioEndPoint內(nèi)部類 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
調(diào)用的run
方法或者用Executor線程池去執(zhí)行run()
,最終調(diào)用都是各個子EndPoint
中的doRun()
方法,最終會取一個Handler
去處理socketWrapper
。繼續(xù)看源碼:
//類:13.AbstractProtocol內(nèi)部類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; }
這部分源碼表明最終調(diào)用的process是通過一個Processor
接口的實現(xiàn)類來完成的,這里最終也是會調(diào)用到各個子類中,那么這里的處理器其實就是處理應(yīng)用協(xié)議,我們可以查看AbstractProcessorLight
的實現(xiàn)類,分別有AjpProcessor
、Http11Processor
、StreamProcessor
,分別代表tomcat支持三種應(yīng)用層協(xié)議,分別是:
AJP協(xié)議
HTTP.1協(xié)議
HTTP2.0協(xié)議
這里我們以常用的HTTP1.1為例,繼續(xù)看源碼:
//類: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); } }
這里我們發(fā)現(xiàn)協(xié)議處理器最終會調(diào)用適配器(CoyoteAdapter
),而適配器最終的工作是轉(zhuǎn)換Request
和Response
對象為HttpServletRequest
和HttpServletResponse
,從而可以去調(diào)用容器,到這里整個連接器的流程和作用我們就已經(jīng)分析完了。
那么我們來回憶下整個流程,我畫了一張時序圖來說明:
這張圖包含了兩個流程,一個是組件的初始化,一個是調(diào)用的流程。連接器(Connector)主要初始化了兩個組件,ProtcoHandler
和EndPoint
,但是我們從代碼結(jié)構(gòu)發(fā)現(xiàn),他們兩個是父子關(guān)系,也就是說ProtcoHandler
包含了EndPoint
。后面的流程就是各個子組件的調(diào)用鏈關(guān)系,總結(jié)來說就是Acceptor
負責(zé)接收請求,然后注冊到Poller
,Poller
負責(zé)處理請求,然后調(diào)用processor
處理器來處理,最后把請求轉(zhuǎn)成符合Servlet
規(guī)范的request
和response
去調(diào)用容器(Container
)。
我們流程梳理清楚了,接下來我們來結(jié)構(gòu)化的梳理下:
回到連接器(Connector
)是源碼,我們發(fā)現(xiàn),上述說的模塊只有ProtocolHandler
和Adapter
兩個屬于連接器中,也就是說,連接器只包含了這兩大子模塊,那么后續(xù)的EndPoint
、Acceptor
、Poller
、Processor
都是ProtocolHandler
的子模塊。 而Acceptor
和Poller
兩個模塊的核心功能都是在EndPoint
中完成的,所以是其子模塊,而Processor
比較獨立,所以它和EndPoint
是一個級別的子模塊。
我們用圖來說明下上述的關(guān)系:
根據(jù)上圖我們可以知道,連接器主要負責(zé)處理連接請求,然后通過適配器調(diào)用容器。那么具體流程細化可以如下:
Acceptor
監(jiān)聽網(wǎng)絡(luò)請求,獲取請求。
Poller
獲取到監(jiān)聽的請求提交線程池進行處理。
Processor
根據(jù)具體的應(yīng)用協(xié)議(HTTP/AJP)來生成Tomcat Request對象。
Adapter
把Request對象轉(zhuǎn)換成Servlet標(biāo)準(zhǔn)的Request對象,調(diào)用容器。
我們從連接器的源碼,一步一步解析,分析了連接器主要包含了兩大模塊,ProtocolHandler
和Adapter
。ProtocolHandler
主要包含了Endpoint
模塊和Processor
模塊。Endpoint
模塊主要的作用是連接的處理,它委托了Acceptor
子模塊進行連接的監(jiān)聽和注冊,委托子模塊Poller
進行連接的處理;而Processor
模塊主要是應(yīng)用協(xié)議的處理,最后提交給Adapter
進行對象的轉(zhuǎn)換,以便可以調(diào)用容器(Container)。
以上就是Tomcat中的連接器是如何設(shè)計的,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
網(wǎng)站題目:Tomcat中的連接器是如何設(shè)計的
標(biāo)題路徑:http://aaarwkj.com/article24/iggsce.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、響應(yīng)式網(wǎng)站、軟件開發(fā)、網(wǎng)站設(shè)計公司、、App開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)