首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 互联网 >

how tomcat works 札记

2012-10-09 
how tomcat works 笔记1 tomcat是模块化的服务器,由两大模块:connector和container。connector主要用来处理

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.

?

?


how tomcat works 札记


how tomcat works 札记

?

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

?

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

?

4

核心类图


how tomcat works 札记

?

从类图结构可以看出,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;    }
?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

热点排行