how tomcat works 笔记

1 tomcat是模块化的服务器,由两大模块:connector和container。

connector主要用来处理网络通讯,接收tcp请求,构造httprequest和httpresponse对象,然后传递给container。

容器的主要任务是触发Servlet.service(ServletRequest arg0, ServletResponse arg1),响应用户请求。

connector和container是解耦的,通过接口规范协作。

org.apache.catalina.Container是一个接口,按照范围从大到小,他的实现类有四个,这几个实现类的关系类似于组合模式的树结构。范围最大的是Engine,他表示整个catalina servlet引擎,他包含一个或者多个Host、Context ;Host表示一个虚拟主机,包含一个或多个Context(好比在一个tomacat服务器下部署多个应用);Context表示一个独立的ServletContext(也就是一个应用,对应的部署描述符是web.xml),他包含一个或多个Wrapper(当需要多个不同的servlet的时候,就会引用多个wrapper子容器)。wraper表示web应用中部署描述符定义的一个servlet,他控制这个servlet的生命周期,负责创建和销毁servlet。wraper不能再有子容器。要部署一个web应用,不一定需要所有的容器。

一般来说,一个connector会引用一个容器,这个容器可能是层级结构的。当接受到客户端请求的时候,connector会调用容器的invoke,容器引用一个pipeline,把请求传递给pipeline,、在pipeline里面顺序执行valve链。如果容器是层级结构的,那么父容器的某个valve会根据某种映射选择一个子容器,子容器执行和父容器类似的过程,请求会横向或者纵向的传递下去。例如,当context接受到一个请求时,他会调用自身的pipeline,执行StandardContextValve,这个value会根据规则找到合适的子容器wrapper,再由wrapper处理请求。

何时需要engine呢?当需要监控到整个engine的请求或者使用独立的connector却需要支持多个host的时候;

用host的时机类似。当和web server 如apache结合使用的时候,一般不需要engine或者host,因为连接器会利用web server的功能确定合适的context或者wrapper.



2 http 服务器实现的主体是Connector.start()(基于server socket),connector.CoyoteAdapter.service(Request req, Response res)内部实现http的处理逻辑。 

3 官方网站的学习资料十分重要。不可不看

4

核心类图


从类图结构可以看出,tomcat具有良好的模块化结构,注重针对接口编程,类的设计具有充分的可扩展性。抽象和实现分离,在通信层面支持bio,nio,apr。

5 处理流程

7.0.27版本的http请求处理流程

Connector.initInternal()-》Connector.startInternal()

 -》AbstractProtocol.start()-》AbstractEndpoint.start()此处采用了模板方法,AbstractEndpoint.startInternal()具体实现由子类定义 -》以http11为例子进行推演JIoEndpoint.startInternal()-》AbstractEndpoint.startAcceptorThreads启动接收器-》JIoEndpoint.Acceptor.run()-》当有连接请求进来时处理请求,JIoEndpoint.processSocket(Socket socket),以线程池的方式处理getExecutor().execute(new SocketProcessor(wrapper))-》JIoEndpoint.Handler.process(SocketWrapper<Socket> socket, SocketStatus status),此处handler的具体实现类对应Http11ConnectionHandler extends AbstractConnectionHandler-》

AbstractProtocol.AbstractConnectionHandler.process(SocketWrapper<S> socket, SocketStatus status)对请求进行处理-》AbstractHttp11Processor.process(SocketWrapper<S> socketWrapper)构造http请求和http响应,触发servlet的service-》CoyoteAdapter.service(Request req, Response res),CoyoteAdapter类作为请求处理器-》coyoteAdapter关联着一个org.apache.catalina.connector.Connector,由connector调用 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response),委托容器处理请求(webx的pipeline借鉴此处)-》pipeline执行至StandardWrapperValve.invoke(Request request, Response response)-》ApplicationFilterChain.doFilter(ServletRequest request, ServletResponse response),在这个filter里面进入我们熟悉的Servlet.service(ServletRequest arg0, ServletResponse arg1)过程。

小记,在分析代码过程中state = handler.process(socket,status);,直接在eclipse下面用ctrl+鼠标,没有直接显示出handler的实现类,而实际上是有实现类的,需要先点击到接口里面,在按F4

6 tomcat的通讯层的实现主要在Endpoint,例如JIoEndpoint,在JIoEndpoint.Acceptor接受连接,拿到套接字后转交给线程池,由线程池执行SocketProcessor。

acceptor接受一个请求的处理逻辑

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {

                // Loop if endpoint is paused
                while (paused && running) {
                    state = AcceptorState.PAUSED;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                if (!running) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    Socket socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSocketFactory.acceptSocket(serverSocket);
                    } catch (IOException ioe) {
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Configure the socket
                    if (running && !paused && setSocketOptions(socket)) {
                        // Hand this socket off to an appropriate processor
                        if (!processSocket(socket)) {
                            // Close socket right away
                            closeSocket(socket);
                        }
                    } else {
                        // Close socket right away
                        closeSocket(socket);
                    }
                } catch (IOException x) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), x);
                    }
                } catch (NullPointerException npe) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), npe);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }
            state = AcceptorState.ENDED;
        
 
        // Process the request from this socket
        try {
            SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
            wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
            // During shutdown, executor may be null - avoid NPE
            if (!running) {
                return false;
            }
            getExecutor().execute(new SocketProcessor(wrapper));
        } catch (RejectedExecutionException x) {
            log.warn("Socket processing request was rejected for:"+socket,x);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            log.error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    
 

最后由协议处理器AbstractHttp11Processor.process(SocketWrapper<S> socketWrapper),去进行底层io操作,读取输入流构造http请求。

 RequestInfo rp = request.getRequestProcessor();
        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

        // Setting up the I/O
        setSocketWrapper(socketWrapper);
        getInputBuffer().init(socketWrapper, endpoint);
        getOutputBuffer().init(socketWrapper, endpoint);
 

Http协议的解码由http11.InternalInputBuffer实现

8 为了提供服务器性能,tomcat还提供基于java nio的NioEndpoint,还有基于APR(Apache Portable Runtime)技术,直接和操作系统交互,更好的集成一些现有的本地服务器技术。web 服务器通过ajp协议和应用服务器通信。

参考(jni http://baike.baidu.com/view/1272329.htm;

http://hi.baidu.com/ugo5/blog/item/96710efe34aa7e5cd7887de2.html

http://www.itpub.net/thread-1064918-1-1.html

http://guojuanjun.blog.51cto.com/277646/688559

http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html

9 tomcat使用自定义的classloader加载servlet,是出于安全考虑。如果采用系统类加载器统一加载,那么运行的恶意servlet就可以访问到classpath中所有的类库。而使用自定义class loader加载servlet类就可以做到代码访问的隔离,除非应用允许,否则一个类只能访问到同一命名空间的类(即同一个类加载器加载的class)。

另外一个原因是为了支持动态类加载,例如reload功能,支持热部署。可以感知到web lib下的class文件修改,自动重新加载,这是系统类加载器做不到的。

  To specify certain rules in loading classes. 

  To cache the previously loaded classes. 

  To pre-load classes so they are ready to use.

tomcat控制一个webapp只能访问到它自己的WEB-INF/lib 下面的class,不能访问tomcat所运行的jvm下面的classpath(由系统类加载器加载的)。

它的默认class loader是WebappLoader,由下面步骤完成隔离加载和自动热部署的效果

  Creating a class loader 

  Setting repositories 

  Setting the class path 

  Setting permissions 

  Starting a new thread for auto-reload.

10 servlet依据是否实现SingleThreadModel,分为单线程模型和多线程模型。实现SingleThreadModel(该模型的含义具有一定误导性,已经被废弃)的servlet的,容器会做同步,保证任意时刻只会有一个线程访问servlet(为了保证性能,容器会创建一个servlet实例池)。相反,另一种servlet的,容器将允许多线程访问,如果有需要,业务要自己做好同步。

由于servlet具有两种含义的线程,所以wrapper需要根据自己所表示的servlet的线程安全性,在分配servlet的时候做更多的处理逻辑。

StandardWrapper.allocate()  

 @Override
    public Servlet allocate() throws ServletException {

        // If we are currently unloading this servlet, throw an exception
        if (unloading)
            throw new ServletException
              (sm.getString("standardWrapper.unloading", getName()));

        boolean newInstance = false;
        
        // If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {

            // Load and initialize our instance if necessary
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            if (log.isDebugEnabled())
                                log.debug("Allocating non-STM instance");

                            instance = loadServlet();
                            if (!singleThreadModel) {
                                // For non-STM, increment here to prevent a race
                                // condition with unload. Bug 43683, test case
                                // #3
                                newInstance = true;
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ExceptionUtils.handleThrowable(e);
                            throw new ServletException
                                (sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                }
            }

            if (!instanceInitialized) {
                initServlet(instance);
            }

            if (singleThreadModel) {
                if (newInstance) {
                    // Have to do this outside of the sync above to prevent a
                    // possible deadlock
                    synchronized (instancePool) {
                        instancePool.push(instance);
                        nInstances++;
                    }
                }
            } else {
                if (log.isTraceEnabled())
                    log.trace("  Returning non-STM instance");
                // For new instances, count will have been incremented at the
                // time of creation
                if (!newInstance) {
                    countAllocated.incrementAndGet();
                }
                return (instance);
            }
        }

        synchronized (instancePool) {

            while (countAllocated.get() >= nInstances) {
                // Allocate a new instance if possible, or else wait
                if (nInstances < maxInstances) {
                    try {
                        instancePool.push(loadServlet());
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException
                            (sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
            if (log.isTraceEnabled())
                log.trace("  Returning allocated STM instance");
            countAllocated.incrementAndGet();
            return instancePool.pop();

        }

    }

11 tomcat的启动类位于stratup包下面,有Bootstrap创建一个Catalina实例,并调用Catalina的处理方法。之所以分为2个类,是因为在不同的系统下面,会有多个Bootstrap的实现。bin目录下面的shell程序就是用来启动tomcat.

12 StringManager,当tomcat发生异常行为时,需要记录错误信息。tomcat采用properties文件来维护错误文案,每个package下面都有自己的一个properties文件。当需要获取错误文案时,直接调用StringManager.getManager(String packageName)即可,这边使用了单例模式,每个package下面共享一个manager.

  /**
     * Get the StringManager for a particular package. If a manager for
     * a package already exists, it will be reused, else a new
     * StringManager will be created and returned.
     *
     * @param packageName The package name
     */
    public static final synchronized StringManager getManager(String packageName) {
        StringManager mgr = managers.get(packageName);
        if (mgr == null) {
            mgr = new StringManager(packageName);
            managers.put(packageName, mgr);
        }
        return mgr;
    }
 

猜你喜欢

转载自hill007299.iteye.com/blog/1464471