理解思路
我们这里会讲解server.xml,
- 继承结构
我们要从高一点的维度去理解Server的结构设计,我们看下server对应的server.xml
<?xml version="1.0" encoding="UTF-8"?>
//这里端口8005负责监听tomcat的关闭请求
<Server port="8005" shutdown="SHUTDOWN">
//监听器相关
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!-- APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
//service相关
<Service name="Catalina">
//连接器相关
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<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.authenticator.SingleSignOn" />
</Host>
</Engine>
</Service>
</Server>
server接口设计
public interface Server extends Lifecycle {
// ------------------------------------------------------------- Properties
/**
* @return the global naming resources.
*/
public NamingResourcesImpl getGlobalNamingResources();
/**
* Set the global naming resources.
*
* @param globalNamingResources The new global naming resources
*/
public void setGlobalNamingResources
(NamingResourcesImpl globalNamingResources);
/**
* @return the global naming resources context.
*/
public javax.naming.Context getGlobalNamingContext();
/**
* @return the port number we listen to for shutdown commands.
*/
public int getPort();
/**
* Set the port number we listen to for shutdown commands.
*
* @param port The new port number
*/
public void setPort(int port);
/**
* @return the address on which we listen to for shutdown commands.
*/
public String getAddress();
/**
* Set the address on which we listen to for shutdown commands.
*
* @param address The new address
*/
public void setAddress(String address);
/**
* @return the shutdown command string we are waiting for.
*/
public String getShutdown();
/**
* Set the shutdown command we are waiting for.
*
* @param shutdown The new shutdown command
*/
public void setShutdown(String shutdown);
/**
* @return the parent class loader for this component. If not set, return
* {@link #getCatalina()} {@link Catalina#getParentClassLoader()}. If
* catalina has not been set, return the system class loader.
*/
public ClassLoader getParentClassLoader();
/**
* Set the parent class loader for this server.
*
* @param parent The new parent class loader
*/
public void setParentClassLoader(ClassLoader parent);
/**
* @return the outer Catalina startup/shutdown component if present.
*/
public Catalina getCatalina();
/**
* Set the outer Catalina startup/shutdown component if present.
*
* @param catalina the outer Catalina component
*/
public void setCatalina(Catalina catalina);
/**
* @return the configured base (instance) directory. Note that home and base
* may be the same (and are by default). If this is not set the value
* returned by {@link #getCatalinaHome()} will be used.
*/
public File getCatalinaBase();
/**
* Set the configured base (instance) directory. Note that home and base
* may be the same (and are by default).
*
* @param catalinaBase the configured base directory
*/
public void setCatalinaBase(File catalinaBase);
/**
* @return the configured home (binary) directory. Note that home and base
* may be the same (and are by default).
*/
public File getCatalinaHome();
/**
* Set the configured home (binary) directory. Note that home and base
* may be the same (and are by default).
*
* @param catalinaHome the configured home directory
*/
public void setCatalinaHome(File catalinaHome);
// --------------------------------------------------------- Public Methods
/**
* Add a new Service to the set of defined Services.
*
* @param service The Service to be added
*/
public void addService(Service service);
/**
* Wait until a proper shutdown command is received, then return.
*/
public void await();
/**
* Find the specified Service
*
* @param name Name of the Service to be returned
* @return the specified Service, or <code>null</code> if none exists.
*/
public Service findService(String name);
/**
* @return the set of Services defined within this Server.
*/
public Service[] findServices();
/**
* Remove the specified Service from the set associated from this
* Server.
*
* @param service The Service to be removed
*/
public void removeService(Service service);
/**
* @return the token necessary for operations on the associated JNDI naming
* context.
*/
public Object getNamingToken();
}
可以看到这里都是一些正常的增删服务的接口
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// 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
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException | IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
//初始化服务
for (Service service : services) {
service.init();
}
}
-
启动start
@Override
protected void startInternal() throws LifecycleException {fireLifecycleEvent(CONFIGURE_START_EVENT, null); //设置状态 setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services synchronized (servicesLock) { for (Service service : services) { //服务启动 service.start(); } }
}
@Override
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled()) {
log.info(sm.getString("standardService.start.name", this.name));
}
//设置starting启动状态
setState(LifecycleState.STARTING);
// Start our defined Container first
//启动执行引擎
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
//启动执行器
executor.start();
}
}
//启动listener
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {//启动连接器
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
- await()
@Override
public void await() {
// 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;
}
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;
}
// Set up a server socket to wait on
try {
//建立serverSocket服务
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连接并发送命令
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(sm.getString("standardServer.accept.security"), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error(sm.getString("standardServer.accept.error"), 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(sm.getString("standardServer.accept.readError"), 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
//查看command命令是不是shutdown
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else {
log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
}
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
//最后要关闭
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
上面的方法中,启动了一个socket,同时再while循环里阻塞等待客户连接,这里阻塞的是主线程,主线程不执行完,那么默认启动的守护线程,就会一直执行,tomcat就不会退出;客户端连接了socket,并发送了shutdown后会跳出while(1) 循环,如下catalina会跳出await()方法,接着执行stop方法,整个tomcat执行stop方法,进而关闭退出tomcat。
public class Catalina{
public void start() {
//省略方法
//这里为true
if (await) {
await();
stop();
}
}
}
- stopInternal方法
@Override
protected void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
// Stop our defined Services
//先结束子组件的生命周期,这里体现了父组件管理子组件的思想
for (Service service : services) {
service.stop();
}
globalNamingResources.stop();
//结束自己
stopAwait();
}
- stopAwait
public void stopAwait() {
stopAwait=true;
Thread t = awaitThread;
//如果主线程还在
if (t != null) {
ServerSocket s = awaitSocket;
//如果服务端socket还在
if (s != null) {
awaitSocket = null;
try {
//关闭socket
s.close();
} catch (IOException e) {
// Ignored
}
}
//给线程设置中断标志
t.interrupt();
try {
//等待主线程
t.join(1000);
} catch (InterruptedException e) {
// Ignored
}
}
}
总结:总体来讲server有几个特性:
1.阻塞主线程,通过建立socket阻塞住主线程;
2.管理子组件service的生命周期;
3.生命周期的每个过程都会发送事件给监听者
已经12点半了,要睡了,最近一个月打算把tomcat源码分析完,给自己打打气加油!