Tomcat 5源码分析
老话题了,不过经典代码分析总是能学到很多东西。 PS:图都存在google drive里的,如果你看不到,说明你已经被HX!!过些日子把所有的图都移过来~^ ^
目录
代码准备与DEBUG调试配置
总体结构
容器与生命周期管理思想
HTTP(TCP)连接管理详细
Deployer模块详细
资源请求与响应
servlet请求
servlet 请求响应
servlet 加载与管理
静态资源请求
SSL详细
思考与问题
Tomcat5中的设计模式
facade
chain of responsibility
factory
observer
问题
magic number
double check
zombie control
REFERENCES
代码准备与DEBUG调试配置
官方下载地址:http://archive.apache.org/dist/tomcat/tomcat-5/v5.0.28/src/
毕竟太老了(04年的东西),很多jar依赖都下不下来了。建议使用我修正后的source,下载后直接根目录ant即可完成build。为了方便跟踪与调试,bin目录下新建一个debug,后面加上:
set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787
即开启JDPA,全称Java
Platform Debugger Architecture(关于JPDA,这里还有篇论文可以看)同时等待DEBUGER连接后再继续(如果你使用的是我提供的,直接运行debug.bat就可以了)。Eclipse里新建Remote Debug,端口号8787。这样就可以在Eclipse里调试Tomcat 5了。
总体结构
从下至上:
(图3 注:成员未全部列出)
容器与生命周期管理思想
容器是tomcat 5中的重要思想。一个容器是负责处理加工来自外部的请求并将结果返回的模块。所有的容器都实现了Container这个接口,其采用了典型的Compositor模式。成员中其中较重要的是pipeline与resource。一个请求是通过invoke方法到达的,Host、Wrapper、Context都继承ContainerBase。前者依次被后者包含,Tomcat
5 对这几个的默认实现均称为StandardXXX,如StandardEngine。 Invoke方法在ContainerBase中的实现即简单的调用pipeline的invoke,如下:
通过执行pipeline的valve,执行相应的操作。上层数据的递交就是通过pipeline实现的。容器还采用了Observer模式来分发消息,每当容器发生图3所示事件时,都会通知listener。其实不仅是容器,这种设计思想还贯穿于整个设计中。
再来说说生命周期管理。在启动tomcat的时候,我们能从控制台上明显看出,启动分两个步骤:init与start。Tomcat 模块生命周期管理都实现了Lifecycle这个接口,其定义了start()与stop()方法,不过奇怪的是init()在lifecycle中却没有定义,但几本上实现了lifecycle的类都有init()方法(所以我觉得这个也应该算在lifecycle里面的)。因此,当启动时,只要递归的调用init()与start()方法,就可以完成启动,注销时则递归地调用stop()即可。与容器的事件设计思想相同,生命周期的每步也会触发事件消息,只要订阅对应的事件,就可以非常容易地知晓其某阶段的进展。非常方便、简洁!
HTTP(TCP)连接管理详细
HTTP使用的是TCP连接。TCP是网联层协议,是端到端的。Tomcat使用的是最原始的Socket进行连接监听,即java.net.ServerSocket.ServerSocket(int, int)。早些时候Tomcat使用的是一个线程监听,多个工作线程的模式:即一个线程专门负责响应连接请求,再递交给工作线程进行处理。Tomcat
5 后开始使用线程池取代了原有的方案。如下图所示:
早期的方式
新方式
那么单从结构来说,新的方式相对了老方法而言,消减了线程之间不必须的关联关系,使单个请求的处理流程完全独立。同时我们看到,在老方式里,一旦listener线程挂掉,那么整个tomcat也就完完了,这是非常危险的事。
从总体来看TCP连接结构管理涉及这几个类:
TcpWorkerThread 是PoolTcpEndPoint的内联类,继承了ThreadWithAttribute接口。可以理解成一个Runable的类。PoolTcpEndPoint里的ThreadPool执行的就是TcpWorkThread任务。启动时,PoolTcpEndPoint会从ThreadPool里取一个线程来监听端口,一旦有连接进来,它就会从ThreadPool里再取一个继续监听。当一个连接进来并最终到达Container(也就是StandardEngine)来进行对应的处理。先来看看整个请求调用的栈:
大概有这么几个步骤:
直接deny之类的,然后调用adapter.service()交给CoyoteAdapter处理,之后便进入了Container,对应不同的业务处理逻辑。
Deployer模块详细
当start(),init()生命周完成后,便到了deployer起作用的时候了。deploy是由org.apache.catalina.Deployer及其实现完成的。
最关键的是install方法,URL指一war包的地址,String指的是一个context path。这里顺便说一下tomcat对整个URL请求部分的命名,如下图:
Deployer的实现org.apache.catalina.core.StandardHostDeployer,支持jar包、war包及文件夹的形式对,然后就是一些验校,之后便new一个context,加到host里就OK啦~
资源请求与响应
servlet请求
servelt 请求响应
先来看看整个调用的栈情况,接着HTTP(TCP)请求之后,继续跟踪:
需要说明的是,index_jsp这个类是根据jsp页面自动生成的,所以上看去有些古怪^_^
request,(HttpServletResponse) response);终到达servlet。
servlet 加载与管理
分为STM(SingleThreadModel)与非STM,STM使用实例栈来管理。负责生成Servlet实例的类为StandardWrapper,对应的方法为allocate()。代码不长,执行流程图如下:
从上面我们可以比较明确的看出,servlet的加载分两条路线,一条是STM,即一个servlet对应一个线程,类似Spring里的prototype,另一种则是非STM,有点像Singleton,即单例。这里要说的是,Tomcat的servlet加载是支持自定义classLoader的,这里的classLoader来自于Container,前面说过了,Container的作用是管理自己的生态圈,如资源的加载、类的加载以及生命周期管理,所以自定义loader放在那里再适合不过了。
当一次servlet请求完成以后,StandardWrapperValve就会调用ServletWrapper的dellocate来销毁一个servlet,其过程大致就是上面过程的反向,还是分为STM与非STM,非STM只会减少引用量,instance不会销毁,STM就是将serlvet归还给instancePool。
静态资源请求
上面说的是动态servlet的请求过程,那么静态资源又是怎么样响应的呢?那我们知道,HTTP请求实质上就是处理GET,POST,PUT,DELETE四种请求,所以静态资源本质上也是由一个servlet来处理的,当然这个servlet不同于JSP的,他是一种HTTP
Servlet(DefaultServlet.java)。
资源的加载逻辑在org.apache.catalina.servlets.DefaultServlet.getResources()方法中。其主要逻辑就是先试着在servlet context(即你应用的根,如:E:\\jakarta-tomcat-5.0.28-src\\jakarta-tomcat-5\\build\\webapps\\ROOT)里寻找资源,如果没有再在JDNI里寻找,如果仍然失败了,就返回null。
随便来看看Servlet的总的继承结构:
正如前面所说的,所有的jsp页面都会被编译成一个jsp类。以XXX_jsp进行命名。当然这里要列举了一些其它类型的servlet,有兴趣的同学可以去看看。
现在我们来请求http://localhost:8080/tomcat.gif
来看看其调用栈:
开始仍然是一路请求到wrapper容器,之后到defaultServelt,再到getResource。总之,从下往上看,非常清楚的。DefaultServer获取资源的方式有根据JNDI与servletContext两种。
SSL详细
SSL即“安全套接字层(Scure Sockets Layer)”,其是这种端到端的加密技术,是“非对称”与“对称加密”的结合(因为对称加密运算少,后详),属于“传输层安全协议”即TLS(Transport Layer Secure),现在普遍使用的是SSL
3.0版本。建立SSL需要一个handshake过程,总的说来有这么几个步骤:
6101。现在我们来看看tomcat里是怎么处理SSL连接请求的。
要使用SSL首先Tomcat需要配置成支持SSL,详见这里,这样在创建ServerSocket的时候便创建的是com.sun.net.ssl.internal.ssl.SSLSocketImpl
而不是前说的普通的java.net.ServerSocket.ServerSocket(int, int),之后便用在TcpWorkerThread处理请求时调用其handshake()方法进行上图所描述的协议握手。SSL密文的加解密也是在SSLSocketImpl里进行的,上图已经说明,SSL在session数据传输时使用的是对称加密,不过jsse包并不开源,所以我们无法得知其具体使用的是什么样的对称加密算法(AES、twofish?),不过现在有openJDK,有兴趣的同学的可以去研究一下。之后便到了Http11Processor,这里会根据配置生成一个org.apache.tomcat.util.net.SSLSupport用于向request里注入一些SSL信息,一般不会用。这篇文章主要讲的是tomcat
5,所以想要更深入的理解SSL,可以参考其它文章。
思考与问题
Tomcat5中的设计模式
facade
facade的作用是为复杂的功能集提供一个统一的入口,还要可以将一个object 隐藏起来,只暴露需要的功能。这里我们举例:org.apache.catalina.core.ApplicationContextFacade,其就是对
org.apache.catalina.core.ApplicationContext的mask,比如org.apache.catalina.core.ApplicationContext.getServletContextName()方法,ApplicationContextFacade里就是没有的。另外要说的就是facade里对context的调用全采用的是反射,这样确实可以减少耦合性,但本人觉得是没有什么必须的,因为反射会增加代码的复杂程度(当然facade在另一个项目里就另说了)。
chain of responsibility
体现责任链模式的设计最明显的当局各Container容器中的pipeline了,每个valve都是一个负责结点,当此任务完成以后,它又会传递给一个结点。为了使用pipeline的执行能够线程安全,负责valve移动的实质上是org.apache.catalina.ValveContext。其负责记录本次pipeline当前执行结点、总结点数及上下文变量。不过比较古怪的是每个pipeline有个名为basic的valve,当pipeline的valves为空时,这个basic
valve就会执行。
factory
工厂模式是个很简单的模式,到处可见。比如org.apache.tomcat.util.net.ServerSocketFactory,此工厂负责生产java.net.ServerSocket。为了方便,工厂本身又是一个单例,所以这里又会涉及一些同步问题,一般有double
check,staic initialization之类的方便,不过tomcat里却用的是最简单当然也最有效的方法,就是synchronized method :)
observer
这个在前面我们就见得很多了,很多容器的事件钩子都是这样实现的。比如:StandardServer.java。观察者都在listener数组中,通知事件时publisher挨个进行通知,当然这里会有一些同步问题,如下:
我们看到其在递归通知对listerner进行了clone那是因为不这样做可以会出现IndexOutOfBoundry问题,即使采用安全的iterator,也会出现ConcurrentModificationException.
问题
当然金无赤足,人无完人。再好的代码也会有问题,下面就来看看:
magic number
这个是老问题了,其实如果要求不是很严格的话,这个也可以不算个问题。比如:org.apache.coyote.http11.Http11Processor.process(InputStream, OutputStream) 第 775 行与第 787 行。
显然这里如果将400改为HttpStatusEnum.BAD_REQUEST要好很多,但开发者却没有这么做,估计是太懒了吧 ^_^
double check
standardservice 478行
严格来说,只有使用的是1.5JDK及以后的版本,同时将container标记为volatile才能完全的线程安全,不然可能获取到未初始化完全的对象(见《Concurrency In Practice》16.2.4.Double Checked Locking 一节)。
zombie control
这个问题子来源于 http11processor 第745行:
这个就太明显了,就不多说了。
REFERENCES
Tomcat SSL配置及Tomcat CA证书安装
建议继续学习:
- tomcat catalina.out日志切割每天生成一个文件 (阅读:8094)
- Tomcat内存溢出的原因 (阅读:3377)
- tomcat的虚拟目录 (阅读:2780)
- 在tomcat应用中获得原始IP (阅读:2016)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:华黎 来源: 淘宝网综合业务平台团队博客
- 标签: Tomcat
- 发布时间:2012-09-20 13:48:13
- [41] 界面设计速成
- [35] Oracle MTS模式下 进程地址与会话信
- [33] 如何拿下简短的域名
- [32] 视觉调整-设计师 vs. 逻辑
- [32] 程序员技术练级攻略
- [32] IOS安全–浅谈关于IOS加固的几种方法
- [31] 图书馆的世界纪录
- [30] 【社会化设计】自我(self)部分――欢迎区
- [30] android 开发入门
- [27] 读书笔记-壹百度:百度十年千倍的29条法则