下图为 apache-tomcat-8.5.23.zip 在windows解压后的目录:
下面是解压后的一些关键目录:
* /bin - 启动和停止服务等批处理文件. ( *.sh) 文件 (为Unix系统)、 (*.bat) 文件 (for Windows系统)是一个功能性的复制文件. 自从Win32 command-line 开始是一些单一的,缺乏功能的组件, 现在有一些拓展性的功能
* /conf - 配置文件和一些相关的DTD文件. 最重要的是 server.xml. 它是这个容器最主要的配置文件.
* /logs - 日志文件会打印到这里
* /webapps - 这里是你的应用程序部署的地方.
一、Tomcat的组织结构
从最本质上讲,tomcat为一个servlet容器
Tomcat的组织结构
1.Tomcat结构解析:
Tomcat是一个基于组件的服务器,它的构成组件都是可配置的,其中最外层的是
Catalina servlet容器,其他组件按照一定的格式要求配置在这个顶层容器中。 Tomcat的各种组件都是在Tomcat安装目录下的/conf/server.xml文件中配置的
从功能的角度将Tomcat源代码分成5个子模块,分别是:
2.由Server.xml的结构看Tomcat的体系结构:
//顶层类元素,可以包括多个Service
//顶层类元素,可包含一个Engine,多个Connecter
//连接器类元素,代表通信接口
//容器类元素,为特定的Service组件处理客户请求,要包含多个Host
//容器类元素,为特定的虚拟主机组件处理客户请求,可包含多个Context
//容器类元素,为特定的Web应用处理所有的客户请求
实际源码如下:
Tomcat的体系结构:
Tomcat的核心组件:Connector和Container
二、Tomcat Server处理一个HTTP请求的过程
Tomcat Server处理一个HTTP请求的过程:
- 用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得
- Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应
- Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host
- Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context去处理)
- path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类
- 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序
- Context把执行完之后的HttpServletResponse对象返回给Host。
- Host把HttpServletResponse对象返回给Engine。
- Engine把HttpServletResponse对象返回Connector。
- Connector把HttpServletResponse对象返回给客户Browser。
三、Tomcat中重要接口
1.Service 接口
StandardService 中主要的几个方法实现的代码:
StandardService. SetContainer:
后再替换新的关联、再初始化并开始这个新的 Container的生命周期。最后将这个过程通知感兴趣的事件监听程序。
值得注意的地方就是,修改 Container 时要将新的 Container 关联到每个 Connector
public void setContainer(Container container) {
Container oldContainer = this.container;
if ((oldContainer != null) && (oldContainer instanceof Engine))
((Engine) oldContainer).setService(null);
this.container = container;
if ((this.container != null) && (this.container instanceof Engine))
((Engine) this.container).setService(this);
if (started && (this.container != null) && (this.container instanceof Lifecycle)) {
try {
((Lifecycle) this.container).start();
} catch (LifecycleException e) {
;
}
}
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++)
connectors[i].setContainer(this.container);
}
if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {
try {
((Lifecycle) oldContainer).stop();
} catch (LifecycleException e) {
;
}
}
support.firePropertyChange("container", oldContainer, this.container);
}
StandardService. addConnector
首先是设置关联关系,然后是初始化工作,开始新的生命周期
这里值得一提的是:注意 Connector 用的是数组,而不是 List 集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这种方式实现了类似的动态数组的功能
public void addConnector(Connector connector) {
synchronized (connectors) {
connector.setContainer(this.container);
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
//从connectors索引为0开始,长度为connectors.length的数组,复制到数组results中,从索引为0开始复制
results[connectors.length] = connector;
connectors = results;
if (initialized) {
try {
connector.initialize();
} catch (LifecycleException e) {
e.printStackTrace(System.err);
}
}
if (started && (connector instanceof Lifecycle)) {
try {
((Lifecycle) connector).start();
} catch (LifecycleException e) {
;
}
}
support.firePropertyChange("connector", null, connector);
}
}
最新的 Tomcat6 中 StandardService 也基本没有变化,从 Tomcat5 开始 Service、Server 和容器类都继承了 MBeanRegistration 接口,Mbeans 的管理更加合理
2. Server接口
StandardServer.addService
public void addService(Service service) {
service.setServer(this);
synchronized (services) {
Service results[] = new Service[services.length + 1];
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results;
if (initialized) {
try {
service.initialize();
} catch (LifecycleException e) {
e.printStackTrace(System.err);
}
}
if (started && (service instanceof Lifecycle)) {
try {
((Lifecycle) service).start();
} catch (LifecycleException e) {
;
}
}
support.firePropertyChange("service", null, service);
}
}
3. Lifecycle接口
Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法
StandardServer.Start代码如下:
public void start() throws LifecycleException {
if (started) {
log.debug(sm.getString("standardServer.start.started"));
return;
}
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
synchronized (services) {
for (int i = 0; i < services.length; i++) {
if (services[i] instanceof Lifecycle)
((Lifecycle) services[i]).start();
}
}
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
Server 的 Stop 方法代码如下:
public void stop() throws LifecycleException {
if (!started)
return;
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
for (int i = 0; i < services.length; i++) {
if (services[i] instanceof Lifecycle)
((Lifecycle) services[i]).stop();
}
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}
四、Tomcat中的组件
1. connector组件
- Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的
- Connector 最重要的功能就是接收连接请求,然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心
- Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、Processor、Protocol, 另外 Coyote 也定义自己的 Request 和 Response 对象
HttpConnector.Start方法:
public void start() throws LifecycleException {
if (started)
throw new LifecycleException
(sm.getString("httpConnector.alreadyStarted"));
threadName = "HttpConnector[" + port + "]";
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
threadStart();
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
}
threadStart() 执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在 HttpProcessor 的 assign 方法中,这个方法是代码如下 :
HttpProcessor.assign:
synchronized void assign(Socket socket) {
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
this.socket = socket;
available = true;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" An incoming request is being assigned");
}
创建 HttpProcessor 对象是会把 available 设为 false,所以当请求到来时不会进入 while 循环,将请求的 socket 赋给当期处理的 socket,并将 available 设为 true,当 available 设为 true 是 HttpProcessor 的 run 方法将被激活,接下去将会处理这次请求。Run 方法代码如下:
HttpProcessor.Run:
public void run() {
while (!stopped) {
Socket socket = await();
if (socket == null)
continue;
try {
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
connector.recycle(this);
}
synchronized (threadSync) {
threadSync.notifyAll();
}
}
解析 socket 的过程在 process 方法中,process 方法的代码片段如下:
HttpProcessor.process:
private void process(Socket socket) {
boolean ok = true;
boolean finishResponse = true;
SocketInputStream input = null;
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());
} catch (Exception e) {
log("process.create", e);
ok = false;
}
keepAlive = true;
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse())
.setHeader("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e);
ok = false;
}
try {
if (ok) {
parseConnection(socket);
parseRequest(input, output);
if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
parseHeaders(input);
if (http11) {
ackRequest(output);
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
}
。。。。。。
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
。。。。。。
}
try {
shutdownInput(input);
socket.close();
} catch (IOException e) {
;
} catch (Throwable e) {
log("process.invoke", e);
}
socket = null;
}
当 Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了
2. Servlet 容器“Container”
Server.xml:
path="/library"
docBase="D:\projects\library\deploy\target\library.war"
reloadable="true"
/>
Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎
从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保存了当前请求正在处理的 Host、Context 和 wrapper!!!
3. Engine 容器
Engine 接口的标准实现类是 StandardEngine,这个类注意一点就是 Engine 没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也只能是 Host 类型的,代码如下:
StandardEngine. addChild
public void addChild(Container child) {
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
public void setParent(Container container) {
throw new IllegalArgumentException
(sm.getString("standardEngine.notParent"));
}
Engine 容器的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听
4. Host 容器
5. Context 容器
Context 如何才能找到正确的 Servlet 来执行它呢?
Tomcat5 以前是通过一个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中
Context 准备 Servlet 的运行环境是在 Start 方法开始的,这个方法的代码片段如下:
StandardContext.start:
synchronized void start() throws LifecycleException {
………
if( !initialized ) {
try {
init();
} catch( Exception ex ) {
throw new LifecycleException("Error initializaing ", ex);
}
}
………
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
setAvailable(false);
setConfigured(false);
boolean ok = true;
File configBase = getConfigBase();
if (configBase != null) {
if (getConfigFile() == null) {
File file = new File(configBase, getDefaultConfigFile());
setConfigFile(file.getPath());
try {
File appBaseFile = new File(getAppBase());
if (!appBaseFile.isAbsolute()) {
appBaseFile = new File(engineBase(), getAppBase());
}
String appBase = appBaseFile.getCanonicalPath();
String basePath =
(new File(getBasePath())).getCanonicalPath();
if (!basePath.startsWith(appBase)) {
Server server = ServerFactory.getServer();
((StandardServer) server).storeContext(this);
}
} catch (Exception e) {
log.warn("Error storing config file", e);
}
} else {
try {
String canConfigFile = (new File(getConfigFile())).getCanonicalPath();
if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {
File file = new File(configBase, getDefaultConfigFile());
if (copy(new File(canConfigFile), file)) {
setConfigFile(file.getPath());
}
}
} catch (Exception e) {
log.warn("Error setting config file", e);
}
}
}
………
Container children[] = findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof Lifecycle)
((Lifecycle) children[i]).start();
}
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
………
}
我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置:
Server.xml:
path="/library"
docBase="D:\projects\library\deploy\target\library.war"
reloadable="true"
/>
当这个 reloadable 设为 true 时,war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在 StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下:
StandardContext. backgroundProcess:
public void backgroundProcess() {
if (!started) return;
count = (count + 1) % managerChecksFrequency;
if ((getManager() != null) && (count == 0)) {
try {
getManager().backgroundProcess();
} catch ( Exception x ) {
log.warn("Unable to perform background process on manager",x);
}
}
if (getLoader() != null) {
if (reloadable && (getLoader().modified())) {
try {
Thread.currentThread().setContextClassLoader
(StandardContext.class.getClassLoader());
reload();
} finally {
if (getLoader() != null) {
Thread.currentThread().setContextClassLoader
(getLoader().getClassLoader());
}
}
}
if (getLoader() instanceof WebappLoader) {
((WebappLoader) getLoader()).closeJARs(false);
}
}
}
6. Wrapper 容器
五、Tomcat和其他WEB容器的区别
1.Tomcat和物理服务器的区别
本质:硬件,也就是我们经常讲的服务器或者物理机,我们的PC就是一台性能较低的网络服务器,常见的有 云服务器(例如阿里云ECS)等
组成:处理器、硬盘、内存、系统总线等,和通用的计算机架构类似,但是由于需要提供高可靠的服务,因此在处理能力、稳定性、可靠性、安全性、可扩展性、可管理性等方面要求较高
2.详解tomcat 与 nginx,apache的区别及优缺点
Apache
Tomcat:
Nginx:
1)区别:
Apache与Tomcat的比较:
Apache是Web服务器,Tomcat是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行
实际使用中Apache与Tomcat常常是整合使用:
Nginx与Apache比较:
支持动态页面;
支持的模块多,基本涵盖所有应用;
性能稳定,而nginx相对bug较多
两者优缺点比较:
3.总结: