模拟实现 Tomcat 的核心模块:NIO,HTTP,容器和集群
如果你想看 Tomcat 源码但又无从入手,不妨从这个项目开始,代码量不多,但包含了 Tomcat 的核心处理流程,并且源码中有相当丰富的注释。相信通过此项目你能了解:
- NIO 基本编程、HTTP 协议的本质、基本的单元测试
- Tomcat 应用部署、自定义类加载器的实现、Servlet 的管理和加载运行以及静态资源的处理和缓存等
- Maven 生成可执行 jar,生成 javadoc,使用 assembly 构建项目,使用 release 插件发布到 git等
文末有源码地址。本文就 NIO 模型、HTTP 协议解析、Digester 工具以及Servlet 容器这些核心模块的设计和实现的难点以及重点进行简单介绍。
1. NIO 服务器的实现
由于非阻塞的特性,NIO 的编写相比于 BIO 很复杂,而原生 NIO 编程就更复杂了,关键是处理好通道和处理器的映射,以及各种状态的管理。此模块的名称是 rxtomcat-net,结构如下:

主要实现了以下功能:
- Acceptor 使用信号量对总连接数进行控制
- Acceptor 和 Poller 使用队列协作完成新连接通道的注册,因为直接注册可能会造成死锁
- Poller 既能通知非阻塞读写事件,也能通知模拟阻塞的读写事件
- Poller 在通知 I/O 事件时,也会模拟 Tomcat 把通道就绪的事件从关注事件集合中移除
- 利用两个用于读和写的 CountDownLatch 实现模拟阻塞
- Poller 还对通道处理超时,通道超时或关闭时会移除它对应的 Processor,防止内存泄露
- 因为是非阻塞,所以当处理中途发现读或写的数据不完整,要再次处理时,需要找到原先的处理器,Handler 内部就是使用 ConcurrentHashMap 保持通道和处理器的映射
- NioChannel 是对 SocketChannel 的封装,主要包含两个 ByteBuffer 用于读和写以及两个模拟阻塞读和写使用的闭锁,以及提供实际的非阻塞和模拟阻塞读写功能
- 为了适配不同协议的处理器定义了一个 Processor 接口
EchoProcessor 是实现的一个回显处理器,它包含一个 main 方法,可直接运行进行测试。
有一点需要注意,从通道读取字节到处理请求都是一个线程,只有在非阻塞读取不完整的请求头数据时,才有可能切换线程。
2. HTTP 协议的解码和编码
完整的实现 HTTP 协议是很复杂的,这里的实现比较简单,模块的名称是 rxtomcat-http,结构如下:

主要实现了以下功能:
- 消息行(请求或响应)的解析和构造,解析时采用有限状态机的方法,这是非阻塞编程常用的手段
- chunked 和 identity 消息体的解析和构造
- 实现 keepAlive 长连接
- 特殊 URL 解码,比如 param=%E5%88%9B+a
- 为容器提供底层 Processor 的回调机制,ActionHook
解析协议麻烦的地方在于处理 TCP 粘包拆包的问题,以及各种缓冲区的清空和重用。在实现时,缓存区大部分使用的是 ByteBuffer。
3. 简单的 Servlet 容器
实现一个简单的 Servlet 容器,模块的名称是 rxtomcat-container,结构如下:

简单起见,只设计了 Context 和 Wrapper 两个容器,主要实现了以下功能:
- Pipeline 和 Valve 的管道处理模型,以及容器 Lifecycle 生命周期的设计
- DefaultServlet 静态资源的处理和缓存
- 根据 web.xml 部署应用,提取 Servlet 和 Filter 及其配置的映射
- 打破双亲委托的类加载器 Loader,实现从 WEB-INF/classes 和 WEB-INF/lib 加载类,以及 class 文件热加载的功能
- 实现 Servlet 的三种 URL 路由规则,以及规范中的 Cookie, HttpSession, FilterChain, HttpServletRequest, HttpServletResponse
- 实现 Session 以及它的管理器 Manager
- 实现了 ServletInputStream 用于支持文件上传的处理
这部分的实现稍微繁琐,也基本复现了 Tomcat 的处理流程,其中唯一有点绕的就是使用 Lifecycle 实现的观察模式,触发特定的生命周期事件,使用特定的类来配置和初始化 Context。
4. 其他工具
模块 rxtomcat-utils 主要是一些工具类:
- 简单实现了 Digester XML 解析工具
- 实现了一个字节数组功能类,主要有字节数组转整形,转十六进制字符串
5. Maven 构建模块
模块 rxtomcat-bootstrap 使用 maven-assembly-plugin 打包发布二进制版本,最终构建生成的项目运行目录结构是:

6. 小结
造轮子确实很费时间,但效果很好。平时写代码,知道原理是什么,但在编写时却无从下手,这就是代码写的少,模仿的少导致的。所以,如果时间充裕,不妨多造造轮子。
- 本文模拟实现的 Tomcat 源码地址是:「github.com/tonwu/rxtomcat」
- 使用的版本是 Tomcat 6.0.53,公众号「顿悟源码」后台回复关键字「Tomcat」可获取带有比较详细中文代码注释的,可直接导入 Eclipse 运行的 Tomcat 工程。
读完一个完整的开源项目,实在太费时间了v_v,后续时间充足的话,计划继续实现集群、异步 Servlet 和 websocket 的代码,欢迎 star 关注
模拟实现 Tomcat 的核心模块:NIO,HTTP,容器和集群的更多相关文章
- Tomcat学习四步走:内核、集群、参数及性能
主题简介: 内核实现原理 分布式集群 生产部署关键参数 性能监控和分析 一.内核实现原理 HTTP Web服务器与浏览器之间以HTTP协议通信,浏览器要访问服务器即向服务器发送HTTP请求报文. 如图 ...
- Spring核心模块:IoC容器介绍
1.IoC容器运用的是控制反转模式. 2.IoC容器负责管理对象之间的依赖关系,并完成对象的注入. 3.在IoC设计中,会将依赖关系注入到特定组件中,其中setter注入和构造器注入是主要的注入方式. ...
- nginx+tomcat+memcached-session-manager组成简单的负载均衡和集群
1.搭建环境 192.168.29.128(luxh-01.com) 安装nginx,参考 http://www.cnblogs.com/luxh/p/4067038.html 192.168.29. ...
- nodejs的mysql模块学习(十)连接池集群配置选项
连接池集群选项 canRetry : 如果true ,连接池集群会在连接失败时尝试连接 默认true removeNodeErrorCount : 如果连接失败,节点的errCount增加.当erro ...
- nodejs的mysql模块学习(九)连接池集群
连接池集群 连接池集群可以提供多个主机连接 创建连接池集群 //创建连接池集群 var poolCluster = mysql.createPoolCluster(); //添加配置 config是一 ...
- apache + tomcat 集群
apache2.2与tomcat集成(可以多个tomcat) 需求概况: 有3个服务: localhost:9091, localhost:9190. localhost:9191分别对应3个tomc ...
- Tomcat 集群
1. 前言 该篇中测试的机器发生了变更,在第一篇中设置的Apache DocumentRoot "d:/deployment"修改为了DocumentRoot d:/clust ...
- 第四篇、Tomcat 集群
1. 前言 该篇中测试的机器发生了变更,在第一篇中设置的Apache DocumentRoot "d:/deployment"修改为了DocumentRoot d:/clust ...
- 用apache和tomcat搭建集群,实现负载均衡
型的企业应用每天都需要承受巨大的访问量,在着巨大访问量的背后有数台服务器支撑着,如果一台服务器崩溃了,那么其他服务器可以使企业应用继续运行,用户对服务器的运作是透明化的,如何实现这种透明化呢?由如下问 ...
随机推荐
- Android TV开发中所有的遥控器按键监听及注意事项,新增home键监听
原文:Android TV开发中所有的遥控器按键监听及注意事项,新增home键监听 简单记录下android 盒子开发遥控器的监听 ,希望能帮到新入门的朋友们 不多说,直接贴代码 public cla ...
- Leetcode 318 Maximum Product of Word Lengths 字符串处理+位运算
先介绍下本题的题意: 在一个字符串组成的数组words中,找出max{Length(words[i]) * Length(words[j]) },其中words[i]和words[j]中没有相同的字母 ...
- SQLite介绍及使用
SQLite,是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了 ...
- OpenCV中基于Haar特征和级联分类器的人脸检测
使用机器学习的方法进行人脸检测的第一步需要训练人脸分类器,这是一个耗时耗力的过程,需要收集大量的正负样本,并且样本质量的好坏对结果影响巨大,如果样本没有处理好,再优秀的机器学习分类算法都是零. 今年3 ...
- pandas 学习(二)—— pandas 下的常用函数
import pandas as pd; 1. 数据处理函数 pd.isnull()/pd.notnull():用于检测缺失数据: 2. 辅助函数 pd.to_datetime() 3. Series ...
- Linux crontab 语法和具体的例子
基本格式 : * * * * * command 分 时 日 月 周 命令 第1列表示分钟1-59 每分钟用*或者 */1表示 第2列表示小时1-23(0表示0点) 第3列表示日期1-31 第4列表示 ...
- springboot 上传图片
1. 创建多层目录 创建多层目录要使用File的mkdirs()方法,其他可以使用mkdir()方法. 2. 文件大小限制 配置文件中,在spring1.4以后要使用 ` spring.http.mu ...
- AngularJS ng-if使用
示例中,根据ng-if指令显示不同任务状态,以及判断任务是否可以操作 <div ng-app="NgifDemoApp" ng-controller="NgifDe ...
- WPF ContextMenu 在MVVM模式中绑定 Command及使用CommandParameter传参
原文:WPF ContextMenu 在MVVM模式中绑定 Command及使用CommandParameter传参 ContextMenu无论定义在.cs或.xaml文件中,都不继承父级的DataC ...
- apache本地服务器的配置流程
安装Apache 一.目的: 1. 能够有一个测试的服务器,不是所有的特殊网络服务都能找到免费的! 二.为什么是 "Apache" 1. 使用最广的 Web 服务器 2. Mac自 ...