中间件-Tomcat

发布于:2023-01-22 ⋅ 阅读:(331) ⋅ 点赞:(0)

​​​​​​​

 

概述

Tomcat是在web开发过程中最常用的一个Servlet容器,尤其在SpringBoot开始流行后更加简化了Tomcat的使用流程。深入理解Tomcat能够学习到这个流行了很长时间的中间件的设计精华,同时还能够为更好的使用Tomcat提供技术支撑。

总体架构

组件说明 

Server

代表一个tomcat实例

Service

一个Service代表一组Connector和Engine的组合

Connector

连接器,负责接受客户端的TCP连接

Engine

引擎代表了处理器请求的处理机的实体,它负责接受Connector的请求并返回Connector响应

Host

虚拟主机,对应不同的host name

Context

代表一个web服务实例

Servlet

代表一个处理业务逻辑的处理器

Jsper

用来处理jsp的组件

Java EL

用来处理表达式的组件

Naming

命名服务

Juli

用来处理服务器日志的组件

层次结构

<Server>
    <Service>
        <Connector/>
        <Connector/>
        <Engine>
            <Host>
                <Context/>
            <Host/>
        <Engine/>
    <Service/>
</Server>

目录结构

bin

脚本目录

conf

配置文件目录

lib

存放依赖包的目录

logs

默认的日志目录

webapps

web应用默认的部署目录

work

存放jsp生成的编译文件的目录

配置

服务器配置

配置文件是conf/server.xml

//port: 接受关闭服务器的指令的端口
//shutdown: 关闭服务器的指令
<Server port="8005" shutdown="SHUTDOWN">
    <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
    <Listener className="org.apache.catalina.security.SecurityListener" />
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
    <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>
    <Service name="Catalina">
        <Executor 
            #线程池名称
            name="tomcatThreadPool" 
            #线程中的线程前缀
            namePrefix="catalina‐exec‐" 
            #最大线程数
            maxThreads="200" 
            #核心线程数
            minSpareThreads="100" 
            #非核心线程的空闲时间
            maxIdleTime="60000" 
            #线程池排队队列的大小
            maxQueueSize="Integer.MAX_VALUE" 
            #启动线程时是否初始化minSpareThreads这部分线程
            prestartminSpareThreads="false" 
            #线程池中线程的优先级
            threadPriority="5" 
            #线程池实现类
            className="org.apache.catalina.core.StandardThreadExecutor"/>

        <Connector port="8080" 
            protocol="HTTP/1.1" 
            executor="tomcatThreadPool" 
            maxThreads="1000" 
            minSpareThreads="100" 
            acceptCount="1000" 
            maxConnections="1000" 
            connectionTimeout="20000" 
            compression="on" 
            compressionMinSize="2048" 
            disableUploadTimeout="true" 
            redirectPort="8443" 
            URIEncoding="UTF‐8" /> 
       <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
            <SSLHostConfig>
                <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                         type="RSA" />
            </SSLHostConfig>
       </Connector>
       <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>
       <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
       <Engine name="Catalina" defaultHost="localhost">
            <Host 
              #虚拟主机名称
              name="localhost"  
              #应用的部署目录,默认时webapps
              appBase="webapps"
              #是否自动解压war文件
              unpackWARs="true" 
              #是否自动检查项目变更,并重新部署
              autoDeploy="true">
                   <!-- 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>

应用配置

应用级别的配置文件是web.xml

<context‐param> 
    <param‐name>contextConfigLocation</param‐name> 
    <param‐value>classpath:applicationContext‐*.xml</param‐value> 
    <description>Spring Config File Location</description> 
</context‐param> 

<session‐config> 
    <session‐timeout>30</session‐timeout> 
    <cookie‐config> 
        <name>JESSIONID</name> 
        <domain>www.itcast.cn</domain> 
        <path>/</path> 
        <comment>Session Cookie</comment> 
        <http‐only>true</http‐only> 
        <secure>false</secure> 
        <max‐age>3600</max‐age> 
    </cookie‐config> 
    <tracking‐mode>COOKIE</tracking‐mode> 
</session‐config> 

<servlet> 
    <servlet‐name>myServlet</servlet‐name> 
    <servlet‐class>cn.itcast.web.MyServlet</servlet‐class> 
    <init‐param> 
        <param‐name>fileName</param‐name> 
        <param‐value>init.conf</param‐value> 
    </init‐param> 
    <load‐on‐startup>1</load‐on‐startup> 
    <enabled>true</enabled> 
    <multipart‐config> 
        #文件存放路径
        <location>C://path</location> 
        #文件大小的最大值
        <max‐file‐size>10485760</max‐file‐size> 
        <max‐request‐size>10485760</max‐request‐size> 
        <file‐size‐threshold>0</file‐size‐threshold> 
    </multipart‐config> 
</servlet> 
<servlet‐mapping> 
    <servlet‐name>myServlet</servlet‐name> 
    <url‐pattern>*.do</url‐pattern> 
    <url‐pattern>/myservet/*</url‐pattern> 
</servlet‐mapping>

<filter> 
    <filter‐name>myFilter</filter‐name> 
    <filter‐class>cn.itcast.web.MyFilter</filter‐class> 
    <async‐supported>true</async‐supported> 
    <init‐param> 
        <param‐name>language</param‐name> 
        <param‐value>CN</param‐value> 
    </init‐param> 
</filter> 
<filter‐mapping> 
    <filter‐name>myFilter</filter‐name> 
    <url‐pattern>/*</url‐pattern> 
</filter‐mapping>

<listener> 
    <listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐ class> 
</listener> 

<welcome‐file‐list> 
    <welcome‐file>index.html</welcome‐file> 
    <welcome‐file>index.htm</welcome‐file> 
    <welcome‐file>index.jsp</welcome‐file> 
</welcome‐file‐list> 

<error‐page> 
    <error‐code>404</error‐code> 
    <location>/404.html</location> 
</error‐page> 
<error‐page> 
    <error‐code>500</error‐code> 
    <location>/500.html</location> 
</error‐page> 
<error‐page> 
    <exception‐type>java.lang.Exception</exception‐type> 
    <location>/error.jsp</location> 
</error‐page>

管理配置

tomcat管理配置主要在tomcat-user.xml(但在生产环境下一般不会对外开放tomcat管理系统)。

<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="tomcat" password="tomcat" roles="admin-gui,admin-script,manager-gui,manager-script"/>

安全

配置安全

  • 删除webapps目录下的所有文件,禁用tomcat管理界面;
  • 注释或删除tomcat-users.xml文件内的所有用户权限;
  • 更改关闭tomcat指令或禁用(server标签的port和shutdown);
  • 定义错误页面,避免内部异常直接暴露给用户;

应用安全

应用程序自身对访问进行认证和授权。

传输安全

使用https代替http。

dongzhang@DongdeMacBook-Air conf % keytool -genkey -alias tomcat -keyalg RSA -keystore tomcatkey.keystore
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  zhang
您的组织单位名称是什么?
  [Unknown]:  local
您的组织名称是什么?
  [Unknown]:  local
您所在的城市或区域名称是什么?
  [Unknown]:  guangzhou
您所在的省/市/自治区名称是什么?
  [Unknown]:  guangzhou
该单位的双字母国家/地区代码是什么?
  [Unknown]:  CN
CN=zhang, OU=local, O=local, L=guangzhou, ST=guangzhou, C=CN是否正确?
  [否]:  y

输入 <tomcat> 的密钥口令
	(如果和密钥库口令相同, 按回车):
再次输入新口令:

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore tomcatkey.keystore -destkeystore tomcatkey.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

#配置ssl Connector
<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>

集群

WebSocket

嵌入式Tomcat

源码

环境准备

  • 下载源码包并解压 https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.65/src/apache-tomcat-9.0.65-src.tar.gz
  • 在源码包目录下创建home文件夹,并把 conf/和 webapps/ 复制到home目录下
  • 在源码包目录下创建一个pom.xml,并添加如下内容
  • <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>Tomcat9.0</artifactId>
      <name>Tomcat9</name>
      <version>9.0.20</version>
    
      <build>
        <finalName>Tomcat9</finalName>
        <sourceDirectory>java</sourceDirectory>
        <resources>
          <resource>
            <directory>java</directory>
          </resource>
        </resources>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3</version>
            <configuration>
              <encoding>UTF-8</encoding>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
    
      <dependencies>
        <dependency>
          <groupId>org.apache.ant</groupId>
          <artifactId>ant</artifactId>
          <version>1.10.1</version>
        </dependency>
        <dependency>
          <groupId>org.apache.ant</groupId>
          <artifactId>ant-apache-log4j</artifactId>
          <version>1.9.5</version>
        </dependency>
        <dependency>
          <groupId>org.apache.ant</groupId>
          <artifactId>ant-commons-logging</artifactId>
          <version>1.9.5</version>
        </dependency>
        <dependency>
          <groupId>javax.xml.rpc</groupId>
          <artifactId>javax.xml.rpc-api</artifactId>
          <version>1.1</version>
        </dependency>
        <dependency>
          <groupId>wsdl4j</groupId>
          <artifactId>wsdl4j</artifactId>
          <version>1.6.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.eclipse.jdt/org.eclipse.jdt.core -->
        <dependency>
          <groupId>org.eclipse.jdt</groupId>
          <artifactId>core</artifactId>
          <version>3.26.0</version>
          <scope>system</scope>
          <systemPath>${basedir}/lib/org.eclipse.jdt.core-3.26.0.jar</systemPath>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.easymock</groupId>
          <artifactId>easymock</artifactId>
          <version>4.0.2</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>biz.aQute.bnd</groupId>
          <artifactId>biz.aQute.bndlib</artifactId>
          <version>5.2.0</version>
          <scope>provided</scope>
        </dependency>
    
      </dependencies>
    </project>
  • 在idea中创建一个空项目,并把tomcat源码项目导入到这个idea项目中
  • 创建一个Application启动向,并指向 org.apache.catalina.startup.Bootstrap
  • 在启动项的jvm参数中添加如下内容
  • -Dcatalina.home=/Users/dongzhang/develop/workspace/echo20222022/open_src/apache-tomcat-9.0.65-src/home
    -Dcatalina.base=/Users/dongzhang/develop/workspace/echo20222022/open_src/apache-tomcat-9.0.65-src/home
    -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
    -Djava.util.logging.config.file=/Users/dongzhang/develop/workspace/echo20222022/open_src/apache-tomcat-9.0.65-src/home/conf/logging.properties
    -Dfile.encoding=UTF-8
  • 在ContextConfig中加入如下代码

核心类

上图中的核心类都是Tomcat中的核心组件的抽象,在LifeCycle这个体系下,整体上采用了模板方法设计模式,以init方法为入口来实现对各个组件的初始化工作。

初始化流程

BootStrap.java

public static void main(String args[]) { 
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.init();
    daemon = bootstrap;
    //初始化
    daemon.load(args);
    //启动
    daemon.start();
}

private void load(String[] arguments) throws Exception { 
    String methodName = "load";
    Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    //调用Catalina.load方法
    method.invoke(catalinaDaemon, param);
}

Catalina.java

public void load() {
    // 解析server.xml
    parseServerXml(true);
    //获取Server实例
    Server s = getServer();

    // 将System.out重定向到日志输出
    initStreams();

    getServer().init();
}
StandardServer.java

从这里开始,大部分的组件初始都采用了基于LifeCycle的模板方法设计模式。

LifeCyclie.java
public interface Lifecycle { 
    public void init() throws LifecycleException ;
}

@Override
public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        }
}

@Override
protected void initInternal() throws LifecycleException {
        // 初始化公共线程连接池
        reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
        register(utilityExecutor, "type=UtilityExecutor");

        //初始化通用字符串缓存
        onameStringCache = register(new StringCache(), "type=StringCache");

        // 初始化命名服务中的资源
        globalNamingResources.init();
        //初始化Service
        for (Service service : services) {
            service.init();
}


StandardService.java

@Override
protected void initInternal() throws LifecycleException {

        if (engine != null) {
            //初始化Engine
            engine.init();
        }

        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            //初始化线程池组件
            executor.init();
        }

        //初始化Listener
        mapperListener.init();

        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                //初始化Connector
                connector.init();
            }
        }


}

Connector.java

public Connector(String protocol) {
        boolean apr = AprStatus.getUseAprConnector() && AprStatus.isInstanceCreated()
                && AprLifecycleListener.isAprAvailable();
        //初始化ProtocolHandler
        ProtocolHandler p = null;
        try {
            p = ProtocolHandler.create(protocol, apr);
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        }
        if (p != null) {
            protocolHandler = p;
            protocolHandlerClassName = protocolHandler.getClass().getName();
        } else {
            protocolHandler = null;
            protocolHandlerClassName = protocol;
        }
        // Default for Connector depends on this system property
        setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}


protected void initInternal() throws LifecycleException { 

    // 初始化CoyoteAdapter
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    if (service != null) {
      //获取连接池,并设置给ProtocolHander,用来实现Reactor I/O
      protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
    }
    //执行初始化
    protocolHandler.init();
}
AbstractHttp11Protocol.java
    public void init() throws Exception {
        //初始化EndPoint
        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length()-1));
        endpoint.setDomain(domain);

        endpoint.init();

}

AbstractEndpoint.java

public final void init() throws Exception {
        if (bindOnInit) {
            //绑定Socket端口
            bindWithCleanup();
            bindState = BindState.BOUND_ON_INIT;
        }

}

private void bindWithCleanup() throws Exception {
     bind();
}

NioEndPoint.java

    @Override
    public void bind() throws Exception {
        initServerSocket();

        setStopLatch(new CountDownLatch(1));

        // Initialize SSL if needed
        initialiseSsl();
    }

protected void initServerSocket() throws Exception {
    //在指定端口上绑定socket监听
    serverSock = ServerSocketChannel.open();
    socketProperties.setProperties(serverSock.socket());
    InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
    serverSock.bind(addr, getAcceptCount());
    serverSock.configureBlocking(true);

}

到此初始化工作就结束了,Host、Context和Wrapper的初始是在启动流程中进行的,总体上来说,初始工作处理做一些属性、参数、配置的初始,最主要的工作就是启动Tomcat在指定端口上的监听,每一个Connector配置都对应一个Socket。

启动流程

Tomcat的启动时在初始化工作完成后进行的,入口在Bootstrap.start()方法。

Bootstrap.java

public void start() throws Exception { 
        //调用Catalina的start方法
        Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
        method.invoke(catalinaDaemon, (Object [])null);
}

Catalina.java

public void start() {
    //启动Server
    getServer().start();
    Runtime.getRuntime().addShutdownHook(shutdownHook);
    //如果配置了shutdown端口,则会启动一个socket来监听设置端口,并处理shutdown命令
    await();
}

StandardServer.java

LifeCycleBase.java
@Override
public final synchronized void start() throws LifecycleException { 
    startInternal();
}

StandardServer.java
@Override
protected void startInternal() throws LifecycleException { 
    globalNamingResources.start();
    //初始Service
    for (Service service : services) {
       service.start();
    }
}

StandardService.java

@Override
protected void startInternal() throws LifecycleException { 
    //启动Engin
    engine.start();
    //启动Executor
    for (Executor executor: executors) {
       executor.start();
    }
    mapperListener.start();
    //启动Connector
    for (Connector connector: connectors) {
         // If it has already failed, don't try and start it
         if (connector.getState() != LifecycleState.FAILED) {
            connector.start();
         }
    }
}

StandardEngine.java

StandardEngin.java
@Override
protected synchronized void startInternal() throws LifecycleException {
    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
       results.add(startStopExecutor.submit(new StartChild(child)));
    }
}

Connector.java

@Override
protected void startInternal() throws LifecycleException {
    protocolHandler.start();
}

AbstractProtocol.java

@Override
public void start() throws Exception {
    endpoint.start();
}

AbstractEndpoint.java

    public final void start() throws Exception {
        if (bindState == BindState.UNBOUND) {
            bindWithCleanup();
            bindState = BindState.BOUND_ON_START;
        }
        startInternal();
    }

NioEndpoint.java

    @Override
    public void startInternal() throws Exception {

            // 启动一个从Channel中拉取事件的线程,这是一个单线程

            //Poller执行的是从缓存里拿到一个Socket交给线程池
            poller = new Poller();
            Thread pollerThread = new Thread(poller, getName() + "-Poller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
            
            //这里启动的Acceptor接受客户端的连接
            startAcceptorThread();

}

protected void startAcceptorThread() {
        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();
}


Acceptor.java
public void run() { 
    socket = endpoint.serverSocketAccept();
}


@Override
protected SocketChannel serverSocketAccept() throws Exception {
        //这里拿到一个真正的客户端连接
        SocketChannel result = serverSock.accept();

        // Bug does not affect Windows. Skip the check on that platform.
        if (!JrePlatform.IS_WINDOWS) {
            SocketAddress currentRemoteAddress = result.getRemoteAddress();
            long currentNanoTime = System.nanoTime();
            if (currentRemoteAddress.equals(previousAcceptedSocketRemoteAddress) &&
                    currentNanoTime - previousAcceptedSocketNanoTime < 1000) {
                throw new IOException(sm.getString("endpoint.err.duplicateAccept"));
            }
            previousAcceptedSocketRemoteAddress = currentRemoteAddress;
            previousAcceptedSocketNanoTime = currentNanoTime;
        }

        return result;
}

请求处理流程

Tomcat请求的第一步是接受客户端连接,然后将连接交给容器处理,在8.5版本之后,tomcat已经移除了BIO模型,支持NIO、NIO2、APR三种类型的IO模型,以NIO模型为了来看一下Tomcat的线程模型:

 下面从源码的角度来看一下具体的请求处理流程。又有请求接收的入口是Endpoint,所以开始出在NioEndPoint中。

Acceptor.java

    @SuppressWarnings("deprecation")
    @Override
    public void run() {  

                    U socket = null;
             
                    // Accept the next incoming connection from the server
                    // socket
                    //这里调用NioEndPoint的方法,拿到一个客户端连接SocketChanal
                    socket = endpoint.serverSocketAccept();
                    
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Configure the socket
                    if (!stopCalled && !endpoint.isPaused()) {
                        // setSocketOptions() will hand the socket off to
                        // an appropriate processor if successful
                        //这里通过调用setSocketOptions,把这个客户端连接socket加入到
                        //Pollor对应的缓存中
                        if (!endpoint.setSocketOptions(socket)) {
                            endpoint.closeSocket(socket);
                        }
                    } else {
                        endpoint.destroySocket(socket);
                    }
    }

NioEndpoint.java

//获取客户端连接SocketChanal
@Override
protected SocketChannel serverSocketAccept() throws Exception {
        SocketChannel result = serverSock.accept();

        // Bug does not affect Windows. Skip the check on that platform.
        if (!JrePlatform.IS_WINDOWS) {
            SocketAddress currentRemoteAddress = result.getRemoteAddress();
            long currentNanoTime = System.nanoTime();
            if (currentRemoteAddress.equals(previousAcceptedSocketRemoteAddress) &&
                    currentNanoTime - previousAcceptedSocketNanoTime < 1000) {
                throw new IOException(sm.getString("endpoint.err.duplicateAccept"));
            }
            previousAcceptedSocketRemoteAddress = currentRemoteAddress;
            previousAcceptedSocketNanoTime = currentNanoTime;
        }

        return result;
}

//将客户端的SocketChanal加入到Poller缓存
@Override
protected boolean setSocketOptions(SocketChannel socket) {
            //创建了一个包装类
            NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
            channel.reset(socket, newWrapper);
            connections.put(socket, newWrapper);
            socketWrapper = newWrapper;

            // Set socket properties
            // Disable blocking, polling will be used
            socket.configureBlocking(false);
            if (getUnixDomainSocketPath() == null) {
                socketProperties.setProperties(socket.socket());
            }

            socketWrapper.setReadTimeout(getConnectionTimeout());
            socketWrapper.setWriteTimeout(getConnectionTimeout());
            socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            //调用Pollor的register方法,加入到Poller的缓存中
            poller.register(socketWrapper);
}


public void register(final NioSocketWrapper socketWrapper) {
            socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
            //构造一个Event对象,加入到缓存
            addEvent(pollerEvent);
}


private void addEvent(PollerEvent event) {
            events.offer(event);
            if (wakeupCounter.incrementAndGet() == 0) {
                selector.wakeup();
            }
}

Poller.java

@Override
public void run() { 
    将Chanal注册到Selector上
    hasEvents = events();
    //遍历事件
    Iterator<SelectionKey> iterator =
                    keyCount > 0 ? selector.selectedKeys().iterator() : null;
    while (iterator != null && iterator.hasNext()) {
        SelectionKey sk = iterator.next();
        iterator.remove();
        NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
        // Attachment may be null if another thread has called
        // cancelledKey()
        if (socketWrapper != null) {
            //开始处理
            processKey(sk, socketWrapper);
        }
    }

}


protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
    processSocket(socketWrapper, SocketEvent.OPEN_READ, true)
}


public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
   
    //这里的SocketProcessorBase 是一个Runnable,最终线程池会执行它的run方法
    SocketProcessorBase<S> sc =  processorCache.pop();
    Executor executor = getExecutor();
    if (dispatch && executor != null) {
        //交给线程池处理
       executor.execute(sc);
    } else {
      sc.run();
    }
}
SocketProcessorBase.java
@Override
public final void run() { 

    doRun();
}

NioEndPoint.SocketProcessor.java
@Override
protected void doRun() { 
    //吧socketWrapper交给Processer处理
    SocketState state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
}

AbstractProtocol.java

@SuppressWarnings("deprecation")
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
    //拿到Processor
    Processor processor = (Processor) wrapper.takeCurrentProcessor();
    //通过Processor来处理
    SocketState state = processor.process(wrapper, status);
}

AbstractProcessorLight.java

@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status) {
    state = service(socketWrapper);
}

Http11Processor.java
//构建Request、Response,调用Adapter
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper) {
    getAdapter().service(request, response);
}

CyoteAdapter.java

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception { 

    // Calling the container
    //这里拿到Service实例,把请求叫给Engin进行处理            
    connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
}

这样,处理流程就从Connector来到了Engine,也就来到了真正的Servlet容器层,

由于Engine、Host和Context是Container,所以都采用了Pipline模式,增加了自定义Valve的入口(在server.xml中这三个标签中都可以配置Valve)。调用从Adapter来到Engine后,首先会来到StandardEngineValue。 

StandardEngineValve.java
@Override
public final void invoke(Request request, Response response) {

    Host host = request.getHost();
    //如果通过Engine没有找到对应的host,就返回404
    if (host == null) {
        if (!response.isError()) {
                response.sendError(404);
        }
        return;
    }
    //这里拿到Host对应的Pipline的第一个Valve,也就是StandardHostValve
    host.getPipeline().getFirst().invoke(request, response);
}
StandardHostValve.java
@Override
public final void invoke(Request request, Response response)
        throws IOException, ServletException { 
    //尝试找到对应的Context,也就是项目,如果没有,还是会返回404
    Context context = request.getContext();
    if (context == null) {
        // Don't overwrite an existing error
        if (!response.isError()) {
            response.sendError(404);
        }
        return;
    }
    向下找,找到Context Pipline的第一个Valve,也就是StandardContextValve
    context.getPipeline().getFirst().invoke(request, response);
}
StandardContextValve.java
@Override
public final void invoke(Request request, Response response)
        throws IOException, ServletException { 

    // Select the Wrapper to be used for this Request
    Wrapper wrapper = request.getWrapper();
    //继续向下,调用Wrapper的Pipline,最终拿到StandardWrapperValve
    wrapper.getPipeline().getFirst().invoke(request, response);
}
StandardWrapperValve.java

在这里就能够找到对应的Servlet实例了

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

    StandardWrapper wrapper = (StandardWrapper) getContainer();
    //从Wrapper中拿到Servlet实例    
    servlet = wrapper.allocate();
    //构建FilterChain,包含了Filter和Servlet
    ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
    //这里调用filterChain,即调用Filter和Servlet
    filterChain.doFilter(request.getRequest(),response.getResponse());
}

ApplicationFilterChain.java

@Override
public void doFilter(ServletRequest request, ServletResponse response){
    final ServletRequest req = request;
    final ServletResponse res = response;
    internalDoFilter(request,response);
}


private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
    ApplicationFilterConfig filterConfig = filters[pos++];
    Filter filter = filterConfig.getFilter();
    //调用Filter
    filter.doFilter(request, response, this);
    //调用Servlet
    servlet.service(request, response);
}

再往下走,就来到了Servlet规范中的HttpServlet

HttpServlet.java

@Override
public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        service(request, response);
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException { 
    //这里根据请求方式调用Servlet中的doGet或doPost等
}

类加载机制

由于在Tomcat中会部署多个项目,而且在每个项目中很有可能存在同样的依赖包或自定义类,为了将每个项目对应的类对象隔离开来,Tomcat并没有直接采用Java的双亲委派加载机制,因为双亲委派模型无法同时加载相同的类对象,所以Tomcat自己实现了一个WebappClassLoader,在使用上,每个web项目都对应一个WebAppClassloader的实例,这个类加载器负责加载web应用目录下的lib包和业务类,而java本身的类还是会由java自带的类加载器进行加载。

Tomcat优化

  • 禁用ajp Connector;
  • 为Connector配置线程池(具体参数根据性能要求和硬件条件进行调整);
  • 现在合适的I/O模型(BIO、NIO、NIO2、APR);
  • 选择合适的jvm 垃圾收集器;
  • 调整新生代的大小(减少gc发生的次数,减少对象gc年龄);
  • 大内存使用G1等高级垃圾收集器;

本文含有隐藏内容,请 开通VIP 后查看