Tomcat概述
1、怎么描述tomcat?
Servlet规范把能够发布和运行Javaweb应用的web服务器称为’Servlet容器’,因此,可以理解为Tomcat就是一个servlet容器。
常见的web服务器(servlet容器)
- webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
- webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
- JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
- Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范 servlet/jsp。开源的,免费的。
2、tomcat作为servlet容器的基本功能:
就是负责接收和解析来自客户的请求,同时把客户的请求传送给响应的servlet,并把servlet的响应结果返回给客户。
3、Servlet规范规定,Servlet容器 接收请求访问特定Servlet的流程
- 客户发出访问特定Servlet的请求
- Servlet容器接收客户请求,解析请求
- Servlet容器创建一个ServletRequest对象,其包含客户所有请求信息。如请求头,请求正文,客户端IP地址等
- Servlet容器创建一个ServletResponse对象
- Servlet容器调用客户请求的Servlet的service()服务方法,并且把ServletRequest对象和ServletResponse对象作为参数传给该方法。
- Servlet从ServletRequest对象获取客户的请求信息
- Servlet利用ServletResponse对象生成响应结果
- Servlet容器把Servlet生成的响应结果发送给客户
4、tomcat的目录结构
5、tomcat处理http服务请求
servlet容器以及servlet接口,是用来实现http服务器和业务类之间的解耦。(servlet接口和servlet容器这一套规范叫做servlet规范)
tomcat按照servlet规范的要求实现了servlet容器,具有HTTP服务器的功能,提供http访问的统一入口。
6、tomcat两个核心组件
连接器 --》对外
容器 --》对内
7、coyote连接器简介
coyote是tomcat连接器框架的名称,是tomcat提供的供客户端访问的外部接口,客户端通过coyote与服务器建立连接,发送请求并接收响应 。
coyote底层封装了底层的网络通信(支持socket请求以及响应的处理),为catalina容器提供了统一的接口,是的catalina容器与具体的请求协议和IO操作方式完全解耦。
coyote将socket输入转换封装成request对象,交给catalina处理,处理完成后,通过coyote提供的response对象将结果写入输出流。
coyote作为独立的模块,只负责具体的协议和IO相关的操作,和servlet规范的实现没有直接关系,coyote生成的response和request 是通过CoyoteAdapter的service方法进一步封装servletReuqest 和 servletResponse
8、coyote中的IO模型和协议
IO模型,自8.5版本起,tomcat传输层移除了对BIO的支持,默认协议是NIO
应用层协议
tomcat为了实现支持多种IO模型和应用层协议,一个容器可以对应多个连接器(如6中的图),单独的容器或者连接器不能对外提供服务,需要组装起来才可以工作,组装后的整体叫做service组件。tomcat支持配置多个service,这样可以实现通过不同端口号来访问同一台机器上部署的不同应用。
9、coyote中的组件
1、endpoint:
通信端点,就是监听通信的端口,是具体的socket接收和发送处理器,endpoint用来实现TCP/IP协议。 Tomcat 并没有EndPoint 接口,而是提供了一个抽象类AbstractEndpoint (NioEndpoint ,Nio2Endpoint ,AprEndpoint 都继承了AbstractEndpoint) ---tomcat10.1.1源码没看到AprEndpoint
在AbstractEndpoint类中,重点关注两个内部类 (Acceptor和SocketProcessor)
1、Acceptor(tomcat10.1.1中这个已经不是内部类了)
Acceptor用于监听socket请求,代码在run()方法中
2、SocketProcessor
SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里 调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor)
2、processer: coyote协议处理接口
简单来说:EndPoint是用来实现TCP/IP协议;Processor用来实现HTTP协议
Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理, Processor是对应用层协议的抽象。
3、ProtocolHandler : Coyote 协议接口
ProtocolHandler: Coyote 协议接口, 通过Endpoint 和 Processor , 实现针对具体协议的处理能力。
Tomcat 按照协议和I/O 提供了6个实现类 :
- AjpNioProtocol
- AjpAprProtocol (tomcat10.1.1中没有看到)
- AjpNio2Protocol
- Http11NioProtocol
- Http11Nio2Protocol
- Http11AprProtocol(tomcat10.1.1中没有看到)
通过Service标签中连接器Connector 的属性 protocol="HTTP/1.1 指定协议名称
<Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector protocol="AJP/1.3" address="::1" port="8009" redirectPort="8443" /> </Service>
4、Adapter
adapter负责将request,response 转换为 servletRequest, servletResponse
调用的是CoyoteAdapter中的service方法,对请求进行解析适配后,调用容器,其中getcontianer()返回的就是Engine对象。
10、tomcat中的容器,catalina概述
Tomcat 本质上就是一款 Servlet 容器, 因此Catalina 才是 Tomcat 的核心 , 其他模块都是为Catalina 提供支撑的。
- 通过Coyote 模块提供链接通信
- Jasper 模块提供JSP引擎
- Naming 提供JNDI 服务
- Juli 提供日志服务。
11、Catalina简介,其组件的职责
Catalina负责管理Server,而Server表示着整个服务器。Server下面有多个服务Service,每个服务都包含着多个连接器组件ConnectorCoyote 实现)和一个容器组件Container。在Tomcat 启动的时候, 会初始化一个Catalina的实例。
12、container容器的结构
Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper。这4种容器不是平行关系,而是父子关系。 Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。
Tomcat 采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件 按照一定的格式要求配置在这个顶层容器中。这些可以在配置文件server.xml中进行配置
13、tomcat如何管理定义的四种容器?
所有容器组件都实现了Container接口,因此组合模 式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的Wrapper,组合容器对象指的是上面的Context、Host或者Engine。
Container接口中有parent/child相关的方法,用于标识父子关系。
此外,Container接口扩展了LifeCycle接口,LifeCycle接口用来统一管理各组件的生命周期。
14、tomcat启动流程
总结:初始化或者启动父组件自身,然后调用子组件。----> 加载Tomcat的配置文件,初始化容器组件,监听对应的端口,准备接受客户端的请求。
启动步骤:
- 启动tomcat , 需要调用 bin/startup.bat (在linux 目录下 , 需要调用 bin/startup.sh) , 在startup.bat 脚本中, 调用了catalina.bat。
- 在catalina.bat 脚本文件中,调用了BootStrap 中的main方法。
- 在BootStrap 的main 方法中调用了 init 方法 , 来创建Catalina 及 初始化类加载器。
- 在BootStrap 的main 方法中调用了 load 方法 , 在其中又调用了Catalina的load方 法。
- 在Catalina 的load 方法中 , 需要进行一些初始化的工作, 并需要构造Digester 对象, 用 于解析 XML。
- 然后在调用后续组件的初始化操作 。。。 加载Tomcat的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求。
15、tomcat中各个组件的默认实现类
接口 | 默认实现 |
---|---|
Server | StanderdServer |
Service | StanderdService |
Engine | StanderdEngine |
Host | StanderdHost |
Context | StanderdContext |
Endpoint | 提供一个抽象类AbstractEndpoint |
Processor | 见图 |
ProtocolHandler | 封装endpoint和processor,实现对具体协议的处理功能 |
16、tomcat的请求处理流程
tomcat中,Mapper组件实现了将用户请求的URL定位到一个Servlet的功能。使得tomcat实现每个请求都可以精准的找到对应的servlet。
Mapper组件工作原理:
Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,你可以想象这些配置信息就是一个多层次的Map。
当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。请你注意,一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。
具体步骤:
- Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket。
- 将连接交给线程池Executor处理,开始执行请求响应任务。
- Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
- Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、Wrapper容器处理请求。
- CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的Request对象和响应对象Response传递到Engine容器中,调用 Pipeline。
- Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的 Valve–StandardEngineValve,负责调用Host容器的Pipeline。
- Host容器的管道开始处理,流程类似,最后执行 Context容器的Pipeline。
- Context容器的管道开始处理,流程类似,最后执行 Wrapper容器的Pipeline。
- Wrapper容器的管道开始处理,流程类似,最后执行 Wrapper容器对应的Servlet对象的处理方法。
在Tomcat中定义了Pipeline 和 Valve 两个接口,Pipeline 用于构建责任链, Valve代表责任链上的每个处理器。Pipeline 中维护了一个基础的Valve,它始终位于Pipeline的末端(最后执行),封装了具体的请求处理和输出响应的过程。当然,我们也可以调用addValve()方法, 为Pipeline 添加其他的Valve, 后添加的Valve 位于基础的Valve之前,并按照添加顺序执行。Pipiline通过获得首个Valve来启动整合链条的执行 。
17、tomcat中的Jasper简介
JSP页面中编写 Java代码,添加第三方的标签库,以及使用EL表达式。但是无论经过何种形式的处理,最终输出到客户端的都是标准的HTML页面(包含js ,css…),并不包含任何的java相关的语法。 故jsp看做是一种运行在服务端的脚本。
服务器如何将 JSP页面转换为HTML页面?
Jasper模块是Tomcat的JSP核心引擎,JSP本质上是一个Servlet。Tomcat使用Jasper对JSP语法进行解析,生成Servlet并生成Class字节码,用户在进行访问jsp时,会访问Servlet,最终将访问的结果直接响应在浏览器端 。另外,在运行的时候,Jasper还会检测JSP文件是否修改,如果修改,则会重新编译JSP文件。
18、JSP的编译方式
tomcat并不会在启动web程序的时候自动编译jsp文件,而是在客户端第一次请求时,才编译需要访问的jsp文件。(如果安装了Apache Ant,则可以使用ant进行预编译)
当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对xml文件进行解析,并读取servlet注册信息。然后,将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化(Tomcat 创建servlet类实例的方法和原理)
Tomcat 在默认的web.xml 中配置了一个**org.apache.jasper.servlet.JspServlet**
,用于处理所有的**.jsp **
或** .jspx **
结尾的请求,该Servlet 实现即是运行时编译的入口。
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
编译过程:
1、调用JspServlet的service方法,里面会获取jsp文件路径,如果界面没有传,会根据配置文件配置的servlet-mapping进行查找(上面xml里面有写)
判断当前请求是否是预编译请求,然后执行serviceJspFile()方法
/
2、 在serviceJspFile()方法中获取JspServletWrapper,然后调用JspServletWrapper的service方法
3 、 若是第一次调用,则需要编译,在JspServletWrapper的service方法中通过调用JspCompilationContext的compile()方法,在调用Compiler的compile()方法,生成Java文件和class文件。
4 、生成文件之后,JspServletWrapper的service方法加载编译并实例化之后的servlet,然后调用其service()方法,这个service方法就是编译生成的service方法。
编译结果:
1、如果在 tomcat/conf/web.xml 中配置了参数scratchdir , 则jsp编译后的结果,就会存储在该目录下。
<init-param> <param-name>scratchdir</param-name> <param-value>D:/tomcat-temp</param-value> </init-param>
2、如果没有配置该选项, 则会将编译后的结果,存储在Tomcat安装目录下的work/Catalina(Engine名称)/localhost(Host名称)/Context名称
3、如果使用的是 IDEA 开发工具集成Tomcat 访问web工程中的jsp , 编译后的结果,存放在 :C:\Users\用户名\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Unnamed_Tomcat8_0\work\Catalina\localhost\jsp_demo\org\apache\jsp
19、Jasper编译文件简介。
分析:
- 编译后的类继承自 org.apache.jasper.runtime.HttpJspBase,是HttpServlet的子类,因此jsp本质上就是一个servlet
- 通过属性 _jspx_dependants 保存了当前JSP页面依赖的资源, 包含引入的外部的JSP页面、导入的标签、标签所在的jar包等,便于后续处理过程中使用(如重新编译检测, 因此它以Map形式保存了每个资源的上次修改时间)。
- 通过属性 _jspx_imports_packages 存放导入的 java 包, 默认导入 javax.servlet ,javax.servlet.http, javax.servlet.jsp 。
- 通过属性 _jspx_imports_classes 存放导入的类, 通过import 指令导入的DateFormat 、SimpleDateFormat 、Date 都会包含在该集合中。_jspx_imports_packages 和 _jspx_imports_classes 属性主要用于配置 EL 引擎上下文。
- 请求处理由方法 _jspService 完成 , 而在父类 HttpJspBase 中的service 方法通过模板方法模式 , 调用了子类的 _jspService 方法。
- _jspService 方法中定义了几个重要的局部变量 : pageContext 、Session、application、config、out、page。由于整个页面的输出有 _jspService 方法完成,因此这些变量和参数会对整个JSP页面生效。 这也是我们为什么可以在JSP页面使用这些变量的原因。
- 指定文档类型的指令 (page) 最终转换为 response.setContentType() 方法调用。
- 对于每一行的静态内容(HTML) , 调用 out.write 输出。
- 对于 <% … %> 中的java 代码 , 将直接转换为 Servlet 类中的代码。 如果在 Java代码中嵌入了静态文件, 则同样调用 out.write 输出。
Tomcat 配置文件
tomcat服务器的主要配置文件都集中在tomcat/conf目录下
1、server.xml
server.xml是tomcat的核心配置文件,包含了servlet容器的所有配置,下面列举一些重要的配置。
1.1 Server
Server是server.xml的根元素,用于创建一个server实例,代码里的默认实现类是StandarServer。
port:tomcat监听的关闭服务器的端口,默认9005,建议改成-1,即关闭该端口
shutdown:关闭服务器的指令字符串,建议修改。
<Server port="9005" shutdown="SHUTDOWN"> 。。。 </Server>
Server内嵌的子元素为:Listener(监听器),GlobalNamingResources(全局命名),Service
1.2 Service
用于创建Service实例,代码默认使用StandarService,默认情况下,tomcat仅仅指定了Service的名称“Catalina”, Service可以内嵌的元素包括
- Listener:为Service添加生命周期监听器
- Executor: 配置Service共享线程池
- Connector: 配置Service包含的连接器
- Engine: 配置Service中连接器对应的servlet容器引擎
1.3 Executor
默认情况下,Service没有添加共享线程池配置,catalina各个组件在用到线程池时会独立创建。
如果想要配置共享线程成,可以在<Service>下添加如下配置
1.4 Connector
Connector用于创建连接器实例,默认情况下,server.xml配置了两个连接器,一个支持HTTP协议,一个支持AJP协议,因此大多数情况下,不需要新增连接器配置,只根据自己需要对已有的连接器进行优化即可。
- port:端口号,Connector用于创建服务端socket并进行监听客户端的请求连接,如果端口号设置为0,则tomcat会随机选用一个可用的端口号给当前Connector使用
- protocol:当前Connector支持的访问协议,默认是HTTP/1.1,并采用自动切换机制,选择一个机遇Java NIO 的连接器或者机遇本地APR的连接器(这个也要看是否含有tomcat的本地库),如果不希望自动切换,可以明确指定协议(如:org.apache.coyote.http11.Http11NioProtocol)
- connectionTimeout:Connector接收链接请求后的等待超时时间,单位为毫秒,-1表示不超时。
- redirectPort:当前Connector不支持SSL请求,接收到的请求符合security-constraint约束,需要SLL传输,catalina会自动将请求重定向到指定的端口。
- excutor:指定共享线程池的名称,也可以通过maxThreads、minSpareThreads等属性配置内部线程池。
- URIEncoding: 用于指定URI的字符编码,tomcat8.x版本后默认是UTF-8。
1.5 Engine
Engine作为servlet引擎的顶级元素,内部可以嵌入:Cluster,Listener,Realm,Valve, Host等;如果在Engine 下配置了Realm,则此配置在当前Engine下的所有Host共享
- name:用于指定Engine的名称,默认是Catalina,这个名称会影响一部分Tomcat的存储路径(如临时文件)
- defaultHost: 默认使用的虚拟主机名称,当客户请求指向的主机无效时,将交由默认的虚拟主机处理,默认为localhost。
1.6 Host
Host元素用于配置一个虚拟主机。如果在Host下配置了Realm,则此配置在当前Host下的所有Context中共享
name: 当前Host通用的网络名,必须与DNS服务器上的注册信息一致,而且,必须要存在一个Host的name与Engine中defaultHost一致。
appBase:当前Host的应用基础目录,当前Host上部署的web应用均在该目录下,默认webapps
unpackWARs: 设置为true,Host在启动时会将appBase目录下的war包解压为目录,设置为false,Host则直接从war包启动
autoDepoly: 控制tomcat是否定期检测并自动部署新增或者变更的web应用。
1.7 Context
用于配置一个web应用,默认配置如下
2、tomcat部署项目的几个方式
1、直接将项目放到webapps的目录下即可
将项目打成一个war包,在将war包放到webapps目录下即可
2、配置conf/server.xml文件
<Host> 标签提中配置<Context>标签
<Context doBase=“D:\hello” path=“/hehe”> *doBase:项目存放的路径 *path:虚拟目录(网页usl访问的路径)
3、在tomcat\conf\Catalina\localhost下面创建任意名称的xml文件,在文件中编写<Context>标签
<Context doBase=“D:\hello” path=“/hehe”>
Tomcat 集群搭建
1、Nginx的负载均衡策略有哪几种?
1、轮询(默认策略):交替
2、权重:在轮询的基础上,指定轮询的几率,适合服务器配置差异较大的情况
3、ip_hash:指定负载均衡按照基于客户端IP的分配方式,这个方法保证了相同的客户端的请求会发送到相同的服务器,以保证session会话。
2、为什么要session共享?
Tomcat集群中,如果用户需要登录,而这个时候,Tomcat做了负载均衡,用户登录的时候就会出现问题。
如下:第一次登录操作,访问的是Tomcat1,保存了session,第二次进行业务操作,但访问到了Tomcat2,此时Tomcat2是没有记录用户的session信息的,那么权限校验就无法通过。
3、session共享的三种方案
1、ip_hash策略
通过Nginx中负载均衡的ip_hash策略,对于一个用户发起的请求,经过ip_hash计算,只会转发到同一个Tomcat服务器上,这样就解决了session共享的问题。
2、session复制
顾名思义,就是用户访问到一个Tomcat时,这个Tomcat在记录session的同时,以广播的形式告知集群中的其他Tomcat,其他Tomcat复制这个session信息,那么下次如果访问转发到其他的Tomcat上时,因为已经赋值了session,因此可以正常访问。
配置方法:
1、在sever.xml中,将如下注释放开
2、 在web.xml中,添加<distributable/>标签
因为是广播复制,因此频繁的广播和复制会消耗性能,如果超过4个节点,则不推荐使用。
3、单点登录
single sign on(SSO),是目前比较流行的企业业务整合方案之一,sso的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,也是用来解决集群环境session共享的方案之一。
简单来说,就是外部请求通过Nginx反向代理到一台Tomcat时,加一层认证服务,这个服务去redis中查询用户是否已经登录,首次登录将登录session存入到redis中,后续其他Tomcat服务器只要在redis中查到这个session信息,那么就可以认证通过。
4、Tomcat支持Https
1、生成密钥库文件
要实现https,就必须先具有tomcat证书。而JAVA中有自带的证书生成工具keytool,该命令执行完毕后,就会在本地生成一个密钥文件 tomcatkey.keystore
keytool -genkeypair -alias 'tomcat' -keyalg 'RSA' -keystore '/usr/local/tomcat/conf/tomcat.keystore
2、将密钥库文件放置在 tomcat/conf目录下
3、配置tomcat/conf/sever.xml文件
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" schema="https" secure="true" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="证书位置" certificateKeystorePassword="密码" type="RSA"/>
</SSLHostConfig>
</Connector>
4、接着就可以使用8443端口访问https (免费的浏览器不信任,但是可以点击高级继续访问)