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 is the connector designed in Tomcat

2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)05/31 Report--

This article will explain in detail how the connector is designed in Tomcat. The content of the article is of high quality, so the editor will share it with you for reference. I hope you will have some understanding of the relevant knowledge after reading this article.

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

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.类: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 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 some 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 partial code state = processor.process (wrapper, status); return SocketState.CLOSED;} / / Class: 14.AbstractProcessorLight implements Processor public SocketState process (SocketWrapperBase socketWrapper, SocketEvent status) throws IOException {/ / omit partial 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 AbstractProcessor public SocketState service (SocketWrapperBase socketWrapper) throws IOException {/ / omit most code getAdapter (). Service (request, response); / / omit most code} / / Class: 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);}}

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). Click "get Java Framework Information" for free

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.

About how the connector is designed in Tomcat is shared here, I hope the above content can be of some help to you, can learn more knowledge. If you think the article is good, you can share it for more people to see.

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

Servers

Wechat

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

12
Report