🦊 Tomcat
2022年10月10日
- frame
🦊 Tomcat
1. 概述
1) Tomcat 与其他框架的关系
2) 介绍
- Tomcat 是 Web应用服务器,是一个
Servlet/JSP
容器. - Tomcat 作为 Servlet 容器,负责处理客户请求,把请求传送给 Servlet,并将 Servlet 的响应传送回给客户端
- 术语
- Context:
- 一个 Context 就是一个 Web 应用程序
- Context:
- 文件结构
/bin
- 启动、关闭、其他脚本文件
.sh
文件用于 Unix 系统,.bat
文件用于 Windows 系统,由于 Win32 命令行缺少一些功能,因此文件中还有一些附加文件
/conf
- 配置文件
- 最重要的文件是
server.xml
,它是容器的主要配置文件
/logs
- 默认情况下,日志文件会存放在此处
/webapps
- 是 webapps 的去处
- 启动过程
startup.sh
catalina.sh start
java -jar org.apache.catalina.startup.Bootstrap.main()
- 核心功能
- 处理 Socket 连接,负责网络字节流与 Request & Response 对象的转化
- 加载并管理
Servlet
,处理具体的Request
请求
2. 架构原理
- Server 对应一个 Tomcat 实例
- 一个 Tomcat 实例 默认对应 一个 Service
- 一个 Service 可能有多个连接器,连接不同协议
- 多个连接器对应一个容器,顶层容器就是 Engine
- 一个容器 包含 多个 Host
- 一个 Host 可能有多个 Context 容器
- 一个 Context 容器可能包含多个 Servlet
3. Connector
1) Tomcat IO 模型
- Tomcat 支持的 IO 模型有:
NIO
- 同步非阻塞 IO,通过 Java NIO 类库实现
NIO2
- 异步 IO,通过 JDK 7 最新的 NIO2 类库实现
APR
- 通过 Apache 可移植运行库实现,是 C/C++ 编写的本地库
2) Tomcat 应用层协议
- Tomcat 支持的应用层协议有:
http 1.1
- 大部分 Web 应用采用的协议
http 2
- 可提高 Web 性能的协议
AJP
- 用于和 Web 服务器集成,例 Apache
3) 核心组件
① Endpoint
- 功能:
- 实现 TCP/IP 协议数据读写
- 本质是调用操作系统的 socket 接口
- 重要组成部分:
Acceptor
- 监听 socket 连接请求
- 循环调用 accept() 接收新连接,一旦有新连接到达,accept() 返回一个 Channel 对象,将其交给 Selector 处理
SocketProcessor
- Selector 发现 Channel数组中有某个 Channel 有数据就绪时,生成一个 SocketProcessor 任务对象给 Executor 处理
- 处理 Acceptor 接收到的 socket 请求,实现 Runnable 接口,读取 & 解析请求数据
② Processor
- 功能:
- 接收 Endpoint 的 socket,读取字节流解析成 Tomcat
Request
&Response
对象 - 通过 Adapter 将其提交到 容器 进行处理
- 接收 Endpoint 的 socket,读取字节流解析成 Tomcat
③ Adapter
- 功能:
- 将传入的 Tomcat
Request
对象转换为ServletRequest
,然后才可以传给 容器 - 将容器传入的
ServletResponse
转换为 TomcatResponse
对象,才可进行后续处理
- 将传入的 Tomcat
4) 涉及的设计模式:
- 模板方法
- 定义抽象基类
AbstractProtocol
,不同应用协议有自己的实现方式
- 定义抽象基类
- 适配器模式
- Adapter
4. Container
1) 相关容器
- 一个 Container 只能有 一个 Engine
- 一个 Engine 引擎 可管理 多个 站点 Host
- 一个 站点 Host 可部署 多个 Web 应用 Context
- 一个 Context 可能有 多个 Wrapper
- 一个 Wrapper 代表 一个 Servlet
2) 涉及的设计模式
- 组合模式
- 不同容器之间存在父子关系,因此形成一个树形结构,就是组合模式
- 每个容器都是一个 接口
- 所有容器均实现了 Container 接口,组合模式使得用户对 单容器 & 组合容器 对象的使用具有一致性
- Container 接口部分源码如下:
public interface Container extends Lifecycle {
/**
* 获取 当前节点 的名称
*/
public String getName();
/**
* 设置当前节点 名称
*/
public void setName(String name);
/**
* 获取 父容器
*/
public Container getParent();
/**
* 给当前节点设置 父节点
*/
public void setParent(Container container);
/**
* 给当前节点 添加子类
*/
public void addChild(Container child);
/**
* 通过 名称 查找 子类
*/
public Container findChild(String name);
/**
* 返回 子类列表
*/
public Container[] findChildren();
/**
* 移除某个 子容器
*/
public void removeChild(Container child);
- 观察者模式
- LifeCycle 设置了事件监听器
- Lifecycle 部分源码如下:
public interface Lifecycle {
/**
* 向当前组件中添加 LifecycleEvent 监听器
*/
public void addLifecycleListener(LifecycleListener listener);
/**
* 获取与当前生命周期绑定的 事件监听器集合
*/
public LifecycleListener[] findLifecycleListeners();
/**
* 移除当前组件绑定的 某个事件监听器
*/
public void removeLifecycleListener(LifecycleListener listener);
/**
* 初始化
*/
public void init() throws LifecycleException;
/**
* 启动
*/
public void start() throws LifecycleException;
/**
* 停止
*/
public void stop() throws LifecycleException;
/**
* 销毁
*/
public void destroy() throws LifecycleException;
public LifecycleState getState();
public String getStateName();
public interface SingleUse {
}
}
5. 请求定位 Servlet 的过程
例:客户端访问 url: http://liuxianzhishou.top:9000/log
- 根据
协议 & 端口号
确定Service & Engine
- 协议为 http协议,因此会被 http 连接器接收该连接请求,一个 连接器 属于 某个 Service 组件,因此 Service 确定
- 一个 Service 中除了多个 连接器外,还有 一个 Container 容器,一个 Container 容器只能有 一个 Engine,因此 Engine 也确定
- 根据
域名
确定 Host- 根据域名找到对应的
Host
容器
- 根据域名找到对应的
- 根据
url 路径
确定Context
组件 - 根据
url 路径
确定Wrapper
[Servlet
]
6. Servlet
类图
1) Servlet 接口
- 从中可以看出 Servlet 的生命周期:
init()
初始化service()
响应请求,进行逻辑处理destroy()
销毁
- 获取 Servlet 的 相关配置信息 & 自身信息
public interface Servlet {
/**
* 1. 初始化
*/
public void init(ServletConfig config) throws ServletException;
/**
* 2. 响应请求。
* 此方法仅在 servlet 的 init() 方法成功完成后调用。
* Servlet 通常在可以同时处理多个请求的多线程 Servlet 容器中运行,因此需要注意 并发安全
*/
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
/**
* 3. 由 servlet 容器调用以向 servlet 指示 servlet 正在停止服务。
* 只有在 servlet 的服务方法中的所有线程都退出或经过超时时间之后,才会调用此方法。
* servlet容器调用该方法后,不会再在该servlet上调用service方法。
*/
public void destroy();
/**
* 返回一个 ServletConfig 对象,其中包含此 servlet 的初始化和启动参数。
*/
public ServletConfig getServletConfig();
/**
* 返回有关 servlet 的信息,例如作者、版本和版权。
* 此方法返回的字符串应该是纯文本,而不是任何类型的标记(例如 HTML、XML 等)
*/
public String getServletInfo();
}
2) HttpServlet
- 重写
service()
方法,根据 请求方法名称 是GET
|POST
|DELETE
等进行分发,给具体的函数进行处理service()
也可被子类重写
- 我们要实现自定义 Servlet,就需要 继承该抽象类,重写里面的
doGet
&doPost
等方法
public abstract class HttpServlet extends GenericServlet {
public HttpServlet() {
// NOOP
}
// 重写了 父类 的方法,将请求方法是 GET | POST | DELETE 等进行分发,给具体的函数进行处理
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod(); // 获取 请求的方法名称
// 根据 请求的方法名称 分发给具体的函数 进行处理
if (method.equals(METHOD_GET)) {
// ...
doGet(req, resp); // 执行 doGet() 方法
} else if (method.equals(METHOD_HEAD)) {
// ...
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
// ...
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
// 提供可供子类重写的 doGet 方法
// 常用的就是 doGet & doPost 方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
// doPost
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
}