这么简单的 Tomcat 源码解读,不看看吗?

908 阅读4分钟

先了解下 Tomcat 结构

image.png

研读源码之前,让我们先了解一下 ta 的架构组成,使用过tomcat的朋友,应该知道tomcat的conf/server.xml文件。

<!-- server是tomcat的顶级容器,表示整个tomcat服务。它包含至少一个Service,用于提供具体的服务 -->
<Server port="8005" shutdown="SHUTDOWN">
  <!-- Service主要有两个组成部分,Engine(容器/Container)和 Connector(可以有多个,用于接收不同的请求,如http,https) -->
  <Service name="Catalina">
    <!-- Connector:连接器,处理连接相关的事务,通过 socket 将操作系统的连接事件转换成 Request,将servlet的返回转换成通过 socket 写出。 -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- Container:容器,包含了所有servlet,以及处理request并返回Response -->
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

Tomcat 结构说明

  1. server是 Tomcat 的顶级容器,表示整个 Tomcat 服务。它包含至少一个Service,用于提供具体的服务
  2. Service主要有两个组成部分,Engine(容器/Container)和 Connector(可以有多个,用于接收不同的请求,如http,https)
  3. Connector:连接器,处理连接相关的事务,通过 socket 将操作系统的连接事件转换成 Request,将servlet的返回转换成通过 socket 写出
  4. Container:容器,包含了所有servlet,以及处理request并返回Response
    • 4.1. Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
    • 4.2. Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
    • 4.3. Context:代表一个应用程序,webapps下的应用程序。
    • 4.4. Wrapper:每一Wrapper封装着一个Servlet;

Tomcat 启动过程

了解了 Tomcat 的基本结构之后,我们来说说Tomcat是如何启动的;

我们通过Tomcat安装目录下的 bin/startup.sh 脚本启动Tomcat服务器,最终其实是调用Bootstrap.main()方法。main方法中启动容器的关键代码如下。

if (command.equals("start")) {
    daemon.setAwait(true);
    daemon.load(args);
    daemon.start();
    if (null == daemon.getServer()) {
        System.exit(1);
    }
}

daemon.load(args);

我们配置的server.xmldaemon.load(args) 中被解析,将便签按父子关系按个解析并实例化。

其中 Connector 标签设置了tomcat的 链接方式Connector 是如何解析并实例化的?
daemon.load(args)解析server.xml时,会调用 ConnectorCreateRule.begin()方法,我们来看看ta做了什么

public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
    Service svc = (Service)digester.peek();
    Executor ex = null;
    if ( attributes.getValue("executor")!=null ) {
        ex = svc.getExecutor(attributes.getValue("executor"));
    }
    // 关键性代码:初始化了一个Connector,并将解析到的Connector标签中的protocol属性做为入参传入
    Connector con = new Connector(attributes.getValue("protocol"));
    if ( ex != null )  _setExecutor(con,ex);

    digester.push(con);
}

关键性代码Connector con = new Connector(attributes.getValue("protocol")); :初始化了一个Connector,并将解析到的Connector标签中的protocol属性做为入参传入。

让我们再看看 Connector 构造方法做了什么

public Connector(String protocol) {
    setProtocol(protocol);
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        this.protocolHandler = (ProtocolHandler) clazz.getDeclaredConstructor().newInstance();
    } catch (Exception e) {}
    if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
        encodedSolidusHandling = EncodedSolidusHandling.DECODE;
    }
}
----------------------------------------------------------------------------------------------->>>>>>>
public void setProtocol(String protocol) {
    if (AprLifecycleListener.isAprAvailable()) {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.http11.Http11AprProtocol");
        } else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.ajp.AjpAprProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        } else {
            setProtocolHandlerClassName
            ("org.apache.coyote.http11.Http11AprProtocol");
        }
    } else {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.http11.Http11Protocol");
        } else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.ajp.AjpProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        }
    }
}

这个Connector 构造方法,根据我们配置的sever.xml,构造了一个http协议对象。

以我们上面给出示例的 server.xml 为例(protocol="HTTP/1.1"),盖构造方法通过反射调用org.apache.coyote.http11.Http11Protocol 类的构造方法,实例化了该对象。

public Http11Protocol() {
    // 关键类,接收请求的对象和方法都在这个 JIoEndpoint 中
    endpoint = new JIoEndpoint();
    cHandler = new Http11ConnectionHandler(this);
    ((JIoEndpoint) endpoint).setHandler(cHandler);
    setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
    setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}

new JIoEndpoint() 类内部声明一个 Acceptor 类,该类有一个run方法,会在start时被执行。这个放在start方法中再做说明,这里先留个印象。

daemon.start();

如果说 daemon.load(args) 是解析 server.xml 并实例化各对象;那么 daemon.start() 就是 Tomcat 启动的关键所在了;

daemon.start() 通过反射调用 Catalina.start()方法

public void start() {
    // getServer 其实是通过解析server.xml中的server标签,从而设置的 StandardServer
    if (getServer() == null) {
        load();
    }
    // 这里的 start() 实则调用 startInternal();
    // 调用链 依次调用 LifecycleBase的子类
    try {
        getServer().start();
    } catch (LifecycleException e) {}
}

getServer().start(); 该方法调用 LifecycleBase.startInternal() 抽象方法,通过调用链依次调用子类实现的方法。

image.png

其中有一个比较关键的类 Connector,是的没错ta又回来,在 load() 方法中实例化的连接器。ta在这里有发挥了什么神奇的作用呢?

protected void startInternal() throws LifecycleException {
    setState(LifecycleState.STARTING);
    try {
        // 关键代码
        protocolHandler.start();
    } catch (Exception e) {}
    mapperListener.start();
}

protocolHandler对象就是在 Connector 构造器中实例化的类org.apache.coyote.http11.Http11Protocol,这里调用的是ta的父类 AbstractProtocol.start();

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

endpoint 就是我们在Http11Protocol构造器中的JIoEndpoint,先执行JIoEndpoint父类的start方法,然后调用JIoEndpoint.startInternal();

public void startInternal() throws Exception {
    if (!running) {
        running = true;
        paused = false;
        if (getExecutor() == null) {
            createExecutor();
        }
        initializeConnectionLatch();
        // 关键代码
        startAcceptorThreads();
        Thread timeoutThread = new Thread(new AsyncTimeout(),
                getName() + "-AsyncTimeout");
        timeoutThread.setPriority(threadPriority);
        timeoutThread.setDaemon(true);
        timeoutThread.start();
    }
}

startAcceptorThreads 方法,实例化了Acceptor,并执行run方法;

protected final void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new Acceptor[count];
    for (int i = 0; i < count; i++) {
        acceptors[i] = createAcceptor();
        String threadName = getName() + "-Acceptor-" + i;
        acceptors[i].setThreadName(threadName);
        Thread t = new Thread(acceptors[i], threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}

Acceptor的run方法中,通过IO模型,利用socket获取客户端连接并处理请求;

Tomcat 启动过程总结(基于 BIO 配置的Tomcat)

  1. Tomcat通过daemon.load(args)解析server.xml,实例化了Engine(容器/Container)和 Connector;
  2. Tomcat通过daemon.start()启动一个Acceptor的线程,该线程用于接收 操作系统 接收到的socket请求。
  3. 如果没有请求进来,该线程会阻塞等待【这里说的请求是指accept请求】
  4. 如果接受到请求,Tomcat会再开启 SocketProcessor 线程处理请求。

Tomcat 请求处理过程

在Tomcat启动过程中,最后我们发现tomcat启动了一个 Acceptor 线程接收客户端accept请求,接收到accept请求后,tomcat启动了 SocketProcessor 线程处理其他读写请求。

那么 SocketProcessor 中是如何处理读写请求的呢?我们深入到 SocketProcessor 的run方法中一探究竟。

public void run() {
    boolean launch = false;
    synchronized (socket) {
        try {
            SocketState state = SocketState.OPEN;
            try {
                // https 进行 ssl 验证
                serverSocketFactory.handshake(socket.getSocket());
            } catch (Throwable t) {}
            if ((state != SocketState.CLOSED)) {
                // 关键代码块
                if (status == null) {
                    state = handler.process(socket, SocketStatus.OPEN_READ);
                } else {
                    state = handler.process(socket,status);
                }
            }
            ````
}

通过 state = handler.process(socket, SocketStatus.OPEN_READ) 处理read请求
这个handler是什么呢?其实就是我们在 daemon.load(args) 中实例化 Http11Protocol 时这设置的;

image.png

由于Http11Protocol不存在process方法,所以此处调用了ta的父类 AbstractHttp11Processor.process();

因为 AbstractHttp11Processor.process() 方法代码超多,篇幅原因此处不粘贴了...

process 方法中有一关键代码adapter.service(request, response)

service方法中通过connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 依次调用 Value 类的子类【StandardEngineValve -> StandardHostValve -> StandardContextValve -> StandardWrapperValve】的invoke方法;

StandardWrapperValve.incoke() 方法中调用过滤器filterChain.doFilter(request.getRequest(), response.getResponse());

filterChain.doFilter 调用了 ApplicationFilterChain.internalDoFilter() 方法,internalDoFilter中通过调用servlet.service(request, response);

最终调用到HttpServlet.service()方法根据请求中的post/get等请求方式,调用doPost/doGet方法;

Tomcat 请求处理过程总结

SocketProcessor 线程在接收到读写事件时,会解析socket中的数据,并封装成request和response,并通过adapter【CoyoteAdapter】调用service -> StandardEngineValve -> StandardHostValve -> StandardContextValve -> StandardWrapperValve 的 invoke() 方法,在StandardWrapperValve.invoke()方法中调用过滤器【ApplicationFilterChain】,在过滤器中调用 servlet 的 doGet/doPost 等方法

git下载,编译Tomcat源码,这篇文章就很详细了