Tomcat
主要功能是提供HTTP的服务器。底层使用TCP/IP的进行的socket通信。它遵循了Servlet的规范、进行了具体实现,完成servlet容器的管理。
连接:服务器与客户端进行的Socket通信的连接。
请求:客户端向服务器发送数据。
应答:服务器向客户端发送数据。
HTTP协议:服务器与客户端之间交流的一种协议,通过规定好的结构来识别相互的内容。
BIO(blocking io) 阻塞IO,NIO(non-blocking io) 非阻塞IO,AIO(Asynchronous io) 异步IO
一 HTTP请求协议简介
请求包:
METHOD URL?KEY=VAL VERSION
HEAD_KEY1: HEAD_VAL1
HEAD_KEY2: HEAD_VAL2
BODY
GET /user/userInfos?userIds=123,456,789 HTTP/1.1
Token:ASBJ4KF3SEWDSKNL53H
Host:www.ezone.com
Cookie:XXX=xxx;YYY=yyy
METHOD: 请求的方式例如 GET POST ...
HEAD_KEY: 请求头的名称。
HEAD_VAL: 请求头名称对应的值。
每一行使用\r\n分割。
BODY 请求携带的内容。与请求头之间空出一个空行
应答包:
VERSION STATUS MSG
HEAD_KEY1:HEAD_VAL1
HEAD_KEY2:HEAD_VAL2
BODY
HTTP/1.1 200 OK
Content-Type:application/json;charset=UTF-8
Content-Length:23
{"name":"zzz", state:0}
二 JAVA 使用 TCP实现
JAVA 实现中TCP的通信实现。分别为 BIO NIO NIO2(异步非阻塞 AIO的一种)
1 BIO
服务器
// 服务器
ServerSocket server = new ServerSocket(port, backlog);
// 阻塞并接受客户端的连接、成功返回Socket对象。
Socket client = server.accept();
// 向客户端发送数据的流
OutputStream out = client.getOutputStream();
// 接受客户端当数据的流
InputStream in = client.getInputStream();
客户端
// 客户端创建Socket 与服务器进行连接
Socket client = new Socket(host, port);
// 向服务器发送数据的流
OutputStream out = client.getOutputStream();
// 接受服务器数据的流
InputStream in = client.getInputStream();
2 NIO
服务器
// 开启多路复用选择器
Selector selector = Selector.open();
// 创建server socket 通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8888), 1000);
// NIO模式 为非阻塞、设置为非阻塞,否则无法注册。
serverSocketChannel.configureBlocking(false);
// 注册到Selector上、设置选择事件OP_ACCEPT、当有连接请求接入的时候会触发。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 3s 轮训的延迟
selector.select(3000);
// 有触发SelectionKey选择的事件将会通过这个方式得到。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 此处进行消费掉!!
iterator.remove();
// 有客户端连接的请求接入
if (selectionKey.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
SocketChannel client = server.accept();
// 设置为非阻塞才能注册到 Selector
client.configureBlocking(false);
// 将接入的客户端也注册到多路复用上、有可读数据时会得到这SelectionKey
client.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()) {
SocketChannel client = (SocketChannel)selectionKey.channel();
try {
// client.read(..)
// client.write(..)
// client.read(..)
} catch(Exception e) {
selectionKey.cancel();
client.close();
}
}
}
}
客户端:(客户端与服务端注册使用方式一致, 此处主写服务器 客户实现省略...)
3 AIO
// 异步IO使用的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(executorService);
// 创建 Socket 服务器
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);
serverChannel.bind(new InetSocketAddress(8888), 1000);
Object attachment = null; // 全局的一个对象(可以自定义为任意)
// 接受请求,通过异步回调方式得到客户端
server.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>(){
@Override
public void completed(AsynchronousSocketChannel client, Object attachment) {
// 异步IO每次触发事件都需要重新注册。一次注册一次执行。
serverChannel.accept(attachment, this);
ByteBuffer buf = ByteBuffer.allocate(1024);
Object clientAttachment = null;
client.read(buf, clientAttachment, new ReaderHandler(client, buf, attachment));
}
public void failed(Throwable exc, Object attachment) {...}
});
public class ReaderHandler implements CompletionHandler<Integer, Object> {
AsynchronousSocketChannel client;
ByteBuffer buf;
Object attachment;
// 构造省略
public ReaderHandler(....) {....}
public void completed(Integer result, Object clientAttachment) {
buf.flip(); // 转化为读数据
byte[] data = new byte[byteBuffer.remaining()];
byteBuffer.get(data);
System.out.println(new String(data, Charset.forName("UTF-8")));
// 清理buf 可以继续写数据
byteBuffer.clear();
// 读取完毕需要重新继续注册
client.read(buf, clientAttachment, this);
}
}
三 Tomcat 的设计
tomcat 分成两部分:
1 HTTP 服务器的功能,通过Socket(Tcp/Ip) 进行通信。进行http的协议解析与包装
2 Servlet 容器实现
tomcat的生命周期
通过 org.apache.catalina.Lifecycle 接口进行管理。
它维护了tomcat init() start() stop() destroy() 的过程。通过子类一层层调用来完成功能。
tomcat的结构
它的结构可以通过 server.xml 来确认
Xml 的定义与tomcat的组件实现一一对应,其组件均实现了.Lifecycle 接口。
Server 代表一个tomcat 其实现为:org.apache.catalina.core.StandardServer
Server下允许存在多个 Service : org.apache.catalina.core.StandardService
Service 是对外提供的一种业务,通常使用的时候只配置过一个,但tomcat实现了允许配置多个。
Service 下包含了两主要部分:
1 Connector 用于绑定端口、并处理请求与应答的协议,可以配置多个。
2 Engine 是 Servlet核心部分、容器的具体实现、每个业务只允许配置一个。org.apache.catalina.core.StandardEngine
tomcat的服务是多个组件之间的组合、相互关联来完成具体功能。
当Server 调用init()时候,会依次调用其子组件的init()方法
server.init()->service.init() -> (connector/engine).init()
server.start()->service.start() -> (connector/engine).start()
Connector 连接器组件,完成HTTP服务器功能。
ProtocolHandler:
Endpoint进行绑定TCP的连接
Adapter调用Servlet的实现,将请求适配为HttpServletRequest进行传递
Engine组件为最核心的一部分实现
Host:代表一个虚拟主机、name指定当域名为设定值时候使用当前主机配置。
Context:代表当前域名主机下的一个Web应用(包含很多应用业务的Servlet实现)
Wrapper: 包装一个Servlet的实现
如果Host域名不存在则会使用Engine指定的defaultHost="localhost"的虚拟主机
当请求进入当时候,通过Connector 进行接受连接。
ProtocolHandler 初始化了连接当绑定模式。
Adapter 接受请求将服务请求适配成HttpServerRequest 并调用Servlet进行业务执行。
四 tomcat 中的ClassLoader
在Tomcat中,每个应用程序的部署它们虽然允许在一个tomcat服务内,但均有各自的classpath,每个应用程序之间依赖Jar位置相互独立。实际上是因为Tomcat自己实现了应用的classloader。
上图是tomcat启动时所创建的用于启动tomcat的classLoader 初始化。
1 简介tomcat的类加载器
CommonLoader 用于加载通用的类加载,目标位置 tomcat/lib/*.jar
CatalinaLoader 服务器加载的加载器,仅服务器可见,应用程序不可见。
SharedLoader 加载应⽤程序时共享的类加载器。
WebappClassLoader 用来加载web的应用程序,默认从/webproject/WEB-INF/classes 和 /webproject/WEB-INF/lib 加载
Tomcat 有自己的一套类加载顺序。
CommonLoader 作为加载器的父加载器,CommonLoader加载的可以被SharedLoader,CatalinaLoader使用。
WebAppClassLoader 可以从SharedLoader获得加载的类、如果类不存在则从自身位置加载。每个同层的加载器之间又能相互隔离。每个 WebAppClassLoader都是同级关系
Tomcat 没有完全遵循双亲委派模型
双亲委派模型:每个加载器加载先让父加载器加载,如果父加载器均不存在则从自己加载。
WebappClassLoader加载器则是先自己进行加载,加载不了再交给CommonLoader进行双亲委派的方式。
2 自定义类加载器方式
继承ClassLoader重写
// 通过继承 ClassLoader 的方式加载
public class MyClassLoader1 extends ClassLoader{
File classPath;
public MyClassLoader1(File classPath) {
this.classPath = classPath;
// 这里需要处理路径下的*.jar
// 省略代码 classpath 下的 *.Jar
}
// 重写 loadClass
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
String path = name.replace(".", "/") + ".class";
InputStream in = getResourceAsStream(name);
byte[] data = ...;
//.... read 逻辑省略
// 加载当前的 Class
Class<?> aClass = super.defineClass(name, data, 0, data.length);
if (aClass == null) {
aClass = super.loadClass(name, resolve);
}
if (aClass == null) {
throw new ClassNotFoundException(name);
}
return aClass;
}
// 重写 getResource
@Override
public URL getResource(String name) {
return ...;
}
}
继承URLClassLoader重写
// 通过继承URLClassLoader的方式,相对简单
public class MyClassLoader2 extends URLClassLoader {
// URL是*.jar 或 classpath的位置
public MyClassLoader2(URL[] urls) {
super(urls);
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
}
3 tomcat的配置说明
Connector
port:端口,例如8080,默认为0,表示随机一个端口。
address:监听的地址,默认为所有地址。
acceptCount:等待队列的最大长度,所有处理线程被占用,新的请求将放入队列等待。
maxConnections:最大连接数。
maxThreads:最大线程数,如果配置executor共享线程池,该属性被忽略。
protocol:使用连接协议
org.apache.coyote.http11.Http11NioProtocol
org.apache.coyote.http11.Http11Nio2Protocol
redirectPort:接收客户端请求为HTTPS时,转发到这个端口
connectionTimeout:客户端连接,指定的时间没有进行请求则发生连接超时。
enableLookups:Host进行DNS查询获得客户端主机,默认为true,设置为false则不DNS解析;
Engine
<engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"></Host>
</engine>
defaultHost 当请求当Host不存在时候,使用默认值,例如请求www.xx.com,不存在这个域名当虚拟主机,则使用localhost当主机接受。
Host
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/>
name 是这个虚拟主机当域名映射。
appBase 是物理路径
unpackWARs 启动解压WAR
autoDeploy 启动状态更改程序文件是否自动部署
Context
<Host ...><Context path="/" docBase="ROOT"/></Host>
path 是contextPath,URL请求当前缀。
docBase Web项目的物理路径。请求会进入这个web程序执行。
四 tomcat 源码
Tomcat的设计,套娃式架构设计
1. 套娃式架构设计的好处:
便于组件生命周期的管理,每个组件在服务器启动开始都将开始自己的生命周期,他们共同实现一个生命周期定义的接口Lifecycle,几乎模版化的执行过程,调用关系变得清晰。
tomcat启动时从server.xml读取配置便于组装tomcat/server实例,因为xml配置层次与框架层次对应,并容易理解。
子容器更好继承父容器一些配置。
2. 解读源码好处
可以学习优秀的设计,增强开发的经验,和了解优秀的逻辑实现。
提高架构思维,便于深入了解一个框架或项目,对性能调优也有帮助。
3. tomcat 源码导入(maven方式)
下载tomcat8.5源码包,解压。自行创建pom.xml,通过编辑器 maven 导入。这样可以简单的导入并启动tomcat。
4 tomcat启动入口
org.apache.catalina.startup.Bootstrap main(args[])
// 简单介绍局部代码
class org.apache.catalina.startup.Bootstrap {
public static void main(String args[]) {
try{
// 创建 classloader
// commonClassLoader
// catalinaClassLoader
// sharedClassLoader
// 创建 Catalina对象
bootstrap.init();
// ....省略无关代码
// 调用 Catalina load方法, 进行服务器的init调用
// server.init() -> service.init() -> (connector|engine).init()
daemon.load(args);
// 调用 Catalina start方法,进去服务器的start调用
// server.start() -> service.start() -> (connector|engine).start()
daemon.start();
// ... 省略无关代码
} catch (Throwable t) {
// .... 省略无关代码
}
}
}
5 tomcat 容器组件
tomcat 组件的实现均实现 Lifecycle,进行维护生命周期,拥有统一生命周期执行过程。
public interface Lifecycle {
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public void destroy() throws LifecycleException;
}
其组件包含多个子组件组成的一个服务容器。用来实现HTTP请求到Servlet的业务处理。
· 初始化阶段: (可以断点) load() 执行进行跟进。
load()方法实际上是通过反射执行 catalina.load()方法。在 catalina.load方法中进行解析了server.xml文件得到Server的实例。开始进行初始化。
Server 代表这个服务器,包含子组件:Service[]
在Server的Init的实现中可以看到它将所有的Service 进行了初始化
Service 组件中包含了:Connector[] 和 Engine 等组件。
Connector[] 和 Engine 是其中重要的组成。
Connector 负责建立TCP连接、接受客户端请求与请求的协议处理。
Engine 负责Web应用的容器实现、主要是Servlet容器实现。
Connector 接受到的请求会调用 Engine 中Servlet 进行处理,处理应答通过Connector建立的连接返回。
在Service 中分别进行了Engine.init() 和 Connector[i].init();
源码中可以看出 一个Service 允许监听多个端口,和通过多种协议进行处理。