Tomcat 是一个 web 应用服务器, 它是对 HTTP 和 Servlet 规范的实现, 简单来说它做了这几件事: 处理 HTTP 协议, 执行 Servlet 和处理网络 I/O. 这里以 6.0.53 版本为例(实现了 HTTP/1.1,Servlet2.5), 研究其基本结构.
关于源码版本, 我使用的是 tomcat6, 因为 7 为了重构有太多的抽象, 看着实在费劲, 6 代码虽有冗余但读起来很直观, 并且低版本也不影响理解 Tomcat 的核心流程.
体系结构
从 server.xml 中就能够看出 Tomcat 各组件的层次结构, 具体结构图如下:
Server: 代表整个容器, 它可能包含一个或多个 Service 和全局 JNDI 资源;
Service: 包含一个或多个 Connector, 这些连接器与一个 Engine 相关联;
Engine: 表示请求处理流水线(pipeline), 它接收所有连接器的请求, 并将响应交给适当的连接器返回给客户端;
Host: 网络名称 (域名) 与 Tomcat 服务器的关联, 默认主机名 localhost, 一个 Engine 可包含多个 Host;
Connector: 处理与客户端的通信, 网络 I/O;
Context: 表示一个 Web 应用程序, 一个 Host 包含多个上下文.
服务器模型
服务器模型(或 I/O 模型), 描述的是 TCP 连接的处理方式, 以及 Socket 读写时线程的状态. Java 里常用的是 BIO 和 NIO, 分别对应同步阻塞和同步非阻塞两种模型, Tomcat 中的 Connector 组件就是对这两种的封装实现.
BIO - 阻塞式
Tomcat 实现了一个一连接一线程的简单服务器模型, 内部采用线程池做了优化, 设计如下:
当 Acceptor 接收到一个 TCP 连接时, 线程池分配一个线程进行处理;
线程调用 read() 方法读取 Socket 输入流中的字节, 此时线程阻塞(Block), 直到收到客户端发送的数据;
收到数据后, 进行解码, 业务处理, 编码, 最后把响应发送到客户端, 关闭连接.
由此可以看出, 合理的分配线程池大小可以一定程度上提高系统的并发能力.
NIO - 非阻塞
BIO 的缺点在于不管当前连接有没有数据传输, 它始终阻塞占用线程池内的一个线程, 而 NIO 的处理方式是若通道无数据可读取, 此时线程不阻塞直接返回, 可用于处理其他连接, 提高了线程利用率. 那怎么知道什么时候处理数据的读写呢? 当通道可读或可写时, 内核会通知用户程序进行处理.
NIO 的编程比较复杂, 常用的是 Reactor 模式, 它描述了一个利用多路复用 I/O, 基于事件驱动的服务器处理模型,(这里) https://github.com/rmwheel/rw-jdk/tree/master/docs/reactor 基于 Doug Lea 的 Scalable IO in Java 对 Reactor 进行了实现. Tomcat 的设计略有不同, 其设计如下:
Acceptor 以阻塞模式接收 TCP 连接, 然后将连接注册到 Poller 上;
Poller 以非阻塞模式处理 SSL 握手和 HTTP 请求头的读取;
BlockPoller 模拟阻塞处理 HTTP 请求体的读取和发送响应.
值得注意的是, 两类 Poller 都只是负责事件的通知, I/O 操作都是由线程池中的线程完成. 那么, ServerSocketChannel 为什么阻塞? 为什么要模拟阻塞处理请求体和 Servlet 响应? 相关的讨论可参考:
Why Tomcat's Non-Blocking Connector is using a blocking socket? https://stackoverflow.com/questions/23168910/why-tomcats-non-blocking-connector-is-using-a-blocking-socket?rq=1
Getting my head around NIO 'simulated' blocking (trying to) http://tomcat.10.x6.nabble.com/Getting-my-head-around-NIO-simulated-blocking-trying-to-td4996773.html
Servlet API 的实现
Servlet 规范描述了容器如何加载和运行 Servlet, 如和将请求映射到用户配置的 Servlet, 如何处理请求和响应等相关问题. Tomcat 主要实现了以下 API:
ServletConfig :Servlet 名字和初始化参数;
ServletContext : 定义了 Web 应用程序, Servlet 运行的上下文;
ServletRequest : 封装客户端请求;
ServletResponse : 封装服务端响应;
FilterChain : 请求过滤调用链;
FilterConfig : 过滤配置对象;
RequestDispatcher : 转发请求, 将请求转发给 JSP 或另一个 Servlet 处理.
其他如 Servlet,Filter,GenericServlet,HttpServlet 接口或类则由用户程序来实现, 更多详细的介绍请参考规范内容.
小结
Tomcat 架构看着挺简单但做起来难, 难的就是把复杂的问题抽象化, 简单化, 体现到代码上就是如何设计和抽象出类并且优雅的组织在一起, 它作为一个流行的中间件, 其内部代码的实现以及优化手段, 也是值得我们去研究和模仿的.
来源: https://www.cnblogs.com/wskwbog/p/9347653.html