tomcat       

tomcat原理

Tomcat 的本质其实就是一个 WEB 服务器 + 一个 Servlet 容器,那么它必然需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是连接器容器,连接器用来处理外部网络连接,容器用来处理内部 Servlet。

image-20191116091507766

public class Connector extends LifecycleMBeanBase  {
/**
     * Coyote protocol handler.
     */
    // 协议的handler
    protected final ProtocolHandler protocolHandler;


    /**
     * Coyote adapter.
     */
    protected Adapter adapter = null;
}

根据protocol=”HTTP/1.1”,生成ProtocolHandler的子类Http11NioProtocol

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
               
http/1.1 -> protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
public class Http11NioProtocol extends AbstractHttp11JsseProtocol<NioChannel> {

    public Http11NioProtocol() {
        // nioEndpoint 利用nio和reactor模式实现io操作
        super(new NioEndpoint());
    }
  
  
    protected class SocketProcessor extends SocketProcessorBase<NioChannel> {

       public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
            super(socketWrapper, event);
       }

        @Override
        protected void doRun() {
          
          getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
    }
     
     // 处理连接的handler
     protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
     
       	Http11Processor processor = new Http11Processor(this, adapter);
        processor.process(wrapper, status);
     }
}

Endpoint 是用来实现 TCP/IP 协议的,Processor 用来实现 HTTP 协议

image-20191116093302140

image-20191116103029764

连接池处理任务流程

org.apache.tomcat.util.net.NioEndpoint.SocketProcessor#doRun->org.apache.coyote.AbstractProtocol.ConnectionHandler#process->org.apache.coyote.AbstractProcessorLight#process->org.apache.coyote.AbstractProcessorLight#process->org.apache.coyote.http11.Http11Processor#service->
  
org.apache.catalina.connector.CoyoteAdapter#service设置request的属于哪个host实例->
  
connector.getService().getContainer().getPipeline().getFirst().invoke.invoke->
org.apache.catalina.core.StandardEngineValve#invoke->
org.apache.catalina.valves.AbstractAccessLogValve#invoke->
org.apache.catalina.valves.ErrorReportValve#invoke->
org.apache.catalina.core.StandardHostValve#invoke->
org.apache.catalina.authenticator.AuthenticatorBase#invoke->
org.apache.catalina.core.StandardContextValve#invoke->
org.apache.catalina.core.StandardWrapperValve#invoke->
org.apache.catalina.core.StandardWrapper#allocate->
org.apache.catalina.core.StandardWrapper#loadServlet->

StandardEngineValve

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
  			// 从requets里知道改请求属于哪个host
        Host host = request.getHost();
        if (host == null) {
            // HTTP 0.9 or HTTP 1.0 request without a host when no default host
            // is defined. This is handled by the CoyoteAdapter.
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);
    }

org.apache.catalina.connector.CoyoteAdapter#postParseRequest

从service的host集合中找到匹配当前hostname的host实例并找到uri与wrapper的映射,并复制到request的MappingData中

connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());

定位servlet

Mapper 组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径,以及 Wrapper 容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map

首先,根据协议和端口号选定 Service 和 Engine。我们知道 Tomcat 的每个连接器都监听不同的端口

然后,根据域名选定 Host。Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器

之后,根据 URL 路径找到 Context 组件。Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径

最后,根据 URL 路径找到 Wrapper(Servlet)。Context 确定后,Mapper 再根据web.xml中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet。

Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理

Valve 表示一个处理点


public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}


public interface Pipeline extends Contained {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}

image-20191116110643376

整个调用过程由连接器中的 Adapter 触发的,它会调用 Engine 的第一个 Valve


// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
请求流转过程

image-20191116113706003

启动流程图

image-20191116145926938

image-20191128162252013

Acceptor

Selector

Processor

image-20191117103831162

ServerSocketChannel 通过 accept() 接受新的连接,accept() 方法返回获得 SocketChannel 对象,然后将 SocketChannel 对象封装在一个 PollerEvent 对象中,并将 PollerEvent 对象压入 Poller 的 Queue 里,这是个典型的“生产者 - 消费者”模式,Acceptor 与 Poller 线程之间通过 Queue 通信。

Poller

Poller 本质是一个 Selector,它内部维护一个 Queue,这个 Queue 定义如下

private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();

将这些事件注册到select里,进行nio

endpoint

Socket acceptor thread Socket poller thread Worker threads pool

Endpoint 组件的主要工作就是处理 I/O,而 NioEndpoint 利用 Java NIO API 实现了多路复用 I/O 模型

tomcat处理请求用的线程池使用无界队列,但重写队列,使得达到核心线程数,也能可以新增线程,主要利用记录当然任务数大于当前线程,就能新建线程

当没有空闲线程,则新建线程,不会添加到队列。(加队列的条件,线程池大小达到最大容量,或有空闲线程)

jdk线程池,没有空闲线程概念,(加队列的条件,线程池大小达到最大容量)


public class TaskQueue extends LinkedBlockingQueue<Runnable> {

  ...
   @Override
  //线程池调用任务队列的方法时,当前线程数肯定已经大于核心线程数了
  public boolean offer(Runnable o) {

      //如果线程数已经到了最大值,不能创建新线程了,只能把任务添加到任务队列。
      if (parent.getPoolSize() == parent.getMaximumPoolSize()) 
          return super.offer(o);
          
      //执行到这里,表明当前线程数大于核心线程数,并且小于最大线程数。
      //表明是可以创建新线程的,那到底要不要创建呢?分两种情况:
      
      //1. 如果已提交的任务数小于当前线程数,表示还有空闲线程,无需创建新线程
      if (parent.getSubmittedCount()<=(parent.getPoolSize())) 
          return super.offer(o);
          
      //2. 如果已提交的任务数大于当前线程数,线程不够用了,返回false去创建新线程
      if (parent.getPoolSize()<parent.getMaximumPoolSize()) 
          return false;
          
      //默认情况下总是把任务添加到任务队列
      return super.offer(o);
  }
  
}
对象池

public class SynchronizedStack<T> {

    //内部维护一个对象数组,用数组实现栈的功能
    private Object[] stack;

    //这个方法用来归还对象,用synchronized进行线程同步
    public synchronized boolean push(T obj) {
        index++;
        if (index == size) {
            if (limit == -1 || size < limit) {
                expand();//对象不够用了,扩展对象数组
            } else {
                index--;
                return false;
            }
        }
        stack[index] = obj;
        return true;
    }
    
    //这个方法用来获取对象
    public synchronized T pop() {
        if (index == -1) {
            return null;
        }
        T result = (T) stack[index];
        stack[index--] = null;
        return result;
    }
    
    //扩展对象数组长度,以2倍大小扩展
    private void expand() {
      int newSize = size * 2;
      if (limit != -1 && newSize > limit) {
          newSize = limit;
      }
      //扩展策略是创建一个数组长度为原来两倍的新数组
      Object[] newStack = new Object[newSize];
      //将老数组对象引用复制到新数组
      System.arraycopy(stack, 0, newStack, 0, size);
      //将stack指向新数组,老数组可以被GC掉了
      stack = newStack;
      size = newSize;
   }
}

双亲委派


public abstract class ClassLoader {

    //每个类加载器都有个父加载器
    private final ClassLoader parent;
    
    public Class<?> loadClass(String name) {
  
        //查找一下这个类是不是已经加载过了
        Class<?> c = findLoadedClass(name);
        
        //如果没有加载过
        if( c == null ){
          //先委托给父加载器去加载,注意这是个递归调用
          if (parent != null) {
              c = parent.loadClass(name);
          }else {
              // 如果父加载器为空,查找Bootstrap加载器是不是加载过了
              c = findBootstrapClassOrNull(name);
          }
        }
        // 如果父加载器没加载成功,调用自己的findClass去加载
        if (c == null) {
            c = findClass(name);
        }
        
        return c
    }
    
    protected Class<?> findClass(String name){
       //1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存
          ...
          
       //2. 调用defineClass将字节数组转成Class对象
       return defineClass(buf, off, len)
    }
    
    // 将字节码数组解析成一个Class对象,用native方法实现
    protected final Class<?> defineClass(byte[] b, int off, int len){
       ...
    }
}

Tomcat 的自定义类加载器 WebAppClassLoader的WebappClassLoaderBase属性 打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载 Web 应用自己定义的类。具体实现就是重写 ClassLoader 的两个方法:findClass 和 loadClass。


public Class<?> findClass(String name) throws ClassNotFoundException {
    ...
    
    Class<?> clazz = null;
    try {
            //1. 先在Web应用目录下查找类 
            clazz = findClassInternal(name);
    }  catch (RuntimeException e) {
           throw e;
       }
    
    if (clazz == null) {
    try {
            //2. 如果在本地目录没有找到,交给父加载器去查找
            clazz = super.findClass(name);
    }  catch (RuntimeException e) {
           throw e;
       }
    
    //3. 如果父类也没找到,抛出ClassNotFoundException
    if (clazz == null) {
        throw new ClassNotFoundException(name);
     }

    return clazz;
}

先在 Web 应用本地目录下查找要加载的类。如果没有找到,交给父加载器去查找,它的父加载器就是上面提到的系统类加载器 AppClassLoader。


public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (getClassLoadingLock(name)) {
 
        Class<?> clazz = null;

        //1. 先在本地cache查找该类是否已经加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        //2. 从系统类加载器的cache中查找是否加载过
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        // 3. 尝试用ExtClassLoader类加载器类加载
        ClassLoader javaseLoader = getJavaseClassLoader();
        try {
            clazz = javaseLoader.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 4. 尝试在本地目录搜索class并加载
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 5. 尝试用系统类加载器(也就是AppClassLoader)来加载
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
       }
    
    //6. 上述过程都加载失败,抛出异常
    throw new ClassNotFoundException(name);
}

先在本地目录下加载,为了避免本地目录下的类覆盖 JRE 的核心类,先尝试用 JVM 扩展类加载器 ExtClassLoader 去加载

在stardardContext的startinternal中


if (getLoader() == null) {
  WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
  webappLoader.setDelegate(getDelegate());
  setLoader(webappLoader);
}

在org.apache.catalina.startup.HostConfig#start->org.apache.catalina.startup.HostConfig#deployDirectory部署webapps下所有文件夹,一个文件夹一个standardContext,并加入standardHost,每个standardContext都会新建WebApploader,达到隔离app的目的

加载顺序

ExtClassLoader - 本地目录下加载 - 父加载器加载(sharedclassloader)

image-20191118165432478

CommonClassLoader对应<Tomcat>/common/*
CatalinaClassLoader对应 <Tomcat >/server/*
SharedClassLoader对应 <Tomcat >/shared/*
WebAppClassloader对应 <Tomcat >/webapps/<app>/WEB-INF/*目录

catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
private void initClassLoaders() {
        try {
          	// parent为null,默认设置 getSystemClassLoader()方法返回的ClassLoader 作为其父类,getSystemClassLoader()返回的 ClassLoader 通常就是 AppClassLoader
            commonLoader = createClassLoader("common", null);
            if (commonLoader == null) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader = this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

Common类加载器,负责加载Tomcat和Web应用都复用的类

Catalina类加载器,负责加载Tomcat专用的类,而这些被加载的类在Web应用中将不可见

Shared类加载器,负责加载Tomcat下所有的Web应用程序都复用的类,而这些被加载的类在Tomcat中将不可见

WebApp类加载器,负责加载具体的某个Web应用程序所使用到的类,而这些被加载的类在Tomcat和其他的Web应用程序都将不可见

Jsp类加载器,每个jsp页面一个类加载器,不同的jsp页面有不同的类加载器,方便实现jsp页面的热插拔

servlet

Wrap组件封装了servlet

public synchronized Servlet loadServlet() throws ServletException {
    Servlet servlet;
  
    // 1. 创建一个Servlet实例
    servlet = (Servlet) instanceManager.newInstance(servletClass);    
    
    // 2.调用了Servlet的init方法,这是Servlet规范要求的
    initServlet(servlet);
    
    return servlet;
}

当请求到来时,Context 容器的 BasicValve 会调用 Wrapper 容器中 Pipeline 中的第一个 Valve,然后会调用到 StandardWrapperValve


public final void invoke(Request request, Response response) {

    //1.实例化Servlet
    servlet = wrapper.allocate();
   
    //2.给当前请求创建一个Filter链
    ApplicationFilterChain filterChain =
        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

   //3. 调用这个Filter链,Filter链中的最后一个Filter会调用Servlet
   filterChain.doFilter(request.getRequest(), response.getResponse());

}

连接器是调用 CoyoteAdapter 的 service 方法来处理请求的,而 CoyoteAdapter 会调用容器的 service 方法

当容器的 service 方法返回时,CoyoteAdapter 判断当前的请求是不是异步 Servlet 请求,如果是,就不会销毁 Request 和 Response 对象,也不会把响应信息发到浏览器


public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) {
    
   //调用容器的service方法处理请求
    connector.getService().getContainer().getPipeline().
           getFirst().invoke(request, response);
   
   //如果是异步Servlet请求,仅仅设置一个标志,
   //否则说明是同步Servlet请求,就将响应数据刷到浏览器
    if (request.isAsync()) {
        async = true;
    } else {
        request.finishRequest();
        response.finishResponse();
    }
   
   //如果不是异步Servlet请求,就销毁Request对象和Response对象
    if (!async) {
        request.recycle();
        response.recycle();
    }
}

logger


private LogFactory() {
    // 通过ServiceLoader尝试加载Log的实现类
    ServiceLoader<Log> logLoader = ServiceLoader.load(Log.class);
    Constructor<? extends Log> m=null;
    
    for (Log log: logLoader) {
        Class<? extends Log> c=log.getClass();
        try {
            m=c.getConstructor(String.class);
            break;
        }
        catch (NoSuchMethodException | SecurityException e) {
            throw new Error(e);
        }
    }
    
    //如何没有定义Log的实现类,discoveredLogConstructor为null
    discoveredLogConstructor = m;
}

public Log getInstance(String name) throws LogConfigurationException {
    //如果discoveredLogConstructor为null,也就没有定义Log类,默认用DirectJDKLog(封装了jul)
    if (discoveredLogConstructor == null) {
        return DirectJDKLog.getInstance(name);
    }

    try {
        return discoveredLogConstructor.newInstance(name);
    } catch (ReflectiveOperationException | IllegalArgumentException e) {
        throw new LogConfigurationException(e);
    }
}

线程池大小选择

利特尔法则:系统中的请求数 = 请求的到达速率 × 每个请求处理时间

线程池大小 = 每秒请求数 × 平均请求处理时间

线程池大小 = (线程 I/O 阻塞时间 + 线程 CPU 时间 )/ 线程 CPU 时间

    public boolean offer(Runnable o) {
      //we can't do any checks
        if (parent==null) return super.offer(o);
        //we are maxed out on threads, simply queue the object
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
        //we have idle threads, just add it to the queue
        if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
        //if we have less threads than maximum force creation of a new thread
        if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
        //if we reached here, we need to add it to the queue
        return super.offer(o);
    }

tomcat工作线程池,如果线程池大小小于最大线程池大小,则不会添加到队列(LinkedBlockingQueue)(和默认的线程池处理不一样,默认的是如果线程池大小等于核心线程池大小的时候,先插入队列,队列满了才添加线程)(tomcat线程池的最大线程数就是jdk线程池的核心线程数

如果有空闲线程(parent.getSubmittedCount()<=(parent.getPoolSize())),插入队列,

如果没空闲添加线程

hostconfig

public class HostConfig implements LifecycleListener {

		public void lifecycleEvent(LifecycleEvent event) {
			if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
		}
		
		public void start() {		
		  if (host.getDeployOnStartup())
            deployApps();
		}

}

standardhost通过事件通知hostconfig加载webapps

contexConfig

加载解析/WEB-INF/web.xml

pipeline

engine,host,context,wrapper; 四个容器中每个容器都包含自己的管道对象,管道对象用来存放若干阀门对象,但tomcat会为他们制定一个默认的基础阀门【StandardEngineValve,StandardHostValve,StandardContextValve ,StandardWrapperValve】。四个基础阀门放在各自容器管道的最后一位,用于查找下一级容器的管道.

分类

一个pipeline包含多个Valve,这些阀共分为两类,一类叫基础阀(通过getBasic、setBasic方法调用),一类是普通阀(通过addValve、removeValve调用)。 一个管道一般有一个基础阀(通过setBasic添加),可以有0到多个普通阀(通过addValve添加)。

image-20191126095336752

自定的valve类文件需要发布到CATALINA_HOME/lib目录下而不是应用的发布目录WEB-INF/classes

mapper

初始化阶段standardContext已经保存所有StandardWrapper信息(path和servlet等信息,servlet默认还未实例化)

protected volatile Servlet instance = null;

启动阶段,start service时mapper监听器启动,mapperListener.start();遍历所有Wrapper把path和对应Wrapper信息放入StandardService的Mapperhostname->contextList(WebResourceRoot,context的描述)

public void addContextVersion(String hostName, Host host, String path,
            String version, Context context, String[] welcomeResources,
            WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {}

在mapperLister中注册host,registerHost(host)->registerContext->org.apache.catalina.mapper.MapperListener#prepareWrapperMappingInfo

保存path和对应wrapper在WrapperMappingInfo中

private void registerHost(Host host) {

        String[] aliases = host.findAliases();
        mapper.addHost(host.getName(), aliases, host);

        for (Container container : host.findChildren()) {
            if (container.getState().isAvailable()) {
                registerContext((Context) container);
            }
        }

        // Default host may have changed
        findDefaultHost();

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerHost",
                    host.getName(), domain, service));
        }
    }


 private void prepareWrapperMappingInfo(Context context, Wrapper wrapper,
            List<WrapperMappingInfo> wrappers) {
        String wrapperName = wrapper.getName();
        boolean resourceOnly = context.isResourceOnlyServlet(wrapperName);
        String[] mappings = wrapper.findMappings();
        for (String mapping : mappings) {
            boolean jspWildCard = (wrapperName.equals("jsp")
                                   && mapping.endsWith("/*"));
            wrappers.add(new WrapperMappingInfo(mapping, wrapper, jspWildCard,
                    resourceOnly));
        }
    }


public class WrapperMappingInfo {

    private final String mapping;
    private final Wrapper wrapper;
    private final boolean jspWildCard;
    private final boolean resourceOnly;

    public WrapperMappingInfo(String mapping, Wrapper wrapper,
            boolean jspWildCard, boolean resourceOnly) {
        this.mapping = mapping;
        this.wrapper = wrapper;
        this.jspWildCard = jspWildCard;
        this.resourceOnly = resourceOnly;
      
    }
}

请求处理

org.apache.coyote.AbstractProcessorLight#process->org.apache.coyote.http11.Http11Processor#service->

org.apache.catalina.connector.CoyoteAdapter#service->engine.getPipeline().getFirst().invoke

 connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

org.apache.catalina.core.StandardEngineValve#invoke->org.apache.catalina.valves.AbstractAccessLogValve#invoke->

org.apache.catalina.valves.ErrorReportValve#invoke->org.apache.catalina.core.StandardHostValve#invoke->

public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Select the Context to be used for this Request
    Context context = request.getContext();
    if (context == null) {
        return;
    }

    if (request.isAsyncSupported()) {
        request.setAsyncSupported(context.getPipeline().isAsyncSupported());
    }

    boolean asyncAtStart = request.isAsync();

    try {
        context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);

        if (!asyncAtStart && !context.fireRequestInitEvent(request.getRequest())) {
            // Don't fire listeners during async processing (the listener
            // fired for the request that called startAsync()).
            // If a request init listener throws an exception, the request
            // is aborted.
            return;
        }

        // Ask this Context to process this request. Requests that are
        // already in error must have been routed here to check for
        // application defined error pages so DO NOT forward them to the the
        // application for processing.
        try {
            if (!response.isErrorReportRequired()) {
                context.getPipeline().getFirst().invoke(request, response);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
            // If a new error occurred while trying to report a previous
            // error allow the original error to be reported.
            if (!response.isErrorReportRequired()) {
                request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                throwable(request, response, t);
            }
        }

        // Now that the request/response pair is back under container
        // control lift the suspension so that the error handling can
        // complete and/or the container can flush any remaining data
        response.setSuspended(false);

        Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

        // Protect against NPEs if the context was destroyed during a
        // long running request.
        if (!context.getState().isAvailable()) {
            return;
        }

        // Look for (and render if found) an application level error page
        if (response.isErrorReportRequired()) {
            // If an error has occurred that prevents further I/O, don't waste time
            // producing an error report that will never be read
            AtomicBoolean result = new AtomicBoolean(false);
            response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
            if (result.get()) {
                if (t != null) {
                    throwable(request, response, t);
                } else {
                    status(request, response);
                }
            }
        }

        if (!request.isAsync() && !asyncAtStart) {
            context.fireRequestDestroyEvent(request.getRequest());
        }
    } finally {
        // Access a session (if present) to update last accessed time, based
        // on a strict interpretation of the specification
        if (ACCESS_SESSION) {
            request.getSession(false);
        }

        context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
    }
}

​ org.apache.catalina.authenticator.AuthenticatorBase#invoke->org.apache.catalina.core.StandardContextValve#invoke->

​ org.apache.catalina.core.StandardWrapperValve#invoke->org.apache.catalina.core.StandardWrapper#allocate->

​ org.apache.catalina.core.StandardWrapper#loadServlet

​ filterChain.doFilter(request, response);->

​ org.apache.catalina.core.ApplicationFilterChain#doFilter

private void internalDoFilter(ServletRequest request,
                              ServletResponse response)
    throws IOException, ServletException {

    // Call the next filter if there is one
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            Filter filter = filterConfig.getFilter();

            if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                    filterConfig.getFilterDef().getAsyncSupported())) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
            }
            if( Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[]{req, res, this};
                SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
            } else {
                filter.doFilter(request, response, this);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.filter"), e);
        }
        return;
    }

    // We fell off the end of the chain -- call the servlet instance
    try {
        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
            lastServicedRequest.set(request);
            lastServicedResponse.set(response);
        }

        if (request.isAsyncSupported() && !servletSupportsAsync) {
            request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                    Boolean.FALSE);
        }
        // Use potentially wrapped request from this point
        if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse) &&
                Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal =
                ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            SecurityUtil.doAsPrivilege("service",
                                       servlet,
                                       classTypeUsedInService,
                                       args,
                                       principal);
        } else {
            servlet.service(request, response);
        }
    } catch (IOException | ServletException | RuntimeException e) {
        throw e;
    } catch (Throwable e) {
        e = ExceptionUtils.unwrapInvocationTargetException(e);
        ExceptionUtils.handleThrowable(e);
        throw new ServletException(sm.getString("filterChain.servlet"), e);
    } finally {
        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
            lastServicedRequest.set(null);
            lastServicedResponse.set(null);
        }
    }
}
路径映射

org.apache.catalina.connector.CoyoteAdapter#service->org.apache.catalina.connector.CoyoteAdapter#postParseRequest处理host+uri到wrapper的映射

 connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());

对于 tomcat 数据读取总结如下:

处理响应

image-20210510000104403

响应数据写入的总结

对于请求体的写采用阻塞的方式,调用NioSelectorPool 的 write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout) 方法。在该方法中又会调用 NioBlockingSelector 的 write() 方法

请求响应时在另一个block-poller线程执行

NioEndpoint对象中维护了一个NioSelecPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程,这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法

先使用socket的原selector,检测可写事件,如果遇到网络问题,写失败,再丢到上面的BlockPoller线程,使用新的selector写数据

org.apache.tomcat.util.net.NioBlockingSelector#write

public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {  
        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());  
        if ( key == null ) throw new IOException("Key no longer registered");  
        KeyAttachment att = (KeyAttachment) key.attachment();  
        int written = 0;  
        boolean timedout = false;  
        int keycount = 1; //assume we can write  
        long time = System.currentTimeMillis(); //start the timeout timer  
        try {  
            while ( (!timedout) && buf.hasRemaining()) {  
                if (keycount > 0) { //only write if we were registered for a write  
                    //直接往socket中写数据  
                    int cnt = socket.write(buf); //write the data  
								    lastWrite.set(cnt);  
                    if (cnt == -1)  
                        throw new EOFException();  
                    written += cnt;  
                    //写数据成功,直接进入下一次循环,继续写  
                    if (cnt > 0) {  
                        time = System.currentTimeMillis(); //reset our timeout timer  
                        continue; //we successfully wrote, try again without a selector  
                    }  
                }  
                //如果写数据返回值cnt等于0,通常是网络不稳定造成的写数据失败  
                try {  
                    //开始一个倒数计数器   
                    if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);  
                    //将socket注册到辅Selector,这里poller就是BlockSelector线程  
                    poller.add(att,SelectionKey.OP_WRITE);  
                    //阻塞,直至超时时间唤醒,或者在还没有达到超时时间,在BlockSelector中唤醒  
                    att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);  
                }catch (InterruptedException ignore) {  
                    Thread.interrupted();  
                }  
                if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {  
                    keycount = 0;  
                }else {  
                    //还没超时就唤醒,说明网络状态恢复,继续下一次循环,完成写socket  
                    keycount = 1;  
                    att.resetWriteLatch();  
                }  
  
                if (writeTimeout > 0 && (keycount == 0))  
                    timedout = (System.currentTimeMillis() - time) >= writeTimeout;  
            } //while  
            if (timedout)   
                throw new SocketTimeoutException();  
        } finally {  
            poller.remove(att,SelectionKey.OP_WRITE);  
            if (timedout && key != null) {  
                poller.cancelKey(socket, key);  
            }  
        }  
        return written;  
    }

响应写出

image-20210628223926001

当调用Response#getOutpuStream()方法或者Response#getWriter()写出数据时,数据并不是直接发送出去,而是缓存在内部的OutputBuffer中。

org.apache.catalina.connector.Response

public class Response implements HttpServletResponse {
  
  /**
   * The associated output buffer.
   */
  protected final OutputBuffer outputBuffer;


  /**
   * The associated output stream.
   */
  protected CoyoteOutputStream outputStream;


  /**
   * The associated writer.
   */
  protected CoyoteWriter writer;





    /**
     * @return the servlet output stream associated with this Response.
     *
     * @exception IllegalStateException if <code>getWriter</code> has
     *  already been called for this response
     * @exception IOException if an input/output error occurs
     */
    // getOutputStream()方法将内容写出到outputBuffer
    // 返回底层的CoyoteOutputStream字节输出流。该输出流将会把内容写出到outputBuffer中。
    @Override
    public ServletOutputStream getOutputStream()
        throws IOException {

        if (usingWriter) {
            throw new IllegalStateException
                (sm.getString("coyoteResponse.getOutputStream.ise"));
        }

        usingOutputStream = true;
        if (outputStream == null) {
            outputStream = new CoyoteOutputStream(outputBuffer);
        }
        return outputStream;

    }
public class OutputBuffer extends Writer {

    private static final StringManager sm = StringManager.getManager(OutputBuffer.class);

    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

    /**
     * Encoder cache.
     */
    private final Map<Charset, C2BConverter> encoders = new HashMap<>();


    /**
     * Default buffer size.
     */
    private final int defaultBufferSize;

    // ----------------------------------------------------- Instance Variables

    /**
     * The byte buffer.
     */
    private ByteBuffer bb;


    /**
     * The char buffer.
     */
    private final CharBuffer cb;
  
  
  
  
  
  
   public void append(byte src[], int off, int len) throws IOException {
        if (bb.remaining() == 0) {
            appendByteArray(src, off, len);
        } else {
            int n = transfer(src, off, len, bb);
            len = len - n;
            off = off + n;
            // 满了才会flush
            if (isFull(bb)) {
                flushByteBuffer();
                appendByteArray(src, off, len);
            }
        }
    }
  
  
      private void flushByteBuffer() throws IOException {
        realWriteBytes(bb.slice());
        clear(bb);
  	  }
  
     public void realWriteBytes(ByteBuffer buf) throws IOException {

        if (closed) {
            return;
        }
        if (coyoteResponse == null) {
            return;
        }

        // If we really have something to write
        if (buf.remaining() > 0) {
            // real write to the adapter
            try {
                coyoteResponse.doWrite(buf);
            } catch (CloseNowException e) {
                // Catch this sub-class as it requires specific handling.
                // Examples where this exception is thrown:
                // - HTTP/2 stream timeout
                // Prevent further output for this response
                closed = true;
                throw e;
            } catch (IOException e) {
                // An IOException on a write is almost always due to
                // the remote client aborting the request. Wrap this
                // so that it can be handled better by the error dispatcher.
                throw new ClientAbortException(e);
            }
        }

    }

connector/Response.java里的outputbuffer调用coyote/Response.java里的outputbuffer(Http11OutputBuffer extends OutputBuffer)

将connector.response里的outputbuffer数据通过coyote.response里的outputbffer写入到socketbuffer

协议解析

if (!inputBuffer.parseHeaders()) {
  // We've read part of the request, don't recycle it
  // instead associate it with the socket
  openSocket = true;
  readComplete = false;
  break;
}


 if (openSocket) {
   if (readComplete) {
     return SocketState.OPEN;
   } else {
     return SocketState.LONG;
   }
 } else {
   return SocketState.CLOSED;
 }

 if (state == SocketState.OPEN) {
   // In keep-alive but between requests. OK to recycle
   // processor. Continue to poll for the next request.
   connections.remove(socket);
   release(processor);
   wrapper.registerReadInterest();
 }

数据不完整,等待下个读事件

 void endRequest() throws IOException {

   if (swallowInput && (lastActiveFilter != -1)) {
     int extraBytes = (int) activeFilters[lastActiveFilter].end();
     byteBuffer.position(byteBuffer.position() - extraBytes);
   }
 }

没被消费的body,被吞掉,byteBuffer的位置往前移动,继续等待下次读事件触发

一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 ServletRequest 和 ServletResponse 对象进行交流。

也可以从 server.xml 的配置结构可以看出 tomcat 整体的内部结构:


<Server port="8005" shutdown="SHUTDOWN">

  <Service name="Catalina">

    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>

    <Engine defaultHost="localhost" name="Catalina">

      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">

        <Context docBase="handler-api" path="/handler" reloadable="true" source="org.eclipse.jst.jee.server:handler-api"/>
      </Host>
    </Engine>
  </Service>
</Server>

Engine:表示一个虚拟主机的引擎,一个 Tomcat Server 只有一个 引擎,连接器所有的请求都交给引擎处理,而引擎则会交给相应的虚拟主机去处理请求;

Host:表示虚拟主机,一个容器可以有多个虚拟主机,每个主机都有对应的域名,在 Tomcat 中,一个 webapps 就代表一个虚拟主机,当然 webapps 可以配置多个;

Context:表示一个应用容器,一个虚拟主机可以拥有多个应用,webapps 中每个目录都代表一个 Context,每个应用可以配置多个 Servlet。

Engine -> Host -> Context -> Wrapper -> Servlet

解析webapps所有war包

解压war包,导入class

读取WEB-INF文件夹里xml配置(或注解(springboot))

类加载servert class

socket网络编程

一个线程监听连接,另外一个线程处理请求

connector组件

所有请求的入口和出口

连接器,监听接口

接受连接请求,分配线程让container来处理这个请求。

protocol 监听的协议,默认是http/1.1

port 指定服务器端要创建的端口号

minThread服务器启动时创建的处理请求的线程数

maxThread最大可以创建的处理请求的线程数

enableLookups如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址

redirectPort指定服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号

acceptCount指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理

connectionTimeout指定超时的时间数(以毫秒为单位)

SSLEnabled 是否开启 sll 验证,在Https 访问时需要开启。

endpoint

默认NioEndpoint,监听端口

生产者,消费者模式

Socket acceptor thread

Socket poller thread

Woker threads pool ####

service组件

service收集connector

container组件

通用容器,包括以下容器

engine

servlet引擎(责任链模式)

An Engine represents the entry point (within Catalina) that processes every request. The Engine implementation for Tomcat stand alone analyzes the HTTP headers included with the request, and passes them on to the appropriate Host (virtual host).

处理请求,把请求分配到对应的host

host

主机

context

webapp

wrapper

Servlet

executor

线程池

processor

protocol

启动

lifecycle生命周期

初始化

启动

catalina -> server -> (service1 service2 …)

service -> (engine connector1 connector2 …)

engine -> host -> context -> wrapper

三个类加载器

Common ClassLoader

Catalina ClassLoader

Shared ClassLoader

下面的简图是Tomcat9版本的官方文档给出的Tomcat的类加载器的图。


      Bootstrap
          |
       System
          |
       Common
       /     \
  Webapp1   Webapp2 ..

这3个部分,在上面的Java双亲委派模型图中都有体现。不过可以看到ExtClassLoader没有画出来,可以理解为是跟bootstrap合并了,都是去JAVA_HOME/jre/lib下面加载类

Web应用默认的类加载顺序是(打破了双亲委派规则):

  1. 先从JVM的BootStrapClassLoader中加载。
  2. 加载Web应用下/WEB-INF/classes中的类。
  3. 加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。
  4. 加载上面定义的System路径下面的类。
  5. 加载上面定义的Common路径下面的类。

如果在配置文件中配置了<Loader delegate="true"/>,那么就是遵循双亲委派规则,加载顺序如下:

  1. 先从JVM的BootStrapClassLoader中加载。
  2. 加载上面定义的System路径下面的类。
  3. 加载上面定义的Common路径下面的类。
  4. 加载Web应用下/WEB-INF/classes中的类。
  5. 加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        // 1. 从本地缓存中查找是否加载过此类
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        // 2. 从AppClassLoader中查找是否加载过此类
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        String resourceName = binaryNameToPath(name, false);
        // 3. 尝试用ExtClassLoader类加载器加载类,防止Web应用覆盖JRE的核心类
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {
            URL url;
            if (securityManager != null) {
                PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                url = AccessController.doPrivileged(dp);
            } else {
                url = javaseLoader.getResource(resourceName);
            }
            tryLoadingFromJavaseLoader = (url != null);
        } catch (Throwable t) {
            tryLoadingFromJavaseLoader = true;
        }

        boolean delegateLoad = delegate || filter(name, true);

        // 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }
    throw new ClassNotFoundException(name);
}

io模型

nio

一个acceptor线程

两个Poller多路复用线程,调用selector.select(timeout)函数

线程创建

  1. http-nio-8080-BlockPoller — NioBlockingSelector

    如果socket.write返回0,说明缓存区已满,则设置selector监控可写,线程阻塞等待socket可写

    poller.add(att, SelectionKey.OP_WRITE, reference);
    att.awaitWriteLatch(AbstractEndpoint.toTimeout(writeTimeout), TimeUnit.MILLISECONDS);
    
		写读或可写则countDown唤醒阻塞线程
		org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller
    
														if (sk.isReadable()) {
                                countDown(socketWrapper.getReadLatch());
                            }
                            if (sk.isWritable()) {
                                countDown(socketWrapper.getWriteLatch());
                            }
                            
  1. Ajp-nio-8009-BlockPoller 处理request

  2. Http-nio-8080-Acceptor一个监听线程

  3. Http-nio-8080-ClientPoller poller线程

  4. http-nio-8080-exec-n处理request的线程池中的线程

       
    一次连接countUpOrAwaitConnectionlatch.countUpOrAwait),一共10000个latch
    使用aqs同步10000个AtomicLong count
       
    LimitLatch{
    	private class Sync extends AbstractQueuedSynchronizer
    }
       
    

    poller线程(selector线程)

    1. 将socket注册到selector:poller线程不断从events队列(SynchronizedQueue队列,入队出队都加了synchronized)里取pollerEvent事件,并执行事件(hasEvents = events(),pollerEvent.run(),run里执行逻辑是:如果pollerEvent是OP_REGISTER,则把read socket注册到Selector)

    2: select socket事件:

    keyCount = selector.selectNow();

    selector.select(selectorTimeout)等待read事件

监听到一个请求之后,把socket发送给poller

poller从events工作队列里拿pollerEvent,把socket赋值给event,然后添加到events工作队列

线程池角度:pollerevent是放在队列里的任务,poller是消费者,不断取event

BlockPoller 线程,该线程主要处理阻塞的读写操作。对于请求体数据读取,根据以前文章一般由 tomcat io 线程进行,当数据不可读的时候的时候(客户端数据未发送完毕),会注册封装的 OPEN_READ 事件到 BlockPoller 线程中,然后阻塞当前线程(一般为tomcat io线程)。对于响应数据的写入,根据以前文章一般也由 tomcat io 线程进行,当数据不可写的时候(原始 socket 发送缓冲区满),会注册封装的 OPEN_WRITE 事件对象到 BlockPoller 线程中,然后阻塞当前线程。

层级

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<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" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <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 share
       a 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">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->


    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
    -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443
         This connector uses the NIO implementation. The default
         SSLImplementation will depend on the presence of the APR/native
         library and the useOpenSSL attribute of the
         AprLifecycleListener.
         Either JSSE or OpenSSL style configuration may be used regardless of
         the SSLImplementation selected. JSSE style configuration is used below.
    -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>
    -->
    <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
         This connector uses the APR/native implementation which always uses
         OpenSSL for TLS.
         Either JSSE or OpenSSL style configuration may be used. OpenSSL style
         configuration is used below.
    -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
               maxThreads="150" SSLEnabled="true" >
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
        <SSLHostConfig>
            <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                         certificateFile="conf/localhost-rsa-cert.pem"
                         certificateChainFile="conf/localhost-rsa-chain.pem"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>
    -->

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine name="Catalina" defaultHost="localhost">

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!--
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
      -->

      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <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>


server
    service
        Connector
        engine
            host
            		context
                Valve
            Realm
        

reference

https://www.processon.com/view/link/5d75f8a7e4b04a19501b07d5

https://www.jianshu.com/p/76ff17bc6dea

http://objcoding.com/2019/05/30/tomcat-architecture/

https://time.geekbang.org/column/article/96764

https://www.cnblogs.com/haimishasha/p/10740606.html

https://juejin.cn/post/6844903881067986957

https://blog.csdn.net/qq_26222859/article/details/45823755