tomcat12启动流程源码分析

信息: Server.服务器版本: Apache Tomcat/12.0.x-dev
信息: Java虚拟机版本:    21

下载源码https://github.com/apache/tomcat,并用idea打开,配置ant编译插件,或者使用我的代码

启动脚本是/bin/startup.bat,内部又执行了bin\catalina.bat脚本,最终是执行了java --classpath bin\bootstrap.jar org.apache.catalina.startup.Bootstrap

1. Bootstrap启动类main方法

private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;public static void main(String args[]) {//传参空数组synchronized (daemonLock) {if (daemon == null) {// Don't set daemon until init() has completedBootstrap bootstrap = new Bootstrap();try {bootstrap.init();} catch (Throwable t) {handleThrowable(t);log.error("Init exception", t);return;}daemon = bootstrap;} else {// When running as a service the call to stop will be on a new// thread so make sure the correct class loader is used to// prevent a range of class not found exceptions.Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);}}try {String command = "start";if (command.equals("start")) {daemon.setAwait(true);//设置属性为true daemon.load(args);daemon.start();if (null == daemon.getServer()) {System.exit(1);}}} catch (Throwable t) {System.exit(1);}
}

首先执行 init() 方法,然后执行 load() 方法,最后执行 start() 方法。下面开始逐个分析

2. 启动类init方法

ClassLoader catalinaLoader = null;public void init() throws Exception {//创建类加载器initClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.getConstructor().newInstance();//指定父加载器为默认的加载器String methodName = "setParentClassLoader";Class<?> paramTypes[] = new Class[1];paramTypes[0] = Class.forName("java.lang.ClassLoader");Object paramValues[] = new Object[1];paramValues[0] = sharedLoader;Method method = startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);catalinaDaemon = startupInstance;
}

2.1. 创建类加载器initClassLoaders

catalinaLoader = createClassLoader("common", null);

private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {//配置类获取信息String value = CatalinaProperties.getProperty("common" + ".loader");//value是: "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"value = replace(value);List<Repository> repositories = new ArrayList<>();String[] repositoryPaths = getPaths(value);return ClassLoaderFactory.createClassLoader(repositoryPaths, parent);
}public static ClassLoader createClassLoader(List<Repository> repositoryPaths, final ClassLoader parent){//把上面的路径转换成具体的文件: repositoryPaths//  0 = {URL@1158} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/"//  1 = {URL@1159} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/annotations-api.jar"//  2 = {URL@1160} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-ant.jar"//  3 = {URL@1161} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-ha.jar"//  4 = {URL@1162} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-ssi.jar"//  5 = {URL@1163} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-storeconfig.jar"//  6 = {URL@1164} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina-tribes.jar"//  7 = {URL@1165} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/catalina.jar"//  8 = {URL@1166} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/ecj-4.33.jar"//  9 = {URL@1167} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/el-api.jar"//  10 = {URL@1168} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jakartaee-migration-1.0.8-shaded.jar"//  11 = {URL@1169} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jasper-el.jar"//  12 = {URL@1170} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jasper.jar"//  13 = {URL@1171} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jaspic-api.jar"//  14 = {URL@1172} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/jsp-api.jar"//  15 = {URL@1173} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/servlet-api.jar"//  16 = {URL@1174} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-api.jar"//  17 = {URL@1175} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-coyote-ffm.jar"//  18 = {URL@1176} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-coyote.jar"//  19 = {URL@1177} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-dbcp.jar"//  20 = {URL@1178} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-cs.jar"//  21 = {URL@1179} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-de.jar"//  22 = {URL@1180} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-es.jar"//  23 = {URL@1181} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-fr.jar"//  24 = {URL@1182} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-ja.jar"//  25 = {URL@1183} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-ko.jar"//  26 = {URL@1184} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-pt-BR.jar"//  27 = {URL@1185} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-ru.jar"//  28 = {URL@1186} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-i18n-zh-CN.jar"//  29 = {URL@1187} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-jdbc.jar"//  30 = {URL@1188} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-jni.jar"//  31 = {URL@1189} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-util-scan.jar"//  32 = {URL@1190} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-util.jar"//  33 = {URL@1191} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/tomcat-websocket.jar"//  34 = {URL@1192} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/websocket-api.jar"//  35 = {URL@1193} "file:/D:/apps/tomcat-source/tomcat/output/build/lib/websocket-client-api.jar"if (parent == null) {return new URLClassLoader(repositoryPaths);} else {return new URLClassLoader(repositoryPaths, parent);}
}
//创建了一个URLClassLoader

2.2. 指定父加载器

public void setParentClassLoader(ClassLoader parentClassLoader) {this.parentClassLoader = parentClassLoader;
}

init 方法完成后,主要做了以下几件事情:

  1. 创建了类加载器 catalinaLoader,用于加载 Tomcat 的核心类。
  2. 设置当前线程的上下文类加载器为 catalinaLoader
  3. 通过反射机制加载 org.apache.catalina.startup.Catalina 类,并创建其实例。
  4. 设置 Catalina 实例的父类加载器为共享类加载器 sharedLoader

整体来说,init 方法的作用是初始化 Tomcat 启动所需的类加载器,并准备好 Catalina 实例以供后续使用。

3. 启动类load方法

daemon.load(args);

Catalina catalinaDaemon;//这个值在上一步init方法赋值了private void load(String[] arguments) throws Exception {// Call the load() methodString methodName = "load";Object param[];Class<?> paramTypes[];if (arguments == null || arguments.length == 0) {paramTypes = null;param = null;} else {paramTypes = new Class[1];paramTypes[0] = arguments.getClass();param = new Object[1];param[0] = arguments;}Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);if (log.isTraceEnabled()) {log.trace("Calling startup class " + method);}method.invoke(catalinaDaemon, param);//也是反射调用,调用了Catalina类中的load方法
}

在看下Catalina类的方法

public void load() {// Before digester - it may be neededinitNaming();//设置系统naming变量// Parse main server.xmlparseServerXml(true);//这个是重点,解析server.xml文件Server s = getServer();if (s == null) { //StandardServer[8005]return;}getServer().setCatalina(this);getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());//D:\apps\tomcat-source\tomcat\output\buildgetServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());//D:\apps\tomcat-source\tomcat\output\build// Stream redirectioninitStreams();//设置系统打印输出// Start the new servertry {getServer().init();//初始化server容器} catch (LifecycleException e) {if (throwOnInitFailure) {throw new Error(e);}}
}

3.1. 设置系统naming变量

没啥好说的,就是设置变量

System.setProperty("catalina.useNaming", "true");
System.setProperty("java.naming.factory.url.pkgs", "org.apache.naming");
System.setProperty("java.naming.factory.initial", "org.apache.naming.java.javaURLContextFactory");

3.2. 解析server.xml文件

parseServerXml(true);

3.2.1. server.xml文件内容

删除了注释

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN"><Listener className="org.apache.catalina.startup.VersionLoggerListener" /><Listener className="org.apache.catalina.core.AprLifecycleListener" /><Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /><Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /><Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /><GlobalNamingResources><Resource name="UserDatabase" auth="Container"type="org.apache.catalina.UserDatabase"description="User database that can be updated and saved"factory="org.apache.catalina.users.MemoryUserDatabaseFactory"pathname="conf/tomcat-users.xml" /></GlobalNamingResources><!-- A "Service" is a collection of one or more "Connectors" that sharea single "Container" Note:  A "Service" is not itself a "Container",so you may not define subcomponents such as "Valves" at this level.Documentation at /docs/config/service.html--><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" /><Engine name="Catalina" defaultHost="localhost"><Realm className="org.apache.catalina.realm.LockOutRealm"><Realm className="org.apache.catalina.realm.UserDatabaseRealm"resourceName="UserDatabase"/></Realm><Host name="localhost"  appBase="webapps"unpackWARs="true" autoDeploy="true"><Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"prefix="localhost_access_log" suffix=".txt"pattern="%h %l %u %t &quot;%r&quot; %s %b" /></Host></Engine></Service>
</Server>

3.2.2. 解析源代码

protected void parseServerXml(boolean start) {// Set configuration source配置文件目录ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), "conf/server.xml"));File file = configFile();//D:\apps\tomcat-source\tomcat\output\build\conf\server.xmltry (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {// Create and execute our DigesterDigester digester = createStartDigester();//创建digester工具用于解析xml文件InputStream inputStream = resource.getInputStream();//resource: D:\apps\tomcat-source\tomcat\output\build\conf\server.xmlInputSource inputSource = new InputSource(resource.getURI().toURL().toString());inputSource.setByteStream(inputStream);digester.push(this);//开始解析xmldigester.parse(inputSource);} catch (Exception e) {log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);}
}
3.2.2.1. 创建digester工具类

创建一个支持解析xml格式的工具,用于解析sever.xml文件,把对应的xml标签创建对应的java类。此类继承了org.xml.sax.ext.DefaultHandler2类(jre包下的)

/*** Create and configure the Digester we will be using for startup.** @return the main digester to parse server.xml*/
protected Digester createStartDigester() {// Initialize the digesterDigester digester = new Digester();// Configure the actions we will be using 配置<Server>标签的java类,还有setServer方法digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");digester.addSetProperties("Server");digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");//GlobalNamingResources标签digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl");digester.addSetProperties("Server/GlobalNamingResources");digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl");digester.addRule("Server/Listener", new ListenerCreateRule(null, "className"));digester.addSetProperties("Server/Listener");digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");//创建<Service>标签对应的java类digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className");digester.addSetProperties("Server/Service");digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service");//Connector标签对应的java类digester.addRule("Server/Service/Connector", new ConnectorCreateRule());digester.addSetProperties("Server/Service/Connector", new String[] { "executor", "sslImplementationName", "protocol" });digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector");// Add RuleSets for nested elementsdigester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));// When the 'engine' is found, set the parentClassLoader.digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader));//省略部分代码...return digester;
}
3.2.2.2. digester解析

digester.parse(inputSource);

public void parse(InputSource inputSource) throws SAXException, IOException {// parse documenttry {XMLInputSource xmlInputSource =new XMLInputSource(inputSource.getPublicId(),inputSource.getSystemId(),null, false);xmlInputSource.setByteStream(inputSource.getByteStream());xmlInputSource.setCharacterStream(inputSource.getCharacterStream());xmlInputSource.setEncoding(inputSource.getEncoding());parse(xmlInputSource);}
}
//最终调用了jre库下面的类
public boolean scanDocument(boolean complete) throws IOException, XNIException {// keep dispatching "events"fEntityManager.setEntityHandler(this);int event = next();//这个方法是核心解析处理方法,生成xml标签对应的java类也是在这个方法中do {//循环读取xml文件里面的每一个标签内容,包含注释,和下面做匹配//断点发现真正处理的标签的逻辑不在这些case中,而是在next()方法中。下面详细看一下next方法switch (event) {case XMLStreamConstants.START_DOCUMENT ://文档解析开始break;case XMLStreamConstants.START_ELEMENT ://标签解析开始break;case XMLStreamConstants.CHARACTERS ://字符处理fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);fDocumentHandler.characters(getCharacterData(),null);break;case XMLStreamConstants.SPACE:break;case XMLStreamConstants.ENTITY_REFERENCE ://实体引用fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);break;case XMLStreamConstants.PROCESSING_INSTRUCTION :fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);fDocumentHandler.processingInstruction(getPITarget(),getPIData(),null);break;case XMLStreamConstants.COMMENT ://文档注释fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);fDocumentHandler.comment(getCharacterData(),null);break;case XMLStreamConstants.DTD :break;case XMLStreamConstants.CDATA:break;default :return false;}//System.out.println("here in before calling next");event = next();//System.out.println("here in after calling next");} while (event!=XMLStreamConstants.END_DOCUMENT && complete);if(event == XMLStreamConstants.END_DOCUMENT) {fDocumentHandler.endDocument(null);return false;}return true;} // scanDocument(boolean):boolean
3.2.2.2.1. 解析xml文件next()核心方法

读取xml文件内容,判断标签类型,如果是'<'开头的,走到SCANNER_STATE_START_OF_MARKUP

//下面的这些方法是java包下的类,逻辑很长复杂,我省略了部分代码
public int next() throws IOException, XNIException {try {do {if (fEntityScanner.skipChar('<', null)) {fScannerState = SCANNER_STATE_START_OF_MARKUP;}switch (fScannerState) {case SCANNER_STATE_START_OF_MARKUP: {//标签hook方法return scanStartElement();}//省略部分代码...}} while (fScannerState == SCANNER_STATE_PROLOG || fScannerState == SCANNER_STATE_START_OF_MARKUP );
}//组装参数,继续调用,第一步是扫描到了<Server>标签
protected boolean scanStartElement(){//省略部分代码...String fElementQName = "Server";Attributes fAttributes = {["port","8005","shutdown"]};fDocumentHandler.startElement(fElementQName, fAttributes, null, null);
}
//调用到了tomcat创建的Digester类
public void startElement(String namespaceURI, String localName, String qName, Attributes list) {// Fire "begin" events for all relevant rules//getRules方法就是获取最开始的配置信息,这里获取到的是Server标签的配置List<Rule> rules = getRules().match(namespaceURI, match);//rules = {ArrayList@1877}  size = 3//  0 = {ObjectCreateRule@1871} "ObjectCreateRule[className=org.apache.catalina.core.StandardServer, attributeName=className]"//  1 = {SetPropertiesRule@2025} "SetPropertiesRule[]"//  2 = {SetNextRule@2026} "SetNextRule[methodName=setServer, paramType=org.apache.catalina.Server]"matches.push(rules);if ((rules != null) && (rules.size() > 0)) {//这里的rulus就是上面的这三个对象:功能分别是创建StandardServer对象、设置server对象里面的变量、调用setServer方法for (Rule value : rules) {try {//执行具体的逻辑:例如创建StandardServer对象、设置StandardServer属性、调用setServer方法//详情参考下面的分析代码value.begin(namespaceURI, name, list);} catch (Error e) {log.error(sm.getString("digester.error.begin"), e);throw e;}}}
}
3.2.2.2.1.1. Digester类的getRules()方法
this.rules = {RulesBase@1296} cache = {HashMap@2032}  size = 137"Server/Service/Engine/Host/Cluster/Channel/Membership/Member" -> {ArrayList@2137}  size = 3"Server/Service/Engine/Host/Cluster/Channel/ChannelListener" -> {ArrayList@2139}  size = 3"Server" -> {ArrayList@1877}  size = 3"Server/Service/Engine/Host/Context/Realm/Realm/Realm" -> {ArrayList@2141}  size = 3"Server/Service/Engine/Host/Context" -> {ArrayList@2143}  size = 4"Server/Listener" -> {ArrayList@2249}  size = 3"Server/GlobalNamingResources" -> {ArrayList@2251}  size = 3"Server/Service/Engine/Cluster/Channel/Sender" -> {ArrayList@2253}  size = 3"Server/GlobalNamingResources/Resource" -> {ArrayList@2283}  size = 3"Server/Service" -> {ArrayList@2285}  size = 3//省略部分。一共是137个key值
3.2.2.2.1.2. 创建StandardServer对象逻辑

value.begin(namespaceURI, name, list);

public void begin(String namespace, String name, Attributes attributes) {//获取到了 org.apache.catalina.core.StandardServerString realClassName = getRealClassName(attributes);//反射创建StandardServer// Instantiate the new object and push it on the context stackClass<?> clazz = digester.getClassLoader().loadClass(realClassName);Object instance = clazz.getConstructor().newInstance();//把对象放到digester中digester.push(instance);
}
3.2.2.2.1.3. 设置StandardServer属性

赋值变量。“port”,“8005”

public void begin(String namespace, String theName, Attributes attributes) {//attributes = ["port","8005","shutdown"];for (int i = 0; i < attributes.getLength(); i++) {String name = attributes.getLocalName(i);//portString value = attributes.getValue(i);//8005//赋值IntrospectionUtils.setProperty(top, name, value, true, actualMethod);}
}//赋值方法setPropertypublic static boolean setProperty(Object o, String name, String value, boolean invokeSetProperty, StringBuilder actualMethod) {//上面的传参“o”是StandardServer实例对象String setter = "set" + capitalize(name);//setPorttry {Method methods[] = findMethods(o.getClass());Method setPropertyMethodVoid = null;Method setPropertyMethodBool = null;// First, the ideal case - a setFoo( String ) method//反射调用setPort方法for (Method item : methods) {Class<?> paramT[] = item.getParameterTypes();if (setter.equals(item.getName())) {//反射invokeitem.invoke(o, new Object[]{value});return true;}}}//省略部分代码...
3.2.2.2.1.4. 调用setServer方法

给Catalina实例赋值sever

3.2.2.2.2. 继续解析server.xml文件

解析server标签中的Listener、Service、Connector、Engine、Host等标签,创建对应的实例java类。
创建逻辑和上面Server逻辑是一样的。

StandardServer
StandardService
Connector
StandardEngine
StandardHost
3.2.2.2.2.1. 值得注意的是Connector类的构造方法中创建了Http11NioProtocol实例
if (protocol == null || "HTTP/1.1".equals(protocol) ||org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol)) {return new org.apache.coyote.http11.Http11NioProtocol();//tomcat8之后默认使用nio
} else if ("AJP/1.3".equals(protocol) ||org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol)) {return new org.apache.coyote.ajp.AjpNioProtocol();
} else {// Instantiate protocol handlerClass<?> clazz = Class.forName(protocol);return (ProtocolHandler) clazz.getConstructor().newInstance();
}
3.2.2.2.2.2. StandardEngine构造方法中创建了StandardEngineValve实例
public StandardEngine() {pipeline.setBasic(new StandardEngineValve());// By default, the engine will hold the reloading threadbackgroundProcessorDelay = 10;
}
3.2.2.2.2.3. StandardHost构造方法中创建了StandardHostValve实例
public StandardHost() {super();pipeline.setBasic(new StandardHostValve());
}

3.3. 设置系统打印输出

// 重定向系统输出流
protected void initStreams() {// Replace System.out and System.err with a custom PrintStreamSystem.setOut(new SystemLogHandler(System.out));System.setErr(new SystemLogHandler(System.err));
}

3.4. 初始化server容器

getServer().init();//初始化server容器

StandardServer 继承了 LifecycleBase 抽象类,因此也继承了 init 方法。LifecycleBase 是 Tomcat 中用于管理组件生命周期的基类。它定义了一些通用的生命周期方法,如 initstartstopdestroy,并提供了状态管理和事件通知的机制。通过继承 LifecycleBaseStandardServer 可以利用这些通用的生命周期管理功能,确保在初始化、启动、停止和销毁过程中执行必要的操作和触发相应的事件。

public final synchronized void init() throws LifecycleException {try {setStateInternal(LifecycleState.INITIALIZING, null, false);//StandServer初始化前执行initInternal();//StandardServer初始化setStateInternal(LifecycleState.INITIALIZED, null, false);//StandardServer初始化后执行,这里没有我们关心的逻辑,重点是上一行【StandardServer初始化】} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());}
}

3.4.1. StandServer初始化前执行

setStateInternal(LifecycleState.INITIALIZING, null, false);
设置当前standerServer的状态为INITIALIZING类型,并且使用观察者模式,执行初始化前事件。

//设置状态
this.state = LifecycleState.INITIALIZING;//触发事件
protected void fireLifecycleEvent(String type, Object data) {LifecycleEvent event = new LifecycleEvent(this, type, data);for (LifecycleListener listener : lifecycleListeners) {//遍历事件监听器列表执行事件方法listener.lifecycleEvent(event);//调用具体的事件监听器}
}
3.4.1.1. StandServer事件监听器列表

下面这些监听器是在server.xml文件配置的
在 Tomcat 12 中,StandardServer 类的 lifecycleListeners 包含以下监听器:

  1. NamingContextListener:用于管理 JNDI(Java Naming and Directory Interface)资源的生命周期。它在 Tomcat 启动时初始化 JNDI 资源,并在停止时清理这些资源。
  2. VersionLoggerListener:用于在 Tomcat 启动时记录版本信息,包括操作系统、JVM 和 Tomcat 的版本。
  3. AprLifecycleListener:用于初始化和终止 APR(Apache Portable Runtime)库。它还负责初始化 SSL(如果配置了)并处理 FIPS 模式(如果启用)。
  4. JreMemoryLeakPreventionListener:用于防止 JRE 内存泄漏。它通过触发一些特定的 JVM 操作来防止常见的内存泄漏问题。
  5. GlobalResourcesLifecycleListener:用于管理全局 JNDI 资源。它在 Tomcat 启动时初始化这些资源,并在停止时清理它们。
  6. ThreadLocalLeakPreventionListener:用于防止线程本地变量引起的内存泄漏。它在 Tomcat 停止时清理线程本地变量,以防止内存泄漏。

这些监听器在 Tomcat 启动、停止等生命周期事件中执行特定的操作,以确保服务器的正常运行和资源管理。这些都是基本的监听器,这里不再详细分析代码

3.4.2. StandServer初始化init()

initInternal();

protected void initInternal() throws LifecycleException {//注册当前standerServer到java的MBean中(在Java中,MBean[Managed Bean]是用于管理和监控资源的 Java 对象。类似于Spring的IoC。来实现对象的管理和监控。)super.initInternal();// Register the naming resources//把NamingResourcesImpl对象注册到MBean中globalNamingResources.init();// Initialize our defined Servicesfor (Service service : findServices()) {//这里的service就一个:StandardServiceservice.init();//StandardService初始化}
}
3.4.2.1. StandardService初始化init()

service.init();
我们先看一下StandardService类,也继承了LifecycleBase 抽象类,因此也继承了 init 方法。

public final synchronized void init() throws LifecycleException {try {setStateInternal(LifecycleState.INITIALIZING, null, false);//StandardService初始化前执行,主要是触发初始化前监听器,standerService默认没有监听器,这里没有逻辑initInternal();//StandardService初始化setStateInternal(LifecycleState.INITIALIZED, null, false);//StandardService初始化后执行,这里没有重要的逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());}
}
3.4.2.1.1. StandardService初始化前执行

setStateInternal(LifecycleState.INITIALIZING, null, false);
设置当前standerService的状态为INITIALIZING类型,并且使用观察者模式,执行初始化前事件。

//设置状态
this.state = LifecycleState.INITIALIZING;//触发事件
protected void fireLifecycleEvent(String type, Object data) {LifecycleEvent event = new LifecycleEvent(this, type, data);for (LifecycleListener listener : lifecycleListeners) {//遍历事件监听器列表执行事件方法,在这里的service没有监听器,lifecycleListeners.size()=0listener.lifecycleEvent(event);//调用具体的事件监听器}
}
3.4.2.1.2. StandardService初始化

initInternal()开始执行service初始化了,直接看下面代码

protected void initInternal() throws LifecycleException {//注册StandardService到MBean中super.initInternal();if (engine != null) {//执行StanderEngine初始化,这里也可以看出来只有一个engineengine.init();}// Initialize any Executors//默认没有配置executor,没有找到!//看源码这个Executor继承了java包下的ExecutorService,多线程相关for (Executor executor : findExecutors()) {if (executor instanceof JmxEnabled) {((JmxEnabled) executor).setDomain(getDomain());}executor.init();}// Initialize mapper listener//mapperListener映射器初始化mapperListener.init();// Initialize our defined Connectors//这里只有一个connector:["http-nio-8080"]for (Connector connector : findConnectors()) {//执行Connector初始化,这里可以看到允许存在多个connectorconnector.init();}
}
3.4.2.1.2.1. StandardEngine初始化init()

engine.init();执行engine初始化操作。
我们先看一下StandardEngine类,也继承了LifecycleBase 抽象类,因此也继承了 init 方法。

public final synchronized void init() throws LifecycleException {try {setStateInternal(LifecycleState.INITIALIZING, null, false);//StandardEngine初始化前执行,触发监听器,这里没有INITIALIZING的监听事件initInternal();//StandardEngine初始化setStateInternal(LifecycleState.INITIALIZED, null, false);//StandardEngine初始后执行,触发监听器,这里没有INITIALIZED的监听事件} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());}
}
3.4.2.1.2.1.1. StandardEngine初始化

initInternal();

protected void initInternal() throws LifecycleException {// Ensure that a Realm is present before any attempt is made to start// one. This will create the default NullRealm if necessary.getRealm();//获取LockOutRealm[StandardEngine] 这个是在server.xml文件配置的super.initInternal();//注册StanderdEngine到MBean
}
3.4.2.1.2.2. mapperListener映射器初始化

mapperListener.init();MapperListener初始化方法。
我们先看一下MapperListener类,也继承了LifecycleBase 抽象类,因此也继承了 init 方法。

public final synchronized void init() throws LifecycleException {try {setStateInternal(LifecycleState.INITIALIZING, null, false);//MapperListener初始化前执行,主要是触发初始化前监听器,MapperListener默认没有监听器,这里没有逻辑initInternal();//MapperListener初始化:注册MapperListener到MBeansetStateInternal(LifecycleState.INITIALIZED, null, false);//MapperListener初始化后执行,这里没有重要的逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());}
}
3.4.2.1.2.3. Connector初始化init()

connector.init();,Connector["http-nio-8080"]初始化方法。
我们先看一下Connector类,也继承了LifecycleBase 抽象类,因此也继承了 init 方法。

public final synchronized void init() throws LifecycleException {try {setStateInternal(LifecycleState.INITIALIZING, null, false);//Connector初始化前执行,主要是触发初始化前监听器,Connector默认没有监听器,这里没有逻辑initInternal();//Connector初始化setStateInternal(LifecycleState.INITIALIZED, null, false);//Connector初始化后执行,这里没有重要的逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());}
}
3.4.2.1.2.3.1. Connector初始化

initInternal(); Connector"http-nio-8080"初始化代码

protected void initInternal() throws LifecycleException {//注册Connector到MBeansuper.initInternal();// Initialize adapter//创建adapter对象:CoyoteAdapter 是 Tomcat 中的一个适配器类,用于将 Coyote 请求和响应对象转换为 Tomcat 的内部请求和响应对象adapter = new CoyoteAdapter(this);//Http11NioProtocol指定adapterprotocolHandler.setAdapter(adapter);// Make sure parseBodyMethodsSet has a default//配置默认请求解析方法:POSTif (null == parseBodyMethodsSet) {setParseBodyMethods(getParseBodyMethods());}try {//http-nio-8080协议初始化protocolHandler.init();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);}
}
3.4.2.1.2.3.1.1. Http11NioProtocol初始化

protocolHandler.init(); http-nio-8080协议初始化,注意这个类没有继承LifecycleBase

public void init() throws Exception {//创建HTTP标头值解析器实现。将原始的 HTTP 数据流解析成结构化的数据:解析请求行:请求行、请求头、请求体、响应头等等httpParser = new HttpParser(relaxedPathChars, relaxedQueryChars);try {//当前类Http11NioProtocol继承了AbstractProtocolsuper.init();//AbstractProtocol初始化} finally {//nothing}
}
3.4.2.1.2.3.1.2. AbstractProtocol初始化

super.init();AbstractProtocol初始化

public void init() throws Exception {//输出日志if (getLog().isInfoEnabled()) {getLog().info("信息: 初始化协议处理器 [http-nio-8080]");logPortOffset();}//注册Http11NioProtocol[http-nio-8080]到MBeanif (oname == null) {// Component not pre-registered so register itoname = createObjectName();//Catalina:type=ProtocolHandler,port=8080if (oname != null) {Registry.getRegistry(null, null).registerComponent(this, oname, null);}}if (this.domain != null) {ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());this.rgOname = rgOname;//Catalina:type=GlobalRequestProcessor,name="http-nio-8080"//注册GlobalRequestProcessor到MBeanRegistry.getRegistry(null, null).registerComponent(getHandler().getGlobal(), rgOname, null);}String endpointName = getName();//"http-nio-8080"endpoint.setName(endpointName.substring(1, endpointName.length() - 1));//http-nio-8080endpoint.setDomain(domain);//Catalina//endpoint = {NioEndpoint@2353} endpoint.init();//NioEndpoint初始化
}
3.4.2.1.2.3.1.3. NioEndpoint初始化

endpoint.init(),NioEndpoint"http-nio-8080" 初始化。主要逻辑是就是bind 8080端口。

public final void init() throws Exception {if (bindOnInit) {//初始化的时候,开始绑定端口[8080]bindWithCleanup();//执行bind0方法:是native方法。bindState = BindState.BOUND_ON_INIT;}if (this.domain != null) {// Register endpoint (as ThreadPool - historical name)oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");//Catalina:name="http-nio-8080",type=ThreadPool 注册到MBeanRegistry.getRegistry(null, null).registerComponent(this, oname, null);ObjectName socketPropertiesOname = new ObjectName(domain +":type=SocketProperties,name=\"" + getName() + "\"");//Catalina:name="http-nio-8080",type=SocketProperties 注册到MBeansocketProperties.setObjectName(socketPropertiesOname);Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);}
}

至此server.load方法就完成了

4. 启动类start方法

daemon.start(); 是tomcat最重要的一步,也是tomcat启动流程的最后一步

public void start() {//记录启动开始时间long t1 = System.nanoTime();// Start the new servertry {//执行StandardServer的start方法getServer().start();} catch (LifecycleException e) {log.fatal(sm.getString("catalina.serverStartFail"), e);try {getServer().destroy();} catch (LifecycleException e1) {log.debug(sm.getString("catalina.destroyFail"), e1);}return;}if (log.isInfoEnabled()) {log.info("信息: [55700]毫秒后服务器启动");}// Register shutdown hook//注册关闭tomcat的hook,当关闭tomcat时执行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);}}//保持main线程一直运行,直到shutdown命令if (await) {await();stop();}
}

4.1. 执行StandardServer的start

  1. getServer().start(); 执行StandardServer的start方法。
    StandardServer 类继承了 LifecycleBase 抽象类,因此也继承了 start 方法。LifecycleBase 提供了通用的生命周期管理功能,包括 initstartstopdestroy 方法。StandardServerstart 方法用于启动 Tomcat 服务器,具体实现如下:
public final synchronized void start() throws LifecycleException {try {setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里standardServer的监听器有6个,参考 标题【#### 3.4.1.1. StandServer事件监听器列表】startInternal();//StandardServer启动方法setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.startFail", toString());}
}
  1. StandardServer启动方法:startInternal();
protected void startInternal() throws LifecycleException {//触发configure_start事件fireLifecycleEvent(CONFIGURE_START_EVENT, null);setState(LifecycleState.STARTING);//设置为start状态// Initialize utility executorsynchronized (utilityExecutorLock) {//创建utilityExecutor调度线程池,设置corePoolSize=2reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));//注册utilityExecutor到MBeanregister(utilityExecutor, "type=UtilityExecutor");}//全局命名资源启动,例如数据库连接、JNDIglobalNamingResources.start();// Start our defined Servicesfor (Service service : findServices()) {//只有一个service = "StandardService[Catalina]"service.start();//StandardService启动}if (periodicEventDelay > 0) {monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(this::startPeriodicLifecycleEvent, 0, 60,TimeUnit.SECONDS);}
}

4.2. 执行NamingResourcesImpl的start

globalNamingResources.start(); 全局命名资源启动。
NamingResourcesImpl 类继承了 LifecycleBase 抽象类,因此也继承了 start 方法。

public final synchronized void start() throws LifecycleException {try {setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里NamingResourcesImpl没有监听器startInternal();//NamingResourcesImpl启动方法,主要逻辑是触发configure_start、start两个事件,但是NamingResourcesImpl没有监听器不执行逻辑setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,NamingResourcesImpl没有监听器不执行逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.startFail", toString());}
}

4.2. 执行StandardService的start

  1. service.start() StandardService启动。
    StandardService 类继承了 LifecycleBase 抽象类,因此也继承了 start 方法。
public final synchronized void start() throws LifecycleException {try {setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里 StandardService 没有监听器startInternal();//StandardService 启动方法setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,StandardService 没有监听器不执行逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.startFail", toString());}
}
  1. StandardService启动方法:startInternal();
protected void startInternal() throws LifecycleException {if (log.isInfoEnabled()) {log.info("正在启动服务[Catalina]");}setState(LifecycleState.STARTING);//start// Start our defined Container firstif (engine != null) {engine.start();//StandardEngine启动}for (Executor executor : findExecutors()) {executor.start();}mapperListener.start();// Start our defined Connectors secondfor (Connector connector : findConnectors()) {// If it has already failed, don't try and start itif (connector.getState() != LifecycleState.FAILED) {connector.start();}}
}

4.2.1. 执行StandardEngine的start

  1. engine.start() StandardEngine启动。
    StandardEngine 类继承了 LifecycleBase 抽象类,因此也继承了 start 方法。
public final synchronized void start() throws LifecycleException {try {setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑,这里 StandardEngine 没有处理before_start事件startInternal();//StandardEngine 启动方法setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,StandardEngine 没有重要逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.startFail", toString());}
}
  1. StandardEngine启动方法:startInternal();
protected void startInternal() throws LifecycleException {// Log our server identification informationif (log.isInfoEnabled()) {log.info("正在启动 Servlet 引擎:[Apache Tomcat/12.0.x-dev]");}// Standard container startupsuper.startInternal();//父类是ContainerBase
}
  1. engine父类ContainerBase启动方法:startInternal();
protected void startInternal() throws LifecycleException {//重新创建startStopExecutor线程池,用于多线程执行启动子容器逻辑reconfigureStartStopExecutor(getStartStopThreads());// Start our subordinate components, if any//获取LockOutRealm[StandardEngine] 这个是在server.xml文件配置的,用于配置tomcat管理页面登录账号与密码 http://localhost:8080/manager/htmlRealm realm = getRealmInternal();if (realm instanceof Lifecycle) {((Lifecycle) realm).start();//启动tomcat管理页面登录账号与密码数据库类,有兴趣话再仔细看下逻辑}// Start our child containers, if anyContainer[] children = findChildren();//只获取到一个:StandardHost[localhost]List<Future<Void>> results = new ArrayList<>(children.length);for (Container child : children) {//child = StandardHost[localhost]//异步执行StandardHost的启动方法start。下面详情分析这一步results.add(startStopExecutor.submit(new StartChild(child)));}MultiThrowable multiThrowable = null;//定义错误集合for (Future<Void> result : results) {try {result.get();} catch (Throwable e) {log.error(sm.getString("containerBase.threadedStartFailed"), e);if (multiThrowable == null) {multiThrowable = new MultiThrowable();}multiThrowable.add(e);//如果报错了添加到错误集合}}if (multiThrowable != null) {//如果有错误 报错、程序终止throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable());}// Start the Valves in our pipeline (including the basic), if anyif (pipeline instanceof Lifecycle) {((Lifecycle) pipeline).start();}setState(LifecycleState.STARTING);// Start our threadif (backgroundProcessorDelay > 0) {monitorFuture = Container.getService(ContainerBase.this).getServer().getUtilityExecutor().scheduleWithFixedDelay(new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);}
}
4.2.1.1. 执行StandardHost的start
  1. results.add(startStopExecutor.submit(new StartChild(child)));
    这一步是异步,先看下new StartChild(child);
private static class StartChild implements Callable<Void> {private Container child;StartChild(Container child) {//child = StandardHost[localhost]this.child = child;}@Overridepublic Void call() throws LifecycleException {child.start();//调用startreturn null;}
}

就是StandardHost又封装了一层,继承了Callable接口,值得注意的是异步执行结果是Void类型。

  1. StandardHost启动方法start()
    StandardHost 类继承了 LifecycleBase 抽象类,因此也继承了 start 方法。
public final synchronized void start() throws LifecycleException {try {setStateInternal(LifecycleState.STARTING_PREP, null, false);//触发before_start事件,相应的事件监听器触发执行逻辑startInternal();//StandardHost 启动方法setStateInternal(LifecycleState.STARTED, null, false);//触发after_start事件,StandardHost 没有重要逻辑} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.startFail", toString());}
}
  1. StandardHost启动前执行方法setStateInternal(before_start)
    StandardHost有一个事件监听器:HostConfig
public void beforeStart() {//host = StandardHost[localhost]if (host.getCreateDirs()) {//默认trueFile[] dirs = new File[] { host.getAppBaseFile(), host.getConfigBaseFile() };//getAppBaseFile() = D:\apps\tomcat-source\tomcat\output\build\webapps//getConfigBaseFile() = D:\apps\tomcat-source\tomcat\output\build\conf\Catalina\localhostfor (File dir : dirs) {//保证文件夹创建成功if (!dir.mkdirs() && !dir.isDirectory()) {log.error(sm.getString("hostConfig.createDirs", dir));}}}
}
  1. StandardHost启动方法startInternal();
    host启动方法主要逻辑是配置valve(阀门)。用于在请求处理的不同阶段对请求和响应进行拦截和处理。Valve 类似于 Servlet 过滤器。
protected void startInternal() throws LifecycleException {// Set error report valveString errorValve = getErrorReportValveClass();//org.apache.catalina.valves.ErrorReportValveif ((errorValve != null) && (!errorValve.equals(""))) {try {boolean found = false;Valve[] valves = getPipeline().getValves();//getPipeline().getValves() =// 0 = {AccessLogValve@4043} 用于记录 HTTP 请求的访问日志。 // 1 = {StandardHostValve@4044} 负责处理 HTTP 请求并将其传递给适当的子容器(通常是 StandardContext,即具体的 Web 应用程序)。for (Valve valve : valves) {if (errorValve.equals(valve.getClass().getName())) {found = true;break;}}if (!found) {Valve valve = ErrorReportValve.class.getName().equals(errorValve) ? new ErrorReportValve() :(Valve) Class.forName(errorValve).getConstructor().newInstance();getPipeline().addValve(valve);//{ErrorReportValve@4408} 生成和处理错误响应页面。}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error(sm.getString("standardHost.invalidErrorReportValveClass", errorValve), t);}}//综上,为StandardHost配置3个valvesuper.startInternal();//父类`ContainerBase`启动方法:`startInternal();`
}

StandardHost配置3个valve作用:

  • AccessLogValve:

    • 作用:记录每个请求的访问日志。
    • 详细描述AccessLogValve 用于记录 HTTP 请求的详细信息,包括客户端 IP 地址、请求时间、请求方法、请求 URI、响应状态码等。它通常用于生成服务器的访问日志,以便进行分析和监控。
  • ErrorReportValve:

    • 作用:处理和显示错误页面。
    • 详细描述ErrorReportValve 在发生错误时生成并返回一个用户友好的错误页面。它捕获 HTTP 错误状态码(如 404、500 等)和异常,并根据配置生成相应的错误响应页面。
  • StandardHostValve:

    • 作用:处理请求并将其分派到适当的 Context
    • 详细描述StandardHostValveHost 容器的默认 Valve,负责将传入的请求分派到适当的 Context(即 Web 应用)。它根据请求的 URI 确定目标 Context,并将请求传递给该 Context 进行处理。
  1. StandardHost父类ContainerBase启动方法:startInternal();
//pipeline = "StandardPipeline[StandardEngine[Catalina].StandardHost[localhost]]" 
//pipeline作用是管理和执行一系列的 Valve
if (pipeline instanceof Lifecycle) {((Lifecycle) pipeline).start();//调用StandardPipeline的启动方法
}
//其中主要逻辑是,遍历管理的valve,每个valve执行start方法
Valve current = first;
while (current != null) {if (current instanceof Lifecycle) {((Lifecycle) current).start();}current = current.getNext();
}
//StandardPipeline管理的valve:
// AccessLogValve[StandardEngine[Catalina].StandardHost[localhost]]
// ErrorReportValve[StandardEngine[Catalina].StandardHost[localhost]]
// StandardHostValve[StandardEngine[Catalina].StandardHost[localhost]]
//一共三个,没有执行start方法没有主要的逻辑。

值得注意的是StandardHost的父类的启动方法中有设置状态为start的逻辑,触发了事件监听:
setState(LifecycleState.STARTING);
这个监听类是HostConfig,下面是主要逻辑

public void start() {try {//注册Catalina:host=localhost,type=Deployer 到MBeanObjectName hostON = host.getObjectName();oname = new ObjectName(hostON.getDomain() + ":type=Deployer,host=" + host.getName());Registry.getRegistry(null, null).registerComponent(this, oname, this.getClass().getName());} catch (Exception e) {log.warn(sm.getString("hostConfig.jmx.register", oname), e);}if (host.getDeployOnStartup()) {//默认true,自动化部署webapps文件夹下的应用deployApps();//部署业务应用,这一步很重要}
}
4.2.1.1.1. deployApps()部署webapps应用

这段代码在HostConfig中。由StandardHost启动方法监听触发。

protected void deployApps() {// Migrate legacy Java EE apps from legacyAppBase//迁移旧版 Java EE 应用程序:调用 migrateLegacyApps 方法,将旧版应用程序从 legacyAppBase 迁移到新的应用程序基础目录。migrateLegacyApps();//没有主要逻辑File appBase = host.getAppBaseFile();//D:\apps\tomcat-source\tomcat\output\build\webappsFile configBase = host.getConfigBaseFile();//D:\apps\tomcat-source\tomcat\output\build\conf\Catalina\localhostString[] filteredAppPaths = filterAppPaths(appBase.list());//这个就是我们的tomcat家目录下面webapps中的默认文件应用//filteredAppPaths = {String[5]@5272} ["docs", "examples", "host-manager", "manager", "ROOT"]// Deploy XML descriptors from configBase//部署 XML 描述符:调用 deployDescriptors 方法,从配置基础目录中部署 XML 描述符。deployDescriptors(configBase, configBase.list());//没有主要逻辑// Deploy WARs//部署 WAR 文件:调用 deployWARs 方法,从应用程序基础目录中部署 WAR 文件。//默认文件中没有war包,我们直接看下一步的文件包部署。其实war包和文件包部署原理是相似的,war解压后就是一个完整的文件包。deployWARs(appBase, filteredAppPaths);// Deploy expanded folders//部署展开的文件夹:调用 deployDirectories 方法,从应用程序基础目录中部署展开的文件夹。deployDirectories(appBase, filteredAppPaths);
}
4.2.1.1.1.1. 部署展开的文件夹 deployDirectories(appBase, filteredAppPaths);

默认的文件有["docs", "examples", "host-manager", "manager", "ROOT"]。我们只看examples文件夹部署过程吧,其他的原理一样。

  1. 部署examples文件夹准备
protected void deployDirectories(File appBase, String[] files) {//获取线程池ExecutorService es = host.getStartStopExecutor();List<Future<?>> results = new ArrayList<>();for (String file : files) {if (file.equalsIgnoreCase("META-INF")) {continue;}if (file.equalsIgnoreCase("WEB-INF")) {continue;}File dir = new File(appBase, file);//dir = D:\apps\tomcat-source\tomcat\output\build\webapps\examplesif (dir.isDirectory()) {ContextName cn = new ContextName(file, false);//cn = /examplesif (tryAddServiced(cn.getName())) {//truetry {if (deploymentExists(cn.getName())) {//falseremoveServiced(cn.getName());continue;}// DeployDirectory will call removeServiced//异步执行部署docs文件夹results.add(es.submit(new DeployDirectory(this, cn, dir)));} catch (Throwable t) {ExceptionUtils.handleThrowable(t);removeServiced(cn.getName());throw t;}}}}for (Future<?> result : results) {try {result.get();//获取部署结果} catch (Exception e) {log.error(sm.getString("hostConfig.deployDir.threaded.error"), e);}}
}
  1. 异步执行部署docs文件夹封装Runnable
    results.add(es.submit(new DeployDirectory(this, cn, dir)));
    先看下DeployDirectory类。继承了Runnable
private static class DeployDirectory implements Runnable {private HostConfig config;private ContextName cn;private File dir;DeployDirectory(HostConfig config, ContextName cn, File dir) {this.config = config;this.cn = cn;this.dir = dir;}@Overridepublic void run() {try {config.deployDirectory(cn, dir);//部署方法} finally {config.removeServiced(cn.getName());}}
}
  1. 执行核心部署逻辑deployDirectory
    config.deployDirectory(cn, dir);
this = {HostConfig@3762} //代码依然是在HostConfig类中
cn = {ContextName@5420} "/examples" 
dir = {File@5406} "D:\apps\tomcat-source\tomcat\output\build\webapps\examples" //部署的文件

我们先看下D:\apps\tomcat-source\tomcat\output\build\webapps\examples\META-INF\context.xml文件内容

<Context ignoreAnnotations="true"><CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"sameSiteCookies="strict" /><Valve className="org.apache.catalina.valves.RemoteAddrValve"allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
</Context>

下面看下部署examples文件源码

protected void deployDirectory(ContextName cn, File dir) {if (log.isInfoEnabled()) {startTime = System.currentTimeMillis();log.info("把web 应用程序部署到目录 [D:\apps\tomcat-source\tomcat\output\build\webapps\examples]");}// D:\apps\tomcat-source\tomcat\output\build\webapps\examples\META-INF\context.xmlcontext = (Context) digester.parse(xml);//digester是xml解析类,生成对应的java类//org.apache.catalina.startup.ContextConfigClass<?> clazz = Class.forName(host.getConfigClass());LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();context.addLifecycleListener(listener);//这里给context中添加了一个事件监听器ContextConfig,后续用到了!//添加应用到StandardHosthost.addChild(context);//省略部分代码...
}
4.2.1.1.1.1. digester解析context.xml文件 context = (Context) digester.parse(xml);

解析xml文件,生成了StandardContext [/examples]类。
值得注意的是StandardContext构造方法中新增了StandardContextValve阀门类

4.2.1.1.1.2. StandardContext[/examples]添加应用到StandardHost

StandardHost类中,addChild方法核心代码是

protected final HashMap<String,Container> children = new HashMap<>();public void addChild(Container child) {//child = {StandardContext@5784} "StandardEngine[Catalina].StandardHost[localhost].StandardContext[/examples]"children.put("/examples", child);//调用StandardContext的start启动方法child.start();
}
4.2.1.1.1.3. StandardContext[/examples]的start启动方法
  1. child.start();。 同样StandardContext继承了LifecycleBase,因此继承了start方法。
@Override
public final synchronized void start() throws LifecycleException {// state = NEW 默认是new,这里是trueif (state.equals(LifecycleState.NEW)) {init();//初始化方法}try {setStateInternal(LifecycleState.STARTING_PREP, null, false);//启动前startInternal();//启动方法setStateInternal(LifecycleState.STARTED, null, false);//启动后} catch (Throwable t) {// This is an 'uncontrolled' failure so put the component into the// FAILED state and throw an exception.handleSubClassException(t, "lifecycleBase.startFail", toString());}
}
  1. StandardContext初始化init()
    这一步没有重要的逻辑。
StandardContext有3个监听器:
- **ContextConfig**:- **作用**`ContextConfig` 监听器负责处理 `Context` 的配置和初始化工作。它会解析 `web.xml` 文件,设置 `Context` 的各种参数,并执行必要的初始化操作。- **StandardHost$MemoryLeakTrackingListener**:- **作用**:跟踪和检测内存泄漏。- **ThreadLocalLeakPreventionListener**:- **作用**:防止线程本地变量泄漏。
  1. StandardContext启动方法startInternal()
    这个启动方法是tomcat中最复杂的,代码很长,先看下作用的功能:
1. 日志记录和通知:记录启动日志并发送 JMX 通知,表示上下文正在启动。
2. 初始化资源:确保命名资源和工作目录已正确配置。
3. 加载器和资源:配置默认资源和类加载器。
4. 字符集映射:初始化字符集映射。
5. 命名上下文:配置命名上下文监听器。
6. 启动子组件:启动加载器、Realm、子容器和管道中的阀门。
7. 管理器配置:配置会话管理器,特别是在集群环境中。
8. 设置上下文属性:将资源、实例管理器、Jar扫描器等设置为上下文属性。
9. 调用初始化器:调用 `ServletContainerInitializer` 进行初始化。
10. 启动监听器和过滤器:配置并启动应用程序事件监听器和过滤器。
11. 加载启动时加载的Servlet:加载和初始化所有“启动时加载”的Servlet。
12. 启动后台处理线程:启动容器后台处理线程。
13. 设置状态:根据启动结果设置上下文的可用状态,并发送相应的JMX通知。

我们只分析一下最主要的步骤:解析StandardWrapper,在此之前,我们先看下examples/WEB-INF/web.xml文件内容

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"metadata-complete="false"> <!-- 注解扫描解析 --><filter><filter-name>HTTP header security filter</filter-name><filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class><async-supported>true</async-supported><init-param><param-name>hstsEnabled</param-name><param-value>false</param-value></init-param>
</filter><servlet><servlet-name>RequestInfoExample</servlet-name><servlet-class>RequestInfoExample</servlet-class>
</servlet>
<servlet-mapping><servlet-name>RequestInfoExample</servlet-name><url-pattern>/servlets/servlet/RequestInfoExample/*</url-pattern>
</servlet-mapping>
<!-- 等等,定义了很多servlet -->
</web-app>

下面开始分析代码

protected void startInternal() throws LifecycleException {//触发configure_start事件//触发了ContextConfig监听器的方法:webConfig();fireLifecycleEvent(CONFIGURE_START_EVENT, null);
}//扫描web.xml文件,扫描/WEB-INF/classes文件夹下的类
protected void webConfig() {private final Map<String,String> servletMappings = new HashMap<>();InputSource contextWebXml = getContextWebXmlSource();//file:/D:/apps/tomcat-source/tomcat/output/build/webapps/examples/WEB-INF/web.xml//解析web.xml,把配置的servlet解析到【servletMappings】if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {ok = false;}//解析文件结果//  webXml.servletMappings = {HashMap@3471}  size = 16//  "/async/stockticker" -> "stock"//  "/servlets/servlet/CookieExample" -> "CookieExample"//  "/CompressionTest" -> "CompressionFilterTestServlet"//  "/servlets/servlet/RequestParamExample" -> "RequestParamExample"//  "/servlets/servlet/RequestInfoExample/*" -> "RequestInfoExample"//  "/servlets/servlet/SessionExample" -> "SessionExample"//  "/async/async0" -> "async0"//  "/servlets/trailers/response" -> "responsetrailer"//  "/async/async1" -> "async1"//  "/servlets/servlet/RequestHeaderExample" -> "RequestHeaderExample"//  "/servlets/nonblocking/numberwriter" -> "numberwriter"//  "/servlets/servlet/HelloWorldExample" -> "HelloWorldExample"//  "/servletToJsp" -> "ServletToJsp"//  "/servlets/nonblocking/bytecounter" -> "bytecounter"//  "/async/async2" -> "async2"//  "/async/async3" -> "async3"if (!webXml.isMetadataComplete()) {//这个是web.xml配置的metadata-complete="false"// Steps 4 & 5.//解析/WEB-INF/classes/*类processClasses(webXml, orderedFragments);}//配置StandardContext,添加wrapperconfigureContext(webXml);
}

另外,扫描注解配置,需要设置web.xml配置为<web-app metadata-complete="false">

  1. 解析/WEB-INF/classes/*类processClasses(webXml, orderedFragments);
    当前应用D:/apps/tomcat-source/tomcat/output/build/webapps/examples开始解析类文件了
protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {if (ok) {WebResource[] webResources = context.getResources().listResources("/WEB-INF/classes");/** webResources = 路径下的文件夹如下:/WEB-INF/classes/├── async├── checkbox├── colors├── compressionFilters├── CookieExample.java├── dates├── error├── examples├── filters├── HelloWorldExample.java├── HelloWorldExample2ForServletAnnotation.java├── jsp2├── listeners├── nonblocking├── num├── RequestHeaderExample.java├── RequestInfoExample.java├── RequestParamExample.java├── ServletToJsp.java├── SessionExample.java├── sessions├── trailers├── util├── validators└── websocket   */for (WebResource webResource : webResources) {// Skip the META-INF directory from any JARs that have been// expanded in to WEB-INF/classes (sometimes IDEs do this).if ("META-INF".equals(webResource.getName())) {continue;}//解析类文件 webResource = D:\apps\tomcat-source\tomcat\output\build\webapps\examples\WEB-INF\classes\HelloWorldExample2ForServletAnnotation.class//这个类HelloWorldExample2ForServletAnnotation是我自己加的,实现了HttpServlet接口的doGet方法,添加了注解@WebServlet({"/HelloWorldExample2ForServletAnnotation"})processAnnotationsWebResource(webResource, webXml, webXml.isMetadataComplete(), javaClassCache);}}
}//最终调用了这个方法,获取类注解:@WebServlet、@WebFilter、@WebListener
protected void processClass(WebXml fragment, JavaClass clazz) {AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();if (annotationsEntries != null) {String className = clazz.getClassName();for (AnnotationEntry ae : annotationsEntries) {String type = ae.getAnnotationType();if ("Ljakarta/servlet/annotation/WebServlet;".equals(type)) {//className = "HelloWorldExample2ForServletAnnotation"processAnnotationWebServlet(className, ae, fragment);} else if ("Ljakarta/servlet/annotation/WebFilter;".equals(type)) {processAnnotationWebFilter(className, ae, fragment);} else if ("Ljakarta/servlet/annotation/WebListener;".equals(type)) {fragment.addListener(className);} else {// Unknown annotation - ignore}}}
}//添加到webXml.servletMappings
public void addServlet(ServletDef servletDef) {//servletName = "HelloWorldExample2ForServletAnnotation"//servletClass = "HelloWorldExample2ForServletAnnotation"servlets.put(servletDef.getServletName(), servletDef);if (overridable) {servletDef.setOverridable(overridable);}
}
  1. 配置StandardContext,添加wrapper
    把上一步处理好的servletMappings转换成wrapper,并添加到context中
for (ServletDef servlet : webxml.getServlets().values()) {Wrapper wrapper = context.createWrapper();//StandardWrapperwrapper.setName(servlet.getServletName());wrapper.setServletClass(servlet.getServletClass());//context = {StandardContext@3456} "StandardEngine[Catalina].StandardHost[localhost].StandardContext[/examples]"context.addChild(wrapper);
}

至此,我们看到把servlet封装成wrapper,wrapper添加到context,context是host的子容器,host属于engine,engine在service中,service是顶级容器server的子容器。

4.2.2. 执行Connector的start

这段代码在StandardService中的start方法中,上述【## 4.2. 执行StandardService的start】中出现过,现在开始分析。

for (Connector connector : findConnectors()) {// If it has already failed, don't try and start it//只有一个元素 Connector["http-nio-8080"]if (connector.getState() != LifecycleState.FAILED) {connector.start();}
}

connector的核心就是一个Http11NioProtocol网络协议类,在启动方法中主要逻辑就是调用Http11NioProtocol的启动方法。

/*** Start the NIO endpoint, creating acceptor, poller threads.*/
@Override
public void startInternal() throws Exception {//设置同时处理请求的最大连接数,默认 8*1024=8192LimitLatch limitLatch = initializeConnectionLatch();// Start poller thread//异步创建nio请求消费者poller = new Poller();Thread pollerThread = new Thread(poller, getName() + "-Poller");pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();// Start acceptor thread//异步创建nio请求生产者acceptor = new Acceptor<>(this);String threadName = getName() + "-Acceptor";acceptor.setThreadName(threadName);Thread t = new Thread(acceptor, threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();
}
4.2.2.1. 启动nioEndpoint线程Poller
  1. 首先创建了Poller对象,构造方法打开了一个选择器
public class Poller implements Runnable {//继承了Runnablepublic Poller() throws IOException {//Selector.open() 是 Java NIO 中用于打开一个新的选择器的静态方法。选择器是一个多路复用器,可以检测多个通道的 I/O 事件(如读、写、连接等),从而实现非阻塞 I/O 操作。this.selector = Selector.open();}//省略部分代码
}

这里没有继续看Selector类的源码,这是java包下的,而且里面好多逻辑调用了native方法,看到不源码,我用AI总结了一下:

1. **打开选择器**:通过 `Selector.open()` 方法创建一个新的选择器实例。
2. **注册通道**:将一个或多个通道注册到选择器上,并指定感兴趣的 I/O 事件。
3. **选择就绪通道**:使用选择器的 `select()``select(long)``selectNow()` 方法可以检测哪些通道已经准备好进行 I/O 操作。
4. **处理就绪通道**:通过 `selectedKeys()` 方法获取已准备好进行 I/O 操作的通道的键集合,并对这些键进行处理。
5. **唤醒选择器**:通过 `wakeup()` 方法可以唤醒阻塞在选择操作上的线程。
6. **关闭选择器**:通过 `close()` 方法关闭选择器,释放相关资源。这里说的通道在tomcat里就是`SocketChannel`,通道可以读写数据,通道与缓冲区(Buffer)结合使用,数据总是从通道读到缓冲区中,或者从缓冲区写到通道中。(就类似于读写File,使用更大的数组读写:提效)
  1. 异步执行Poller线程
    看一下Poller的run方法实现:
/*** The background thread that adds sockets to the Poller, checks the* poller for triggered events and hands the associated socket off to an* appropriate processor as events occur.*/
@Override
public void run() {// Loop until destroy() is calledwhile (true) {boolean hasEvents = false;try {if (!close) {//读事件hasEvents = events();if (wakeupCounter.getAndSet(-1) > 0) {//wakeupCounter:默认是0,如果有新事件添加到evens队列,就+1,这样就不用阻塞获取channel// If we are here, means we have other stuff to do// Do a non blocking selectkeyCount = selector.selectNow();//立即获取已准备好的通道数量} else {keyCount = selector.select(selectorTimeout);//阻塞1秒,获取已准备好的通道数量}wakeupCounter.set(0);}} catch (Throwable x) {ExceptionUtils.handleThrowable(x);log.error(sm.getString("endpoint.nio.selectorLoopError"), x);continue;}//获取已准备好的通道集合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();iterator.remove();//获取socket通道NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();// Attachment may be null if another thread has called// cancelledKey()if (socketWrapper != null) {//处理socket请求processKey(sk, socketWrapper);}}// Process timeouts//处理超时timeout(keyCount,hasEvents);}}
4.2.2.1.1. 读事件events()方法

这个方法的作用就是把selector中的通道设置为读事件或写事件

private final SynchronizedQueue<PollerEvent> events =new SynchronizedQueue<>();//创建events队列public boolean events() {boolean result = false;PollerEvent pe = null;//遍历events队列事件,这个事件是由Acceptor添加的,下面会讲到for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {result = true;//获取socket通道NioSocketWrapper socketWrapper = pe.getSocketWrapper();SocketChannel sc = socketWrapper.getSocket().getIOChannel();int interestOps = pe.getInterestOps();//acceptor线程创建的pollerEvent都是注册事件,下面的代码有分析if (sc == null) {log.warn(sm.getString("endpoint.nio.nullSocketChannel"));socketWrapper.close();} else if (interestOps == OP_REGISTER) {//注册事件改成读事件try {sc.register(getSelector(), SelectionKey.OP_READ, socketWrapper);} catch (Exception x) {log.error(sm.getString("endpoint.nio.registerFail"), x);}} else {final SelectionKey key = sc.keyFor(getSelector());if (key == null) {// The key was cancelled (e.g. due to socket closure)// and removed from the selector while it was being// processed. Count down the connections at this point// since it won't have been counted down when the socket// closed.socketWrapper.close();} else {final NioSocketWrapper attachment = (NioSocketWrapper) key.attachment();if (attachment != null) {// We are registering the key to start with, reset the fairness counter.try {int ops = key.interestOps() | interestOps;//普通的请求接口,设置为读事件或写事件,一般来说都是读attachment.interestOps(ops);key.interestOps(ops);} catch (CancelledKeyException ckx) {socketWrapper.close();}} else {socketWrapper.close();}}}if (running && eventCache != null) {pe.reset();eventCache.push(pe);}}return result;
}
4.2.2.1.2. 处理socket请求

processKey(sk, socketWrapper); 把客户端的socketChannel请求进行处理。调用http的doGet、doPost等方法。

//1. processKey是一个异步方法,实现了Runnable
sc = createSocketProcessor(socketWrapper, event);
executor.execute(sc);//异步//2. 当前是在Connector中,获取对应的service
if (status == SocketEvent.OPEN_READ) {state = service(socketWrapper);
}//上面说过,pipeline是管理valve的,当前service的子容器engine的pipeline只有一个阀门:【StandardEngineValve】
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);//3. StandardEngineValve的invoke逻辑
//standardHost的pipeline有三个,上面也说过:
//  0 = {AccessLogValve@4541} //输出日志,然后执行【ErrorReportValve】的invoke  例如getNext().invoke(request, response);
//  1 = {ErrorReportValve@4556} //直接执行【StandardHostValve】的invoke
//  2 = {StandardHostValve@4566} 重点看这个
host.getPipeline().getFirst().invoke(request, response);//4. StandardHostValve的invoke逻辑
//context的pipeline有三个:
// 0 = {RemoteAddrValve@4681} 校验ip,然后执行【FormAuthenticator】的invoke
// 1 = {FormAuthenticator@4684} 校验tomcat登录信息,然后执行【StandardContextValve】的invoke
// 2 = {StandardContextValve@4708} 重点看这个,这个阀门是实例化Context的时候创建的,上面提到过
context.getPipeline().getFirst().invoke(request, response);//5. StandardContextValve的invoke逻辑
//我当前的请求路径是http://localhost:8080/examples/HelloWorldExample2ForServletAnnotation
//对应的wrapper是"StandardEngine[Catalina].StandardHost[localhost].StandardContext[/examples].StandardWrapper[HelloWorldExample2ForServletAnnotation]"
//对应的valve只有一个:StandardWrapperValve
request.getWrapper().getPipeline().getFirst().invoke(request, response);//6. StandardWrapperValve的invoke逻辑
//filterChain默认有两个:
// 0 = org.apache.catalina.filters.HttpHeaderSecurityFilter
// 1 = org.apache.tomcat.websocket.server.WsFilter
filterChain.doFilter(request.getRequest(), response.getResponse());//7. filterChain的doFilter
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];try {Filter filter = filterConfig.getFilter();//遍历每个过滤器,执行filter.doFilter(request, response, this);} catch (IOException | ServletException | RuntimeException e) {throw e;}return;}//最后执行业务类servlet.service(request, response);
}//8. 执行业务类的doGet
if (method.equals(METHOD_GET)) {doGet(req, resp);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {System.out.print("请求参数:"+request);response.write("你好世界");
}
4.2.2.2. 启动nioEndpoint线程Acceptor
  1. 首先是创建了Acceptor对象,继承了Runnable,然后异步线程执行run方法,先看下run方法:
public void run() {try {// Loop until we receive a shutdown command//循环执行,直到关闭tomcat服务while (!stopCalled) {state = AcceptorState.RUNNING;try {//if we have reached max connections, wait//检查请求连接总数,到达最大连接数8192,上面代码分析过【connectionLimitLatch】endpoint.countUpOrAwaitConnection();U socket = null;try {// Accept the next incoming connection from the server// socket// 阻塞监听8080端口新连接socket = endpoint.serverSocketAccept();} catch (Exception ioe) {// We didn't get a socketendpoint.countDownConnection();}// Configure the socketif (!stopCalled && !endpoint.isPaused()) {// setSocketOptions() will hand the socket off to// an appropriate processor if successful//新连接socket注册if (!endpoint.setSocketOptions(socket)) {endpoint.closeSocket(socket);}} else {endpoint.destroySocket(socket);}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);String msg = sm.getString("endpoint.accept.fail");log.error(msg, t);}}} finally {stopLatch.countDown();}state = AcceptorState.ENDED;
}
4.2.2.2.1. 检查请求连接总数是否达到最大连接数

endpoint.countUpOrAwaitConnection(); 如果达到默认的连接数8192就抛异常

long newCount = count.incrementAndGet(); //每次循环都会加1,值得注意的是,当处理完成当前socket或者程序报错,都会把count减1
if (newCount > limit) //limit=8192,newCount=当前连接数throw new InterruptedException();
4.2.2.2.2. 阻塞监听8080端口新连接

socket = endpoint.serverSocketAccept(); 这个是java内部方法

int n = Net.accept(fd, newfd, issa);//是个native方法,阻塞接收新连接,
//最终返回新连接的SocketChannel
return new SocketChannelImpl(provider(), family, newfd, sa);
4.2.2.2.3. 新连接socket注册

endpoint.setSocketOptions(socket),处理新连接

//socket = {SocketChannelImpl@4755} "java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:50015]"
@Override
protected boolean setSocketOptions(SocketChannel socket) {NioSocketWrapper socketWrapper = null;try {// Allocate channel and wrapperNioChannel channel = null;SocketBufferHandler bufhandler = new SocketBufferHandler(socketProperties.getAppReadBufSize(),socketProperties.getAppWriteBufSize(),socketProperties.getDirectBuffer());//新创建一个channelchannel = createChannel(bufhandler);//组装WrapperNioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);//设置新连接socketchannel.reset(socket, newWrapper);connections.put(socket, newWrapper);socketWrapper = newWrapper;// Set socket properties// Disable blocking, polling will be usedsocket.configureBlocking(false);if (getUnixDomainSocketPath() == null) {socketProperties.setProperties(socket.socket());}socketWrapper.setReadTimeout(getConnectionTimeout());socketWrapper.setWriteTimeout(getConnectionTimeout());socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());//最重要的一步,注册socketWrapper到events事件队列poller.register(socketWrapper);return true;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);try {log.error(sm.getString("endpoint.socketOptionsError"), t);} catch (Throwable tt) {ExceptionUtils.handleThrowable(tt);}if (socketWrapper == null) {destroySocket(socket);}}// Tell to close the socket if neededreturn false;
}
4.2.2.2.3.1. 新连接socketWrapper注册到events事件队列

poller.register(socketWrapper);注册到事件队列,给Poller线程消费

public void register(final NioSocketWrapper socketWrapper) {//设置读事件给socketWrappersocketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.//新增Poller事件,并设置注册事件类型PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);//添加poller事件到events事件队列addEvent(pollerEvent);
}//addEvent(pollerEvent);方法内容
events.offer(event); //events就是上面poller线程的事件队列SynchronizedQueue
if (wakeupCounter.incrementAndGet() == 0) {selector.wakeup();//唤醒poller线程
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/503927.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT 端口扫描附加功能实现 端口扫描5

上篇QT 下拉菜单设置参数 起始端口/结束端口/线程数量 端口扫描4-CSDN博客 在扫描结束后设置Scan按钮为可用&#xff0c;并提示扫描完成 在 MainWindow 类中添加一个成员变量来跟踪正在进行的扫描任务数量&#xff1a; 在 MainWindow 的构造函数中初始化 activeScanTasks&…

使用WPF在C#中制作下载按钮

本示例使用 WPF 制作一个下载按钮。以下 XAML 代码显示了程序如何构建该按钮。 <Window x:Class"howto_download_button.Window1"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/…

Unity Burst详解

【简介】 Burst是Unity的编译优化技术&#xff0c;优化了从C#代码编译成Native代码的过程&#xff0c;经过编译优化后代码有更高的运行效率。 在Unity中使用Burst很简单&#xff0c;在方法或类前加上[BurstCompile]特性即可。在构建时编译代码的步骤&#xff0c;Burst编译器会…

Redis 数据库源码分析

Redis 数据库源码分析 我们都知道Redis是一个 <key,value> 的键值数据库&#xff0c;其实也就是一个 Map。如果让我来实现这样一个 Map&#xff0c;我肯定是用数组&#xff0c;当一个 key 来的时候&#xff0c;首先进行 hash 运算&#xff0c;接着对数据的 length 取余&…

基于YOLO11的无人机视角下羊群检测系统

基于YOLO11的无人机视角下羊群检测系统 (价格90) 包含 [sheep] 【羊】 1个类 通过PYQT构建UI界面&#xff0c;包含图片检测&#xff0c;视频检测&#xff0c;摄像头实时检测。 &#xff08;该系统可以根据数据训练出的yolo11的权重文件&#xff0c;运用在其他检测系统上…

WebRTC 在视频联网平台中的应用:开启实时通信新篇章

在当今这个以数字化为显著特征的时代浪潮之下&#xff0c;实时通信已然稳稳扎根于人们生活与工作的方方面面&#xff0c;成为了其中不可或缺的关键一环。回首日常生活&#xff0c;远程办公场景中的视频会议让分散各地的团队成员能够跨越地理距离的鸿沟&#xff0c;齐聚一堂共商…

【Go学习】-02-1-标准库:fmt、os、time

【Go学习】-02-1-标准库&#xff1a;fmt、os、time 1 fmt标准库1.1 输出1.1.1 fmt.Print1.1.2 格式化占位符1.1.2.1 通用占位符1.1.2.2 布尔型1.1.2.3 整型1.1.2.4 浮点数与复数1.1.2.5 字符串和[]byte1.1.2.6 指针1.1.2.7 宽度标识符1.1.2.8 其他flag 1.1.3 Fprint1.1.4 Sprin…

快速入门Spring Cloud Alibaba,轻松玩转微服务

​ 1 快速入门Spring Cloud Alibaba&#xff0c;轻松玩转微服务 1.1 架构 架构图&#xff1a; 1.2 项目结构 1.2.1 系统框架版本 版本适配查看&#xff1a;https://sca.aliyun.com/docs/2023/overview/version-explain/ Spring Boot Version &#xff1a;3.2.4 Spring Clo…

腾讯云AI代码助手-每日清单助手

作品简介 每日清单助手是一款可以记录生活的小程序&#xff0c;在人们需要记录时使用&#xff0c;所以根据这个需求来创建的这款应用工具&#xff0c;使用的是腾讯云AI代码助手来生成的所有代码&#xff0c;使用方便&#xff0c;快捷&#xff0c;高效。 技术架构 python语言…

Pytorch学习12_最大池化的使用

输入图像 import torch from torch import nninputtorch.tensor([[1,2,0,3,1],[0,1,2,3,1],[1,2,1,0,0],[5,2,3,1,1],[2,1,0,1,1]]) inputtorch.reshape(input,(-1,1,5,5))#二维张量转换为一个四维张量。(batch_size, channels, height, width)print(input.shape)ceil_modeTrue…

009:传统计算机视觉之边缘检测

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 本节来看一个利用传统计算机视觉方法来实现图片边缘检测的方法。 什么是边缘检测&#xff1f; 边缘检测是通过一些算法来识别图像中物体之间或者物体与背景之间的边界&…

HarmonyOS Next系列之华为账号一键登录功能实现(十四)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

大数据架构设计:数据分层治理的全景指南

大数据架构设计&#xff1a;数据分层治理的全景指南 在大数据架构中&#xff0c;数据分层治理是一种被广泛采用的设计模式&#xff0c;其核心目的是为数据赋予结构化管理的能力&#xff0c;降低复杂度&#xff0c;并为数据的多样化使用场景提供保障。在这篇文章中&#xff0c;…

unity学习14:unity里的C#脚本的几个基本生命周期方法, 脚本次序order等

目录 1 初始的C# 脚本 1.1 初始的C# 脚本 1.2 创建时2个默认的方法 2 常用的几个生命周期方法 2.1 脚本的生命周期 2.1.1 其中FixedUpdate 方法 的时间间隔&#xff0c;是在这设置的 2.2 c#的基本语法别搞混 2.2.1 基本的语法 2.2.2 内置的方法名&#xff0c;要求更严…

Ubuntu中使用miniconda安装R和R包devtools

安装devtools环境包 sudo apt-get install gfortran -y sudo apt-get install build-essential -y sudo apt-get install libxt-dev -y sudo apt-get install libcurl4-openssl-dev -y sudo apt-get install libxml2.6-dev -y sudo apt-get install libssl-dev -y sudo apt-g…

如何在 Windows 10/11 上录制带有音频的屏幕 [3 种简单方法]

无论您是在上在线课程还是参加在线会议&#xff0c;您都可能需要在 Windows 10/11 上录制带有音频的屏幕。互联网上提供了多种可选方法。在这里&#xff0c;本博客收集了 3 种最简单的方法来指导您如何在 Windows 10/11 上使用音频进行屏幕录制。请继续阅读以探索&#xff01; …

Python 中几个库的安装与测试

一、jupyter 安装步骤 确保系统已经安装了Python&#xff08;建议 Python 3.6 及以上版本&#xff09;。点击WinR输入cdm进入命令提示符窗口&#xff0c;然后输入pip install jupyter&#xff0c;按下回车键。等待安装过程完成。安装过程中&#xff0c;你会看到命令行输出安装…

【阅读笔记】基于FPGA的红外图像二阶牛顿插值算法的实现

图像缩放技术在图像显示、传输、分析等多个领域中扮演着重要角色。随着数字图像处理技术的发展&#xff0c;对图像缩放质量的要求也越来越高。二阶牛顿插值因其在处理图像时能够较好地保持边缘特征和减少细节模糊&#xff0c;成为了图像缩放中的一个研究热点。 一、 二阶牛顿插…

5.1 数据库:INSERT 插入语句

工作中增删改查这四类sql语句里边用的最多的就是查询语句。因为绝大多数的软件系统都是读多写少的&#xff0c;而且查询的条件也是各种各样。本节课程我们来学习下一个DML语句&#xff0c;那就是向数据表里面写入记录的insert语句。Insert语句是可以向数据表里边写入&#xff0…

【 算法设计与分析-回顾算法知识点】福建师范大学数学与计算机科学学院 2006 — 2007学年第二学期考试 A 卷

一&#xff0e;填空题&#xff08;每空2分&#xff0c;共30分&#xff09; 1&#xff0e;算法的时间复杂性指算法中 元运算 的执行次数。 2&#xff0e;在忽略常数因子的情况下&#xff0c;O、和三个符号中&#xff0c; O 提供了算法运行时间的一个上界。 3&#xff0e;设Dn…