




已阅读5页,还剩20页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Tomcat的系统架构与设计模式(君山,11/20/2009) 如有错误之处请指出()前言:从标题看上去很大,Tomcat很复杂,不是一篇文章就能说清楚的,就算我想说恐怕我也说不清楚,我主要是想知道Tomcat如何分发请求的,如何处理多用户同时请求的,还有他的容器是如何工作的。这也是一个Web服务器要解决的两个关键问题。这也是我写这个文档的目的。一、 Tomcat总体设计这里所说的Tomcat是以Tomcat5为基础,也兼顾最新的Tomcat6。Tomcat基本的设计思路还是有连贯性的。1. Tomcat总体结构Tomcat的结构复杂,但是Tomcat也非常的模块化,找到了Tomcat最主要的模块其实Tomcat就能被你抓住“七寸”了。下面是Tomcat的总体结构图:图 1.Tomcat的总体结构从上图中可以看出Tomcat的心脏是两个组件:Connector和Container,关于这两个组件将在后面详细介绍。Connector组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计者本身,而且和不同的应用场景十分相关,所以一个Container可以选择对应多个Connector。多个Connector和一个Container就形成了一个Service,Service的概念大家都很熟悉了,有了Service就可以对外提供服务了,但是Service还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个Tomcat 的生命周期由Server控制。1.1 以Service作为“婚姻”我们将Tomcat中Connector、Container和其他一些组件作为一个整体比作一对情侣的话,Connector主要负责对外交流,可以比作为Boy,Container主要处理Connector接受的请求,主要是处理内部事务,可以比作为Girl。那么这个Service就是连接这对男女的结婚证了。是Service将他们连接在一起,共同组成一个家庭。说白了,Service只是在Connector和Container外面多包一层,把他们组装在一起,向外面提供服务,一个Service可以设置多个Connector,但是只能有一个Container容器。这个Service接口的类图如下:图 2. Service 类图从Service接口中定义的方法中可以看出,它主要是为了关联Connector和Container,同时会初始化它下面的其他组件,注意接口中它并没有规定他一定要控制了它下面的组件的生命周期。所有组件的生命周期在一个Lifecycle的接口中控制,这里用到了一个重要的设计模式,关于这个接口将在后面介绍。Tomcat中Service接口的标准实现类是StandardService它不仅实现了Service接口同时还实现了Lifecycle以可以控制它下面的组件的生命周期。StandardService类结构图如下:图 3 StandardService的类结构图从上图中可以看出除了Service接口的方法的实现以及控制组件生命周期的Lifecycle接口的实现,还有几个方法是用于在事件监听的方法的实现,不仅是这个Service组件,Tomcat中其他组件也同样有这几个方法,这也是一个典型的设计模式,将在后面介绍。下面看一下StandardService中主要的几个方法实现的代码,下面是setContainer(Container)和addConnector(Connector)方法的源码:public void setContainer(Container container) Container oldContainer = this.container; if (oldContainer != null) & (oldContainer instanceof Engine) (Engine) oldContainer).setService(null); this.container = container; if (this.container != null) & (this.container instanceof Engine) (Engine) this.container).setService(this); if (started & (this.container != null) & (this.container instanceof Lifecycle) try (Lifecycle) this.container).start(); catch (LifecycleException e) ; synchronized (connectors) for (int i = 0; i connectors.length; i+) connectorsi.setContainer(this.container); if (started & (oldContainer != null) & (oldContainer instanceof Lifecycle) try (Lifecycle) oldContainer).stop(); catch (LifecycleException e) ; support.firePropertyChange(container, oldContainer, this.container);这段代码很简单,其实就是先判断当前的这个Service有没有已经关联了Container,如果已经关联了,那么去掉这个关联关系oldContainer.setService(null)。如果这个oldContainer已经被启动了,结束他的生命周期。然后再替换新的关联、再初始化并开始这个新的Container的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是,修改Container时要将新的Container关联到每个Connector,还好Container和Connector没有双向关联,不然这个关联关系将会很难维护。public void addConnector(Connector connector) synchronized (connectors) connector.setContainer(this.container); connector.setService(this); Connector results = new Connectorconnectors.length + 1; System.arraycopy(connectors, 0, results, 0, connectors.length); resultsconnectors.length = connector; connectors = results; if (initialized) try connector.initialize(); catch (LifecycleException e) e.printStackTrace(System.err); if (started & (connector instanceof Lifecycle) try (Lifecycle) connector).start(); catch (LifecycleException e) ; / Report this property change to interested listeners support.firePropertyChange(connector, null, connector); 上面是addConnector(Connector)方法,这个方法也很简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意Connector用的是数组而不是List集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,他这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象copy到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴。最新的Tomcat6中StandardService也基本没有变化,但是从Tomcat5开始Service、Server和容器类都继承了MBeanRegistration接口,Mbeans的管理更加合理。1.2 以Server为“居”前面说一对情侣因为Service而成为一对夫妻,有了能够组成一个家庭的基本条件,但是他们还要有个实体的家,这是他们在社会上生存之本,有了家他们就可以安心的为人民服务了,一起为社会创造财富了。Server要完成的任务很简单,就是要能够提供一个接口让其他程序能够访问到这个Service集合、同时要维护他所包含的所有Service的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的Service。还有其他的一些次要的任务如,你住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。Server的类结构图如下:图 4 Server的类结构图他的标准实现类StandardServer实现了上面这些方法,同时也实现了Lifecycle、MbeanRegistration两个接口的所有方法,下面主要看一下StandardServer重要的一个方法addService的实现:public void addService(Service service) service.setServer(this); synchronized (services) Service results = new Serviceservices.length + 1; System.arraycopy(services, 0, results, 0, services.length); resultsservices.length = service; services = results; if (initialized) try service.initialize(); catch (LifecycleException e) e.printStackTrace(System.err); if (started & (service instanceof Lifecycle) try (Lifecycle) service).start(); catch (LifecycleException e) ; / Report this property change to interested listeners support.firePropertyChange(service, null, service); 从上面第一句就知道了Service和Server是相互关联的,Server也是和Service管理Connector一样管理Service,也是将Service放在一个数组中,下面的代码也是来管理这个新加进来的Service的生命周期。Tomcat6中也是没有什么变化的。1.3 组件的生命线“Lifecycle”前面一直在说Service和Server在管理它下面组件的生命周期,那它们是如何管理它们的呢?Tomcat中组件的生命周期是通过Lifecycle接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件可以控制Tomcat中所有组件的生命周期,这个最高的组件就是Server,而控制Server的是Startup,也就是你启动和关闭Tomcat。下面是Lifecycle接口的类结构图:图 5 Lifecycle类结构图除了控制生命周期的Start和Stop方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其他的框架中也被使用,如在Spring中。关于这个设计模式会在后面介绍。Lifecycle接口的实现都在其他组件中实现,就像前面中说的,组件的生命周期由包含它的父组件控制,所以他的Start方法自然就是调用它下面的组件的Start方法,Stop方法也是一样。如在Server中Start方法就会调用Service组件的Start方法,Server的Start方法代码如下:public void start() throws LifecycleException / Validate and update our current component state if (started) log.debug(sm.getString(standardServer.start.started); return; / Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; / Start our defined Services synchronized (services) for (int i = 0; i services.length; i+) if (servicesi instanceof Lifecycle) (Lifecycle) servicesi).start(); / Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); 监听的代码会包住Service组件的启动过程,就是简单的循环启动所有Service组件的Start方法,但是所有Service必须要实现Lifecycle接口,这样做会更加灵活。Server的Stop方法代码如下:public void stop() throws LifecycleException / Validate and update our current component state if (!started) return; / Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null); lifecycle.fireLifecycleEvent(STOP_EVENT, null); started = false; / Stop our defined Services for (int i = 0; i services.length; i+) if (servicesi instanceof Lifecycle) (Lifecycle) servicesi).stop(); / Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null); 所要做的事情也和Start方法差不多。2. Connector组件Connector组件是Tomcat中两个核心组件之一,他的主要任务是负责接收浏览器的发过来的tcp连接请求,创建一个Request和Response对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的Request和Response对象传给处理这个请求的线程,处理这个请求的线程就是Container组件要做的事了。由于这个过程比较复杂,大体的流程可以用下面的流程图来解释:图 6 Connector处理一次请求序列图Tomcat5中默认的Connector是Coyote,这个Connector是可以选择替换的。Connector最重要的功能就是接收连接请求然后分配线程让Container来处理这个请求,所以这必然是多线程的,多线程的处理时Connector的核心。Tomcat5将这个过程更加细化,他将Connector划分成Connector、Processor、Protocol,另外Coyote也定义自己的Request和Response对象。下面主要看一下Tomcat4中如何处理多线程的连接请求,先看一下Connector的主要类图:图 7 Connector的主要类图看一下HttpConnector的Start方法:public void start() throws LifecycleException / Validate and update our current state if (started) throw new LifecycleException (sm.getString(httpConnector.alreadyStarted); threadName = HttpConnector + port + ; lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; / Start our background thread threadStart(); / Create the specified minimum number of processors while (curProcessors 0) & (curProcessors = maxProcessors) break; HttpProcessor processor = newProcessor(); recycle(processor); threadStart()执行就会进入等待请求的状态,知道一个请求到来才会激活它继续执行,这个激活时在HttpProcessor的assign方法中,这个方法是代码如下:synchronized void assign(Socket socket) / Wait for the Processor to get the previous Socket while (available) try wait(); catch (InterruptedException e) / Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll(); if (debug = 1) & (socket != null) log( An incoming request is being assigned); 创建HttpProcessor对象是会把available设为false,所以当请求到来时不会进入while循环,将请求的socket赋给当期处理的socket,并将available设为true,当available设为true是HttpProcessor的run方法将被激活将会处理这次请求。Run方法代码如下:public void run() / Process requests until we receive a shutdown signal while (!stopped) / Wait for the next socket to be assigned Socket socket = await(); if (socket = null) continue; / Process the request from this socket try process(socket); catch (Throwable t) log(process.invoke, t); / Finish up this request connector.recycle(this); / Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) threadSync.notifyAll(); 解析socket的过程在process方法中,process方法的代码片段如下:private void process(Socket socket) boolean ok = true; boolean finishResponse = true; SocketInputStream input = null; OutputStream output = null; try input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize(); catch (Exception e) log(process.create, e); ok = false; keepAlive = true; while (!stopped & ok & keepAlive) finishResponse = true; try request.setStream(input); request.setResponse(response); output = socket.getOutputStream(); response.setStream(output); response.setRequest(request); (HttpServletResponse) response.getResponse().setHeader (Server, SERVER_INFO); catch (Exception e) log(process.create, e); ok = false; try if (ok) parseConnection(socket); parseRequest(input, output); if (!request.getRequest().getProtocol() .startsWith(HTTP/0) parseHeaders(input); if (http11) ackRequest(output); if (connector.isChunkingAllowed() response.setAllowChunking(true); 。 try (HttpServletResponse) response).setHeader (Date, FastHttpDateFormat.getCurrentDate(); if (ok) connector.getContainer().invoke(request, response); 。 if (finishResponse) try response.finishResponse(); catch (IOException e) ok = false; catch (Throwable e) log(process.invoke, e); ok = false; try request.finishRequest(); catch (IOException e) ok = false; catch (Throwable e) log(process.invoke, e); ok = false; try if (output != null) output.flush(); catch (IOException e) ok = false; if ( close.equals(response.getHeader(Connection) ) keepAlive = false; status = Constants.PROCESSOR_IDLE; request.recycle(); response.recycle(); try shutdownInput(input); socket.close(); catch (IOException e) ; catch (Throwable e) log(process.invoke, e); socket = null; 当Connector将socket连接封装成request和response对象后接下来的事情就交给Container来处理了。3. Servlet容器“Container”Container是容器的接口,所有子容器都必须实现这个接口,Container容器的设计时典型的责任链的设计模式,他有四个组件构成分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine包含Host,Host包含Context,Context包含Wrapper。通常一个Servlet class 对应一个Wrapper,如果有多个Servlet就可以定义多个Wrapper,如果有多个Wrapper就要定义一个更高的Container了,如Context,Context通常就是对应下面这个配置:3.1 容器的总体设计Context还可以定义在父容器Host中,Host不是必须的,但是要运行war程序,就必须Host的,应为war中必有web.xml文件,这个文件的解析就需要Host了,如果要有多个Host就要定义一个top容器Engine了。Engine没有父容器了,一个Engine代表一个完整的Servlet 引擎。那么这些容器是如何协同工作的呢?先看一下他们之间的类图:图 8 四个容器的类图下面看一下,当Connector接受到一个连接请求时,将请求交给Container,Container是如何处理这个请求的?这四个组件是怎么分工,怎么把请求传给特的子容器的呢?又是如何将最终的请求交给Servlet处理。下面是这个过程的时序图:图 9 Engine和Host处理请求的时序图这里看到了Valve是不是很熟悉,没错webx中也有的Valve,而且webx中的Pipeline和这里的原理也基本是相似的,都是一个管道,Engine和Host都会执行这个Pipeline,你可以在这个管道上增加任意的Valve,Tomcat会挨个执行这些Valve,而且四个组件都会有自己的一套Valve集合,你怎么才能定义自己的Valve呢?在server.xml文件中的相应的组件范围内,如给Engine和Host增加一个Valve如下: 。 。 StandardEngineValve 和StandardHostValve是Engine和Host的默认的Valve,他们是最后一个Valve负责将请求传给他们的子容器,以继续往下执行。前面是Engine和Host容器的请求过程,下面看Context和Wrapper容器时如何处理请求的。下面是时序图:图 10 Context和wrapper的处理请求时序图从Tomcat5开始容器的路由放在了request中,request中保存了,当前请求正在处理的Host、Context和wrapper。3.2 Engine容器Engine容器比较简单,它只定义了一些基本的关联关系,接口类图如下:图 11 Engine接口的类结构他的标准实现类是StandardEngine,这个类注意一点就是Engine没有父容器了,如果调用setParent方法时将会报错。添加子容器也只能是Host类型的,代码如下:public void addChild(Container child) if (!(child instanceof Host) throw new IllegalArgumentException (sm.getString(standardEngine.notHost); super.addChild(child);public void setParent(Container container) throw new IllegalArgumentException (sm.getString(standardEngine.notParent);他的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。3.3 Host容器Host是Engine的子容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的责任就是运行多个应用,他负责安装盒展开这些应用,并且能够标识这个应用以便能够区分它们。它的子容器通常是Context。他除了关联子容器外,还有就是保存一个主机应该有的信息。下面是和Host相关的类图:图 12 Host相关的类图从上图中可以看出除了所有容器都继承的ContainerBase外,StandardHost还实现了Deployer接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个web application。Deployer接口的实现是StandardHostDeployer,这个类中实现的最要的几个方法,Host可以调用这些方法。3.4 Context容器Context代表Servlet的Context。它更能表示成Servlet的容器,他具备了Servlet运行的基本环境,理论上只要有Context就能运行Servlet了。简单的Tomcat可以没有Engine和Host。Context最重要的功能就是管理它里面的Servlet实例,Servlet实例在Context中是以Wrapper出现的,还有一点就是Context如何才能找到正确的Servlet来执行它呢?Tomcat5以前是通过一个Mapper类来管理的,Tomcat5以后这个功能被移到了request中,在前面的时序图中,可以发现获取子容器都是通过request来分配的。Context准备Servlet的运行环境是在Start方法开始的,这个方法的代码片段如下:public synchronized void start() throws LifecycleException 。 if( !initialized ) try i
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《谏太宗十疏书》课件
- 语文教学研修汇报
- 亲戚关系单词课件
- 《诗经·何草不黄》课件
- 亲子健康调理课件
- 亮丽的城市风光课件
- 事故安全培训感受体会课件
- 事业单位财务
- 气体行业的年度工作总结
- 汽车外饰工程师年终总结
- 小学生词性课件
- 佳能闪光灯信号发射器ST-E2中文说明书
- 上海市周浦中学2025年化学高一下期末统考试题含解析
- 易地移民搬迁政策课件
- 无人机产业股权合作收益分配与技术研发协议
- 2025至2030中国环卫行业市场发展现状及竞争格局与投资机会报告
- 反邪教责任管理制度
- 林业产业链延伸与价值提升的实施途径
- 产品打样管理制度
- 儿童注意力课件
- 2025至2030LED显示屏行业发展趋势分析与未来投资战略咨询研究报告
评论
0/150
提交评论