I. Architecture
Next, let's talk about my understanding of Tomcat architecture.
Overall architecture:
1. component-Oriented Architecture
2. Based on JMX
3. event listening
1) component-Oriented Architecture
The Tomcat code looks huge, but it is clear and simple in structure. It mainly consists of a bunch of components, such as server, service, and connector, and manages these components based on JMX, in addition, the component that implements the above interface also implements the lifecycle interface that represents the lifetime, so that its component performs a fixed lifetime, and extends the lifecycleevent through event listening throughout its lifetime. The following figure shows the Tomcat core classes:
1. Catalina: The main class that interacts with the start/stop shell script. Therefore, if you want to study the process of starting and stopping shell scripts, you should start from this class.
2. SERVER: it is the container of the entire Tomcat component and contains one or more services.
3. Service: A service is a collection of ctor and container. The service uses an appropriate connector to receive user requests and then sends them to the corresponding container for processing.
4. connector: a connector that implements a specific protocol. For example, HTTP, https, and AJP protocols are implemented by default.
5. Container: it can be understood as the container that processes a certain type of requests. Generally, the processing method is to package the processing processor of the request as a valve object, and put it in a pipeline of pipeline type in a certain order. Container has multiple sub-types: Engine, host, context, and wrapper. These sub-types are contained in sequence to process requests of different granularities. In addition, the container contains some basic services, such as loader, manager, and realm.
6. Engine: The engine contains the host and context. After receiving the request, the engine still processes the host in the corresponding context.
7. HOST: the virtual host we understand.
8. Context: the context of a specific web application. Each request is processed in the corresponding context.
9. Wrapper: wrapper is the iner for each servlet, and each servlet has a corresponding wrapper for management.
It can be seen that the core components of server, service, connector, container, engine, host, context, and wrapper are in descending scope and include layer by layer.
The following are the basic components used by the iner:
1. Loader: It is used by container to load various required classes.
2. MANAGER: it is used by the iner to manage the session pool.
3. realm: used for Security Authorization and authentication.
After analyzing the core classes, let's take a look at the Tomcat startup process. The Tomcat startup sequence diagram is as follows:
As you can see, Tomcat is started in two processes: init and start. The core components both implement the lifecycle interface and both require the start method, therefore, in the START process, the START process of sub-components is called layer by layer from the server.
2) based on JMX
Tomcat registers each component and manages it through registry, which is implemented based on JMX, therefore, the init and start processes of the component are actually the start methods for initializing mbean and triggering mbean. A large number of results are shown as follows:
Registry.getRegistry(null, null).invoke(mbeans, "init", false);Registry.getRegistry(null, null).invoke(mbeans, "start", false); |
Such code, in fact, is to manage the behavior and life cycle of various components through JMX.
3) event listening
Each component has various behaviors in its lifecycle, and these actions trigger corresponding events. Tomcat is capable of extending these behaviors by listening for these times. During the init and start processes of the component, a large number of widgets are displayed, such:
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); |
This code triggers a certain type of event. If you want to add your own behavior to it, you just need to register the corresponding type of event.
2. Internal and external of a complete request
I analyzed the Tomcat architecture and startup process a few days ago. Today, I began to study its operation mechanism. Tomcat is essentially a web server that can run JSP/servlet. Therefore, the most typical application is that users access the server through a browser. Tomcat receives the request and forwards it to the servlet, after the servlet completes processing, the result is returned to the client. Today, we will analyze the internal mechanism of such a complete request.
Through debug, the core process of Tomcat processing requests is as follows:
1. Start the endpoint of the pre-support protocol at startup. The endpoint will start a special thread to listen to requests of the corresponding protocol. By default, jioendpoint will be started, jioendpoint receives HTTP requests based on Java serversocket
2. After serversocket receives the socket requested by the client, it is packaged all the way and transmitted from host to wrapper all the way, and then requested to the corresponding Servlet
The above two processes will be highlighted below.
Through the previous analysis (Tomcat source code analysis 1), we can know that Tomcat ctor will be started when Tomcat is started, and proto will start the endpoint through protocolhandler. By default, Tomcat starts two types of ctor, namely HTTP and AJP, which correspond to http11protocol and ajpprotocol in sequence, and both start jioendpoint. Let's take a look at the start method of jioendpoint:
public void start() throws Exception { // Initialize socket if not done before if (!initialized) { init(); } if (!running) { running = true; paused = false; // Create worker collection if (getExecutor() == null) { createExecutor(); } // Start acceptor threads for (int i = 0; i < acceptorThreadCount; i++) { Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i); acceptorThread.setPriority(threadPriority); acceptorThread.setDaemon(getDaemon()); acceptorThread.start(); } } } |
The code above clearly indicates that the acceptorthreadcount threads are started, and each thread is represented by the acceptor agent. For details, refer to the acceptor run method:
public void run() { // Loop until we receive a shutdown command while (running) { // Loop if endpoint is paused while (paused) { try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore } } // Accept the next incoming connection from the server socket try { Socket socket = serverSocketFactory.acceptSocket(serverSocket); serverSocketFactory.initSocket(socket); // Hand this socket off to an appropriate processor if (!processSocket(socket)) { // Close socket right away try { socket.close(); } catch (IOException e) { // Ignore } } }catch ( IOException x ) { if ( running ) log.error(sm.getString("endpoint.accept.fail"), x); } catch (Throwable t) { log.error(sm.getString("endpoint.accept.fail"), t); } // The processor will recycle itself when it finishes } } |
We can conclude that Tomcat receives client requests by listening to socket through serversocket. No need to parse the specific code. Anyone who knows Java net can understand the above Code. Tomcat uses the most standard and basic SOCKET call method to process network requests. After finding the source for processing the request, what we need to do is to make it simple. Break the breakpoint and request the simplest Hello world in the browser, all the way to debug. The following figure shows the sequence of the main process:
The above process can be divided into the following three major core points:
1. Socket parsing and Packaging Based on http1.1 Protocol
2. One way Inoke of standardenginevalve, standardhostvalve, standardcontextvalve and standardwrappervalve. Four different levels of valve are processed and encapsulated at different levels.
3. Implement filter interception and actual servlet requests based on the Application Chain Mode
The above three core points are very rich research points and will be analyzed one by one in the next few days.
3. Portable Thread Pool
Recently, I want to implement a portable thread pool. The specific requirement is that the thread in the pool is used to process certain information, which can be regarded as the external State on which the thread depends. If a simple thread pool is used for implementation, some information must be provided during thread initialization so that the thread cannot be reused. When looking at the source code of the old version of Tomcat, I found the answer. The main idea is to use the thread waiting and calling. The implementation of httpprocessor is based on this idea. The sequence diagram is as follows:
During the initialization of the httpprocessor thread, the required socket object cannot be assigned, because if the socket is granted during the initialization phase, this thread cannot be recycled to process other sockets. Therefore, in the run stage of httpprocessor, the thread is first placed on wait, which is embodied in the await method. The Code is as follows:
/** * Await a newly assigned Socket from our Connector, or null * if we are supposed to shut down. */ private synchronized Socket await() { // Wait for the Connector to provide a new Socket while (!available) { try { wait(); } catch (InterruptedException e) { } } // Notify the Connector that we have received this Socket Socket socket = this.socket; available = false; notifyAll(); if ((debug >= 1) && (socket != null)) log(" The incoming request has been awaited"); return (socket); } |
/** * Await a newly assigned Socket from our Connector, or null * if we are supposed to shut down. */ private synchronized Socket await() { // Wait for the Connector to provide a new Socket while (!available) { try { wait(); } catch (InterruptedException e) { } } // Notify the Connector that we have received this Socket Socket socket = this.socket; available = false; notifyAll(); if ((debug >= 1) && (socket != null)) log(" The incoming request has been awaited"); return (socket); } |
When httpconnector calls the httpprocessor. Assign (socket) method, the socket object is assigned to the thread and the thread is invoked to continue execution. The source code of the assign method is as follows:
/** * Process an incoming TCP/IP connection on the specified socket. Any * exception that occurs during processing must be logged and swallowed. * NOTE: This method is called from our Connector‘s thread. We * must assign it to our own thread so that multiple simultaneous * requests can be handled. * * @param socket TCP socket to process */ synchronized void assign(Socket socket) { // Wait for the Processor to get the previous Socket while (available) { try { wait(); } catch (InterruptedException e) { } } // Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); } |
/** * Process an incoming TCP/IP connection on the specified socket. Any * exception that occurs during processing must be logged and swallowed. * NOTE: This method is called from our Connector‘s thread. We * must assign it to our own thread so that multiple simultaneous * requests can be handled. * * @param socket TCP socket to process */ synchronized void assign(Socket socket) { // Wait for the Processor to get the previous Socket while (available) { try { wait(); } catch (InterruptedException e) { } } // Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); } |
After the thread is invoked and assigned to the socket object, continue to execute the core process method. The complete source code of httpprocessor. Run is as follows:
/** * The background thread that listens for incoming TCP/IP connections and * hands them off to an appropriate processor. */ public void run() { // Process requests until we receive a shutdown signal while (!stopped) { // Wait for the next socket to be assigned Socket socket = await(); if (socket == null) continue; // Process the request from this socket try { process(socket); } catch (Throwable t) { log("process.invoke", t); } // Finish up this request connector.recycle(this); } // Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) { threadSync.notifyAll(); } } |
/** * The background thread that listens for incoming TCP/IP connections and * hands them off to an appropriate processor. */ public void run() { // Process requests until we receive a shutdown signal while (!stopped) { // Wait for the next socket to be assigned Socket socket = await(); if (socket == null) continue; // Process the request from this socket try { process(socket); } catch (Throwable t) { log("process.invoke", t); } // Finish up this request connector.recycle(this); } // Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) { threadSync.notifyAll(); } } |
4. The whole process of request and Response Processing
From the Tomcat source code analysis (2), we can see that a user's request will be processed in n steps, and finally sent to the servlet written by the developer, namely, httpservletrequest and httpservletresponse, therefore, we can think that this process is nothing more than packaging the original socket into the httpservletrequest and httpservletresponse used in servlet, but the packaging functions completed in each link are different from those in some parts. The information flow is shown in:
The request and response classes are shown as follows:
Org. Apache. Coyote. Request and org. Apache. Coyote. response are used internally by Tomcat and are not provided to developers for calling. Classes are of the final type. The following describes the processing process from socket to org. Apache. Catalina. connector. Request by combining the sequence diagram of a complete request:
It can be seen that the Request Parsing and processing process is not done in one method, but is gradually parsed in the process of information flow. Different Levels of processors parse different levels of information, in the parsing process at the same time do some judgment and interception work, for example, when the discovery is to access the WEB-INF resources, will directly return the error to the client and so on.
From:
Http://www.uml.org.cn/j2ee/201306285.asp