引言
Tomcat是一个流行的servlet容器,对于开发人员来说整体和容器打交道有必要花一些时间爱你了解其内部结构。本文将从一下几个方面来剖析其内部结构。
- 整体结构
- 连接器
- 初始化过程
- 如何处理一个请求
- 初始化过程
- 容器
- Session管理
- 设计模式
- Context
- Wrapper
- Session管理
整体结构
首先我们先来看一下Tomcat的整体结构。如下图所示,整个tomcat容器的顶层是server对象,下属多个service。每个service有自己的连接器与容器组合[1]。其中连接器负责处理网络连接,并将包装过的数据流传递给容器。容器最后负责解释servlet,并返回结果。下面我们仔细分析一下各层次的内部结构。
?
图 2.1 tomcat整体结构
?
Server:代表了整个web容器服务,一个server可以包含多个service,并维护service集合,对外提供接口[1]。Server实现了Lifecycle接口,通过start,stop函数管理下属的组件。下面我们来看一些典型的方法,这些方法不仅在server这层中出现,在其下级组件中也频繁出现,所以,有必要好好研究一番。
添加一个service。
Addxxx方法的主要作用是将组件本身和上层组件相连。具体在server中,下层service将自身父对象设为当前server。接下来要判断整个系统是否已经进行过初始化,如果这个方法是在初始化之前调用的,那么不必初始化新加入的service。反之需要主动调用其初始化函数。同理,启动方法也是如此。
Start方法:
Start方法的主要任务是启动组件下属的子组件,至于子组件如何管理启动更下一层的组件,组件本身不加控制。
Initialize方法:
和start方法作用差不多,initialize的主要任务是启动下级组件的初始化工作。
Await方法:
。。。
。。。
Server的主要作用是管理全局组件的启动,初始化以及停止工作。但是和许多组件一样,在完成初期工作后需要进入一个等待状态以节约cup资源,所以需要await方法。Server的await方法是在Bootstrap程序启动并初始化server后,调用server的await方法,使其进入等待状态,等候shutdown命令的到来[2]。
上述几个函数在tomcat的其他组件中广泛采用,虽然细节不尽相同,但是整体的功能是一致的,之后不再赘述。
Service:
一个Service的主要功能是管理属于他的连接器和容器,简单的讲,他就是一个连接器和容器的组合。其中连接器可以有多个,以数组的形式存放,在添加新的连接器时需要重新申请空间。这里效率问题需要商讨。
容器对于service来说只能有一个,之所以这么做的原因是分离系统可能遇到需要变化的部分以及其不变的部分。具体来讲,连接器负责处理网络连接,而网络连接可能需要适配多种情况,比如说https与http,ssl与非ssl。所以可能需要不同的连接器处理不同的协议。相反,容器负责解释servlet,后者是针对特定的规范开发的,不会改变。Service同样实现了Lifecycle接口,和server一样他也通过start,stop方法管理下属的组件。
连接器
连接器的主要功能是解析http请求,并将其中的http信息包装成request和response对象,传递给后续的容器处理。这些信息包括http请求头,参数,cookie等内容。这里需要注意的是tomcat Catalina 的connector支持http1.1的长连接功能,长连接是http1.1中的新特性,在原来的1.0协议中浏览器在发起请求后即可与服务器断开连接,网页中图片等资源需要重新请求,会造成不必要的开销。而长连接指的是服务器与浏览器建立连接后除非浏览器主动关闭连接,否则连接将持续保持,这样就可以避免不必要的连接开销。连接器内部通过自制的线程池处理并发请求,但是通过阅读连接器源码我们会发现他本身是单个后台线程的。那么连接器如何处理并发请求呢?
初始化
实际上,在初始化的时候,连接器先产生一个server socket对象,接着启动自身线程,之后会生成一个HttpProcessor线程池,里面所有的线程都处于挂起状态。初始化完成之后,连接器在自身的run方法内将自己阻塞,等待连接到来。
处理请求
当连接到来的时候,连接器只是负责建立连接,然后将这个连接传递给线程池中某一个被唤醒的HttpProcessor对象处理。HttpProcessor对象负责具体的并发工作[2]。Initialize生成一个server socket:
Start方法主要工作:
Run 负责建立连接,并分配processor处理线程:
HttpProcessor的assign方法:
HttpProcessor的run方法:
HttpProcessor的await方法:
Processor通过一个available变量控制线程的休眠与唤醒,简单来说就是设available的初始值为false,那么在processor的run方法中会调用await先将自己挂起。当连接器调用processor的assign方法时,processor获得socket对象,将available置为true,唤醒线程。此时await会失效,执行process方法。在process中主要完成了两件事,第一,解析http请求并将其包装。第二,将包装后的请求传递给容器,并等待其返回。
处理请求的过程
容器
简单来说容器的主要负责传递请求给servlet,并返回响应。下图是容器整体结构,分为四层,从顶之下依次是engine,host,context,wrapper。与tomcat整体的结构相类似,容器之间的管理也是典型的链式结构。例如说Engine的start函数会启动其下属的所有容器。
?
容器的层次
下面我们简单的看一下各层次代表的功能。
- Engine :表示整个 Catalina Servlet引擎
- Host :表示一个虚拟主机。
- Context :表示一个web app应用
- Wrapper:表示单个servlet
?
容器设计模式(职责链设计模式)
职责链设计模式简单的理解就是一个请求在父对象处理完后,通过调用引用对象的invoke()方法将请求传递给下一级。Tomcat中职责链模式被广泛的应用。从engine到host再到context一直到wrapper都通过一个责任链传递请求,整个容器结构就像一条链子一样被串在一起。
?
?
为了便于维护和扩展,容器间的方法调用统一采用"管道+阀"的模式,也就是上述的职责链设计模式。下面我们具体看个例子:
StandarEngine:
StandardPipeline:
StandardPipelineValveContext:
我们可以看到StandarEngine会调用其管道pipeline中的阀的集合StandardPipelineValveContext,而StandardPipelineValveContext会依次传递请求给其集合内的阀。最后StandardPipelineValveContext会将请求传递给基础阀basic,将请求传递给下层的容器。
Session管理
接下来我们来看一下tomcat的session管理管理功能。总的来说tomcat通过Session管理器来管理建立的Session对象,Session管理器负责创建、更新、销毁Session对象[2]。下面这个代码片段展示了host通过cookie中sessionid来寻找对应的session。
Session管理器的主要方法:
- createSession() - 创建session实例
- getMaxInactiveInterval()、setMaxInactiveInterval() – 获取或设置一个到期时间,单位为秒
- add()、remove() – 从session池中添加、移除session实例
默认的Session存储在服务器的内存中,当服务器被关闭或者内存不足时,需要将session存储到磁盘中,即session的持久化功能。
?
?
在tomcat中,通过store接口规定session的持久化操作规范。
Store接口有两个比较重要的方法
- Save()方法用于将制定的Session对象存储到某种持久性存储器中
- load()方法会从存储器中依据Session对象的标识符价将该session对象载入到内存中
实现这个接口的有两个重要的类,分别是FileStore,主要负责将Session对象存储到某个文件中。另一个是JDBCStore,负责Session对象通过JDBC存入到数据库中。
Context容器
Context的主要特征
- Context实例代表一个具体的Web应用程序,具备servlet运行的基本环境。Context最重要的功能就是管理servlet实例。包含一个或多个Wrapper实例,每个Wrapper表示一个具体的servlet定义
- StandarContext具备url映射功能,即负责寻找url对应的wrapper容器。
- StandardContext支持运行时重载文件,并且具备热部署的能力。
Context的url映射
Context通过一个map来映射url和相对应的wrapper容器,具体是通过StandarContextMaper的map方法中四条规则匹配获取得到的。
先解析得到要匹配的应用名称:
规则一:完全匹配规则
规则二:前缀匹配规则
规则三:扩展名匹配规则
默认匹配规则
Context的重载机制
重载是指当web.xml或者WEB-INF/classes目录下文件被修改过,tomcat会重新扫描目录。StandardContext定义了reloadable 属性来指明该应用程序是否启用了重载功能[3]。Tomcat4中,StandardContext对象使用另一个线程检查WEB-INF目录中的所有类和JAR文件的时间戳。
Standarcontext的run方法加载实现runnable接口的webappLoader:
webappLoader会在主方法中周期扫描context目录,如果发生变化立即通知reload线程重载整个context。
重载分两步,第一步关闭所有wrapper容器,接着新的wrappeer和老的wrappeer一同启动。
?
4.6 Wrapper容器
Wrapper代表一个servlet,它负责管理一个servlet,包括servlet的装载,初始化,执行,以及资源回收。需要注意的是Wrapper是最底层的容器,它底下没有子容器了。在 servlet 第一次被请求的时候, StandardWrapper 加载 servlet 类。它是动态的加载 servlet[2]。
Wrapper的过滤器
过滤器是wrapper的另一个重要机制,web开发者可以设置多个过滤器,每个过滤器中可以装载多个过滤类。通过读取配置文件信息,wrapper会依次加载过滤器,并将请求传递。同时在过滤器的末尾会调用servlet的service方法。
?
StandardWrapper需要将自己的大部分公共方法对servlet程序员隐藏起来。因此,StandardWrapper类将自身包装成门面类StandardWrapperFacade的实例。门面设计模式主要用在一个大的系统中,子系统需要互相通信但又不能将内部过多的信息暴露给其他系统的情况。
?
小结
Tomcat是一个较为复杂的servlet容器,本文只是简单的介绍了其中一些重要的组件及其运行原理,对于内部其他组件,比如说日志系统,类加载器等没有涉及。本文研究的tomcat是4版本的,新的tomcat更为复杂,但是主要的原理应该没有改动。作为一名web开发人员深入了解一下服务器的内部结构是很有必要的。