Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to design connectors in Tomcat

2025-03-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

This article is to share with you about how the connector in Tomcat is designed. The editor thinks it is very practical, so I share it with you to learn. I hope you can get something after reading this article.

Review of the previous issue

The previous article "how Tomcat starts in SpringBoot" starts from the start of the main method, peering into how SpringBoot starts Tomcat. In the analysis of Tomcat, we highlighted that Tomcat mainly includes two components, connector (Connector) and container (Container), as well as their internal structure diagrams, so today we will analyze how the connector in Tomcat is designed and what its function is.

Note: the tomcat version of this article is 9.0.21, zero-based readers are not recommended to read.

Start with the Connector source code

Since we are here to parse the connector (Connector), let's start with the source code directly. I will remove the unimportant parts of all the source code, so I will ignore most of the source code details and only focus on the process. The source code is as follows (high-energy early warning, a lot of code):

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

Let's take a look at the construction method of Connector. In fact, we only do one thing, and that is to set the corresponding ProtocolHandler according to the protocol. According to the name, we know that this is the protocol processing class, so an important sub-module inside the connector is ProtocolHandler.

About the life cycle

We see that Connector inherits LifecycleMBeanBase, so let's look at the final inheritance relationship of Connector:

We see that the final implementation is the Lifecycle interface, let's see where this interface is sacred. Let me take down the notes on its interface and explain it.

/ * 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.

This annotation translation means that this interface is provided to the component declaration cycle management and provides a declaration cycle flow diagram. Here we just need to know the normal process:

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

Explore connectors from the lifecycle

According to the lifecycle description above, we can know that the Connector is managed according to this declaration cycle, so we found a clue, so the connector will definitely initialize and then start. We can see what the connector initialization has done by looking at its initInternal () method. The source code is as follows:

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

According to the above source code, we find that we mainly deal with protocolHandler and initialize it. At the same time, we notice that protocolHandler sets an adapter. Let's see what the adapter does. The trace source code is as follows:

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

This comment has made it very straightforward that this adapter is used to invoke the connector. Let's move on to the initialization method of protocolHandler.

/ * Endpoint that provides low-level network Endpoint that provides low-level network O-must be matched to the * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO * Endpoint etc.). * / private final AbstractEndpoint 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 ();}

There is a new object, endpoint, and we can tell from the annotations that endpoint is used to handle network IO and must match to a specified subclass (such as Nio, that is, NioEndPoint processing). Endpoint.init () actually does some configuration of the network, and then initializes it. According to our cycle management above, we know that init () is followed by start (), so we look at the start () source code of Connector:

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.类:NioEndpointprotected 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 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 handles processKey (sk, socketWrapper);}} / / omits part of the code}

This completes the call by taking out the previously registered event through selector.

/ / 9. Class: NioEndPoint inner class Poller implements Runnable protected void processKey (SelectionKey sk, NioSocketWrapper socketWrapper) {/ / omit most of the code processSocket (socketWrapper, SocketEvent.OPEN_WRITE, true)} / / 10. Class: AbstractEndPoint public boolean processSocket (SocketWrapperBase socketWrapper, SocketEvent event, boolean dispatch) {/ / omit some code Executor executor = getExecutor (); if (dispatch & & executor! = null) {executor.execute (sc);} else {sc.run ();} return true;} / / 11. Class: 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 ();}} / / Class: 12.NioEndPoint extends AbstractJsseEndpoint protected void doRun () {/ / omit part of the code 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);}

The run method that Poller calls or executes run () with the executor thread pool, the final call is the doRun () method in each child EndPoint, and eventually a Handler is taken to process the socketWrapper. Continue to look at the source code:

/ / Class: 13.AbstractProtocol inner class ConnectionHandler implements AbstractEndpoint.Handler public SocketState process (SocketWrapperBase wrapper, SocketEvent status) {/ / omit part of the code state = processor.process (wrapper, status); return SocketState.CLOSED } / / Class: 14.AbstractProcessorLight implements Processor public SocketState process (SocketWrapperBase socketWrapper, SocketEvent status) throws IOException {/ / omit some code state = service (socketWrapper); return state;}

This part of the source code indicates that the final process is called through an implementation class of the Processor interface, and it will eventually be called into various subclasses. Then the processor here is actually dealing with application protocols. We can check the implementation classes of AbstractProcessorLight, which are AjpProcessor, Http11Processor and StreamProcessor, respectively, which support three application layer protocols on behalf of tomcat:

AJP protocol

HTTP.1 protocol

HTTP2.0 protocol

Here we take the commonly used HTTP1.1 as an example to continue to look at the source code:

/ / Class: 15. Http11Processor extends AbstractProcessorpublic SocketState service (SocketWrapperBase socketWrapper) throws IOException {/ / omit most of the code getAdapter (). Service (request, response); / / omit most code} / / Class: 16 CoyoteAdapter implements Adapterpublic 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);}}

Here we find that the protocol processor will eventually call the adapter (CoyoteAdapter), and the adapter's final job is to convert the Request and Response objects to HttpServletRequest and HttpServletResponse, so that we can call the container, and we have analyzed the flow and function of the entire connector.

Summary

So let's recall the whole process, and I drew a sequence diagram to illustrate:

This diagram contains two processes, one is the initialization of the component, and the other is the process of calling. The Connector mainly initializes two components, ProtcoHandler and EndPoint, but we find from the code structure that they are parent-child relationship, that is, ProtcoHandler contains EndPoint. The latter process is the call chain relationship of each sub-component. To sum up, Acceptor is responsible for receiving the request, then registering with Poller,Poller to handle the request, then calling the processor processor to handle it, and finally turning the request into request and response that conform to the Servlet specification to invoke the container (Container).

Now that we have sorted out the process, let's sort it out structurally:

Back to the connector (Connector) is the source code, we found that the module mentioned above only ProtocolHandler and Adapter belong to the connector, that is, the connector only contains these two major sub-modules, then the subsequent EndPoint, Acceptor, Poller, Processor are ProtocolHandler sub-modules. The core functions of Acceptor and Poller modules are completed in EndPoint, so it is its sub-module, while Processor is relatively independent, so it and EndPoint is a level of sub-module.

Let's use a diagram to illustrate the above relationship:

From the figure above, we can see that the connector is mainly responsible for handling the connection request, and then invokes the container through the adapter. Then the specific process can be refined as follows:

Acceptor listens for network requests and gets requests.

The Poller obtains the listening request and submits it to the thread pool for processing.

Processor generates Tomcat Request objects according to the specific application protocol (HTTP/AJP).

Adapter converts Request objects into Servlet standard Request objects, calling the container.

We analyze the source code of the connector step by step, and analyze that the connector mainly contains two modules, ProtocolHandler and Adapter. ProtocolHandler mainly includes Endpoint module and Processor module. The main function of the Endpoint module is the connection processing, it entrusts the acceptor sub-module to monitor and register the connection, and entrusts the sub-module Poller to handle the connection, while the Processor module mainly applies the protocol processing, and finally submits it to Adapter for object transformation, so that the container (Container) can be called.

The above is how the connectors in Tomcat are designed, and the editor believes that there are some knowledge points that we may see or use in our daily work. I hope you can learn more from this article. For more details, please follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report