// org.apache.catalina.startup.Catalina#start /** * Start a new server instance. */ publicvoidstart(){
if (getServer() == null) { // 首次会走到这里,负责加载web.xml,初始化对应的组件 load(); }
if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; }
long t1 = System.nanoTime();
// Start the new server try { // 调用server的start方法 getServer().start(); } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; }
long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); }
// Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } // startup时Bootstrap会设置为true // 调用server的await,退出后调用自身的stop方法 if (await) { await(); stop(); } }
// Register global String cache // Note although the cache is global, if there are multiple Servers // present in the JVM (may happen when embedding) then the same cache // will be registered under multiple names onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory MBeanFactory factory = new MBeanFactory(); factory.setContainer(this); onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources globalNamingResources.init();
// Populate the extension validator with JARs from common and shared // class loaders // 省略... // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].init(); } }
// org.apache.catalina.core.StandardServer#await /** * Wait until a proper shutdown command is received, then return. * This keeps the main thread alive - the thread pool listening for http * connections is daemon threads. */ @Override publicvoidawait(){ // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } // port没有定义的话,就直接没10s检查一次是否结束服务 // 这里使用了变量awaitThread来标识结束,当然他是volatile的 if( port==-1 ) { try { awaitThread = Thread.currentThread(); while(!stopAwait) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { // continue and check the flag } } } finally { awaitThread = null; } return; }
// 这里会启动一个Server,监听shutdown的端口,和发过来的命令 // Set up a server socket to wait on try { awaitSocket = new ServerSocket(port, 1, InetAddress.getByName(address)); } catch (IOException e) { log.error("StandardServer.await: create[" + address + ":" + port + "]: ", e); return; }
try { awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command while (!stopAwait) { ServerSocket serverSocket = awaitSocket; if (serverSocket == null) { break; }
// Wait for the next connection Socket socket = null; StringBuilder command = new StringBuilder(); try { InputStream stream; long acceptStartTime = System.currentTimeMillis(); try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (SocketTimeoutException ste) { // This should never happen but bug 56684 suggests that // it does. log.warn(sm.getString("standardServer.accept.timeout", Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste); continue; } catch (AccessControlException ace) { log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace); continue; } catch (IOException e) { if (stopAwait) { // Wait was aborted with socket.close() break; } log.error("StandardServer.await: accept: ", e); break; }
// Read a set of characters from the socket int expected = 1024; // Cut off to avoid DoS attack while (expected < shutdown.length()) { if (random == null) random = new Random(); expected += (random.nextInt() % 1024); } while (expected > 0) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { log.warn("StandardServer.await: read: ", e); ch = -1; } // Control character or EOF (-1) terminates loop if (ch < 32 || ch == 127) { break; } command.append((char) ch); expected--; } } finally { // Close the socket now that we are done with it try { if (socket != null) { socket.close(); } } catch (IOException e) { // Ignore } }
// Match against our command string boolean match = command.toString().equals(shutdown); if (match) { log.info(sm.getString("standardServer.shutdownViaPort")); break; } else log.warn("StandardServer.await: Invalid command '" + command.toString() + "' received"); } } finally { ServerSocket serverSocket = awaitSocket; awaitThread = null; awaitSocket = null;
// Close the server socket and return if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { // Ignore } } } }
➜ bin telnet localhost 8005 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. sdf Connection closed by foreign host.
# 上面的命令不对,tomcat没有反应,这里还能连接8005端口 ➜ bin telnet localhost 8005 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. SHUTDOWN Connection closed by foreign host.
# 此时tomcat已经被shutdown了 ➜ bin telnet localhost 8005 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... telnet: connect to address 127.0.0.1: Connection refused telnet: Unable to connect to remote host
被shutdown的同时,会在Catalina.out中打印如下的日志:
1
27-Nov-2021 21:00:14.933 INFO [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
如果下次,tomcat莫名奇妙shutdown了,可以考虑下是不是被人打接口导致的。
Service
A “Service” is a collection of one or more “Connectors” that share
a single “Container” Note: A “Service” is not itself a “Container”,
so you may not define subcomponents such as “Valves” at this level.
/** * The set of Connectors associated with this Service. */ protected Connector connectors[] = new Connector[0]; privatefinal Object connectorsLock = new Object();
/** * Add a new Connector to the set of defined Connectors, and associate it * with this Service's Container. * * @param connector The Connector to be added */ @Override publicvoidaddConnector(Connector connector){
synchronized (connectorsLock) { // 省略 } }
重要属性变更时,会发出一个PropertyChangeEvent:
1 2 3 4 5 6 7
/** * The property change support for this component. */ protectedfinal PropertyChangeSupport support = new PropertyChangeSupport(this);
// Report this property change to interested listeners support.firePropertyChange("container", oldEngine, this.engine);
Engine
If used, an Engine is always the top level Container in a Catalina hierarchy.
It is useful in the following types of scenarios:
You wish to use Interceptors that see every single request processed by the entire engine.
You wish to run Catalina in with a standalone HTTP connector, but still want support for multiple virtual hosts.
Standard implementation of the Wrapper interface that represents an individual servlet definition. No child Containers are allowed, and the parent Container must be a Context.
55 <!--The connectors can use a shared executor, you can define one or more named thread pools--> 56 <!-- 57 <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" 58 maxThreads="150" minSpareThreads="4"/> 59 -->
// org.apache.catalina.startup.ConnectorCreateRule#begin @Override publicvoidbegin(String namespace, String name, Attributes attributes) throws Exception { Service svc = (Service)digester.peek(); Executor ex = null; if ( attributes.getValue("executor")!=null ) { // 如果配置executor属性,则从service中,查找对应的executor ex = svc.getExecutor(attributes.getValue("executor")); } Connector con = new Connector(attributes.getValue("protocol")); if (ex != null) { // 设置executor为共享的 setExecutor(con, ex); } String sslImplementationName = attributes.getValue("sslImplementationName"); if (sslImplementationName != null) { setSSLImplementationName(con, sslImplementationName); } digester.push(con); }
executor
A reference to the name in an Executor element. If this attribute is set, and the named executor exists, the connector will use the executor, and all the other thread attributes will be ignored. Note that if a shared executor is not specified for a connector then the connector will use a private, internal executor to provide the thread pool
//org.apache.catalina.core.StandardThreadExecutor#startInternal /** * Start the component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protectedvoidstartInternal()throws LifecycleException {
taskqueue = new TaskQueue(maxQueueSize); TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority()); // 注意,这里是tomcat自己实现的ThreadPoolExecutor executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf); executor.setThreadRenewalDelay(threadRenewalDelay); if (prestartminSpareThreads) { executor.prestartAllCoreThreads(); } taskqueue.setParent(executor);