微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

学习Tomcat这一篇就够了


第一章 Tomcat概述

1.1、Tomcat概述

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应HTML(标准通用标记语言下的一个应用)页面的访问请求。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。

1.2、Tomcat历史

  1. Tomcat 最初由Sun公司的软件架构师 James Duncan Davidson 开发,名称为“JavaWebServer”。
  2. 1999年,在 Davidson 的帮助下,该项目于1999年于apache软件基金会旗下的JServ项目合并,并发布第一个版本(3.x),即是现在的Tomcat,该版本实现了Servlet2.2和JSP 1.1规范 。
  3. 2001年,Tomcat 发布了4.0版本, 作为里程碑式的版本,Tomcat 完全重新设计了其架构,并实现了Servlet 2.3和JSP 1.2规范。
  4. 目前 Tomcat 已经更新到 10.0.x版本,但是目前企业中的Tomcat服务器,主流版本还是7.x 和 8.x,所以本课程是基于 8.5 版本进行讲解。

1.3、Tomcat官网

点击打开

第二章 Tomcat单实例安装

2.1、环境准备

  • 虚拟机的版本:VMware-workstation-full-15.5.6-16341506.exe
  • 系统镜像版本:CentOS-6.10-x86_64-bin-DVD1.iso,全新安装,桌面版,可上网
  • 系统内存大小:1GB
  • 系统硬盘大小:20GB
  • 连接工具版本:SecureCRTSecureFX_HH_x64_7.0.0.326.zip

2.2、Tomcat下载

[root@caochenlei ~]# wget https://mirrors.bfsu.edu.cn/apache/tomcat/tomcat-8/v8.5.57/bin/apache-tomcat-8.5.57.tar.gz

2.3、Tomcat解压

[root@caochenlei ~]# tar -zxvf apache-tomcat-8.5.57.tar.gz

2.4、Tomcat安装

[root@caochenlei ~]# mv apache-tomcat-8.5.57 /usr/local/tomcat

2.5、Tomcat启动

注意:Tomcat启动需要Java环境,我这里没有安装,使用的是系统自带的,如果你的系统没有Java环境请自行安装,Java环境安装不再本讲之中!

启动Tomcat:

[root@caochenlei ~]# /usr/local/tomcat/bin/startup.sh

关闭防火墙:

[root@caochenlei ~]# service iptables stop
[root@caochenlei ~]# chkconfig iptables off

**在浏览器输入:**http://192.168.239.144:8080/

image-20200906155005379

2.6、Tomcat关闭

[root@caochenlei ~]# /usr/local/tomcat/bin/shutdown.sh

第三章 Tomcat配置文件详解

3.1、server.xml 详解

server.xml 是tomcat 服务器的核心配置文件,包含了Tomcat的 Servlet 容器(Catalina)的所有配置。

3.1.1、Server

Server是server.xml的根元素,用于创建一个Server实例,认使用的实现类是 org.apache.catalina.core.StandardServer。

<Server port="8005" shutdown="SHUTDOWN">
...
</Server>

标签属性和子元素:

  • port:Tomcat 监听的关闭服务器的端口。
  • shutdown:关闭服务器的指令字符串。
  • Server内嵌的子元素为 Listener、GlobalNamingResources、Service。

3.1.2、Listener

认配置的5个Listener的含义:

<!‐‐ 用于以日志形式输出服务器 、操作系统、JVM的版本信息 ‐‐>
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />

<!‐‐ 用于加载(服务器启动)  销毁 (服务器停止) APR。 如果找不到APR库, 则会输出日志, 并不影响Tomcat启动 ‐‐>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

<!‐‐ 用于避免JRE内存泄漏问题 ‐‐>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

<!‐‐ 用户加载(服务器启动)  销毁(服务器停止) 全局命名服务 ‐‐>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

<!‐‐ 用于在Context停止时重建Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 ‐‐>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

3.1.3、GlobalNamingResources

GlobalNamingResources中定义了全局命名服务:

<GlobalNamingResources>
	<!‐‐ 可编辑的用户数据库,UserDatabaseRealm也可以使用该数据库用户进行身份验证 ‐‐>
	<Resource 	name="UserDatabase" 
				auth="Container" 
				type="org.apache.catalina.UserDatabase" 
				description="User database that can be updated and saved" 
				factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 
				pathname="conf/tomcat‐users.xml" />
</GlobalNamingResources>

3.1.4、Service

该元素用于创建 Service 实例,认使用 org.apache.catalina.core.StandardService。认情况下,Tomcat 仅指定了Service 的名称, 值为 “Catalina”。Service 可以内嵌的元素为 : Listener、Executor、Connector、Engine,其中 : Listener 用于为Service添加生命周期监听器, Executor 用于配置Service 共享线程池,Connector 用于配置Service 包含的链接器, Engine 用于配置Service中链接器对应的Servlet 容器引擎。一个Server服务器,可以包含多个Service服务。

<Service name="Catalina">
...
</Service>

3.1.5、Executor

认情况下,Service 并未添加共享线程池配置。 如果我们想添加一个线程池, 可以在 下添加如下配置:

<Executor   name="tomcatThreadPool" 
			namePrefix="catalina‐exec‐" 
			maxThreads="200" 
			minSpareThreads="100" 
			maxIdleTime="60000" 
			maxQueueSize="Integer.MAX_VALUE" 
			prestartminSpareThreads="false" 
			threadPriority="5"
			className="org.apache.catalina.core.StandardThreadExecutor" />

标签属性和子元素:

  • name:线程池名称,用于Connector中指定。
  • namePrefix:所创建的每个线程的名称前缀,一个单独的线程名称为 namePrefix+threadNumber。
  • maxThreads:池中最大线程数。
  • minSpareThreads:活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。
  • maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,认值为6000(1分钟),单位毫秒。
  • maxQueueSize:在被执行前最大线程排队数目,认为Int的最大值,也就是广义的无限。除非特殊情况,这个值不需要更改, 否则会有请求不会被处理的情况发生。
  • prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。 认值为false,即不启动。
  • threadPriority:线程池中线程优先级,认值为5,值从1到10。
  • className:线程池实现类,未指定情况下,认实现类为 org.apache.catalina.core.StandardThreadExecutor。 如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。

3.1.6、Connector

Connector 用于创建链接器实例。认情况下,server.xml 配置了两个链接器,一个支持HTTP协议,一个支持AJP协议。因此大多数情况下,我们并不需要新增链接器配置, 只是根据需要对已有链接器进行优化。

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

标签属性和子元素:

  • port:端口号,Connector 用于创建服务端Socket 并进行监听, 以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector使用。

  • protocol:当前Connector 支持的访问协议。 认为 HTTP/1.1,并采用自动切换机制选择一个基于 JAVA NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)。如果不希望采用上述自动切换的机制, 而是明确指定协议, 可以使用以下值。

    • Http协议:
    org.apache.coyote.http11.Http11NioProtocol  ,非阻塞式 Java NIO 链接器
    org.apache.coyote.http11.Http11Nio2Protocol ,非阻塞式 JAVA NIO2 链接器
    org.apache.coyote.http11.Http11AprProtocol  ,APR 链接
    • AJP协议:
    org.apache.coyote.ajp.AjpNioProtocol  ,非阻塞式 Java NIO 链接器
    org.apache.coyote.ajp.AjpNio2Protocol ,非阻塞式 JAVA NIO2 链接器
    org.apache.coyote.ajp.AjpAprProtocol  ,APR 链接
  • connectionTimeout:Connector接收连接后的等待超时时间, 单位为毫秒。 -1 表示不超时。

  • redirectPort:当前Connector 不支持SSL请求, 接收到了一个请求, 并且也符合 security-constraint 约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。

  • executor:指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads 等属性配置内部线程池。

  • URIEncoding:用于指定编码URI的字符编码, Tomcat8.x版本认的编码为UTF-8,Tomcat7.x版本认为ISO-8859-1。

  • maxThreads:池中最大线程数。

  • minSpareThreads:活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。

  • acceptCount:接收的连接数。

  • maxConnections:接收的最大连接数。

  • compression:是否压缩。

  • compressionMinSize:压缩的大小。

  • disableuploadTimeout:禁用上传超时。

完整的配置如下:

<Connector  port="8080" 
			protocol="HTTP/1.1" 
			connectionTimeout="20000" 
			redirectPort="8443" 
			executor="tomcatThreadPool" 
			URIEncoding="UTF‐8"
			maxThreads="1000" 
			minSpareThreads="100" 
			acceptCount="1000" 
			maxConnections="1000" 
			compression="on" 
			compressionMinSize="2048"
			disableuploadTimeout="true" />

3.1.7、Engine

Engine 作为Servlet 引擎的顶级元素,内部可以嵌入: Cluster、Listener、Realm、 Valve和Host。

<Engine name="Catalina" defaultHost="localhost">
...
</Engine>

标签属性和子元素:

  • name: 用于指定Engine的名称认为Catalina 。该名称会影响一部分Tomcat的存储路径(如临时文件)。
  • defaultHost : 认使用的虚拟主机名称, 当客户端请求指向的主机无效时, 将交由认的虚拟主机处理, 认为localhost。

3.1.8、Host

Host 元素用于配置一个虚拟主机, 它支持以下嵌入元素:Alias、Cluster、Listener、Valve、Realm、Context。

如果在Engine下配置Realm, 那么此配置将在当前Engine下的所有Host中共享。 同样,如果在Host中配置Realm , 则在当前Host下的所有Context中共享。

Context中的Realm优先级 > Host 的Realm优先级 > Engine中的Realm优先级。

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
	...
</Host>

标签属性和子元素:

  • name:当前Host通用的网络名称,必须与DNS服务器上的注册信息一致。 Engine中包含的Host必须存在一个名称与Engine的defaultHost设置一致。
  • appBase:当前Host的应用基础目录,当前Host上部署的Web应用均在该目录下(可以是绝对目录,相对路径),认为webapps。
  • unpackWARs:设置为true,Host在启动时会将appBase目录下war包解压为目录。设置为false, Host将直接从war文件启动。
  • autoDeploy:控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用。

通过给Host添加别名,我们可以实现同一个Host拥有多个网络名称,配置如下:

<Host name="www.web1.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
	<Alias>www.web2.com</Alias>
</Host>

这个时候,我们就可以通过两个域名访问当前Host下的应用(需要确保DNS或hosts中添加了域名的映射配置)。

3.1.9、Context

Context 用于配置一个Web应用,认的配置如下:

<Context docBase="myApp" path="/myApp">
	....
</Context>

标签属性和子元素:

  • docBase:Web应用目录或者War包的部署路径。可以是绝对路径,也可以是相对于Host appBase的相对路径。
  • path:Web应用的Context 路径。如果我们Host名为localhost, 则该web应用访问的根路径为: http://localhost:8080/myApp。
  • 支持的内嵌元素为:CookieProcessor, Loader, Manager,Realm,Resources,WatchedResource,JarScanner,Valve。

简单的举例:

<Host name="www.tomcat.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
	<Context docBase="D:\servlet_project" path="/myApp"></Context>
	<Valve  className="org.apache.catalina.valves.AccessLogValve" 
			directory="logs" 
			prefix="localhost_access_log" 
			suffix=".txt" 
			pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

3.2、tomcat-users.xml 详解

配置文件中,主要配置的是Tomcat的用户,角色等信息,用来控制Tomcat中 host-manager、manager的访问权限。

从早期的Tomcat版本开始,就提供了Web版的管理控制台,他们是两个独立的Web应用,位于webapps目录下。Tomcat 提供的管理应用有用于管理的Host的host-manager 和用于管理Web应用的manager。

3.2.1、host-manager应用配置

Tomcat启动之后,可以通过 http://localhost:8080/host-manager/html 访问该Web应用。 host-manager 添加了访问权限控制,当打开网址时,需要输入用户名密码(conf/tomcat-users.xml中配置) 。所以要想访问该页面,需要在conf/tomcat-users.xml 中配置,并分配对应的角色:

<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="admin" password="123456" roles="admin-gui,admin-script"/>

标签属性和子元素:

  • admin-gui:用于控制页面访问权限。
  • admin-script:用于控制以简单文本的形式进行访问。

启动Tomcat,然后点击host-manager,我是在虚拟机的火狐浏览器中打开的,它不能让别人随意访问,否则会出现安全问题:

image-20200906172949187

3.2.2、manager应用配置

manager的访问地址为 http://localhost:8080/manager,同样,manager也添加页面访问控制,所以要想访问该页面,需要在conf/tomcat-users.xml 中配置,并分配对应的角色:

<role rolename="admin‐gui" />
<role rolename="admin‐script" />
<role rolename="manager‐gui" />
<role rolename="manager‐script" />
<user username="admin" password="123456" roles="admin-script,admin-gui,manager-gui,manager-script" />

启动Tomcat,然后点击manager,我是在虚拟机的火狐浏览器中打开的,它不能让别人随意访问,否则会出现安全问题:

image-20200906173409876

3.3、web.xml 详解

web.xml 是web应用的描述文件, 它支持的元素及属性来自于Servlet 规范定义 。 在Tomcat 中, Web 应用的描述信息包括 tomcat/conf/web.xml 中认配置以及 Web应用 WEB-INF/web.xml 下的定制配置。

3.3.1、ServletContext初始化参数

我们可以通过 添加ServletContext 初始化参数,它配置了一个键值对,这样我们可以在应用程序中使用 javax.servlet.ServletContext.getinitParameter()方法获取参数。举例如下:

<context‐param>
	<param‐name>contextConfigLocation</param‐name>
	<param‐value>classpath:applicationContext‐*.xml</param‐value>
	<description>Spring Config File Location</description>
</context‐param>

标签属性和子元素:

  • param‐name:初始化参数名称
  • param‐value:初始化参数的值。
  • description:这个参数的描述信息。

3.3.2、会话配置

用于配置Web应用会话,包括 超时时间、Cookie配置以及会话追踪模式。它将覆盖server.xml 和 context.xml 中的配置。举例如下:

<session-config>
	<session-timeout>30</session‐timeout>
	<cookie-config>
		<name>JESSIONID</name>
		<domain>www.baidu.cn</domain>
		<path>/</path>
		<comment>Session Cookie</comment>
		<http-only>true</http‐only>
		<secure>false</secure>
		<max-age>3600</max‐age>
	</cookie‐config>
	<tracking-mode>COOKIE</tracking‐mode>
</session‐config>

标签属性和子元素:

  • session-timeout: 会话超时时间,单位:分钟。
  • cookie-config:用于配置会话追踪Cookie。
    • name:Cookie的名称
    • domain:Cookie的域名。
    • path:Cookie的路径。
    • comment:Cookie的注释。
    • http-only:Cookie只能通过HTTP方式进行访问,JS无法读取或修改,此项可以增 加网站访问的安全性。
    • secure:此Cookie只能通过HTTPS连接传递到服务器,而HTTP连接则不会传递该信息。注意是从浏览器传递到服务器,服务器端的Cookie对象不受此项影响。
    • max-age:以秒为单位表示cookie的生存期,认为‐1表示是会话Cookie,浏览器 关闭时就会消失。
  • tracking-mode:用于配置会话追踪模式,Servlet3.0版本中支持的追踪模式: COOKIE、URL、SSL。

3.3.3、Servlet配置

Servlet 的配置主要是两部分, servlet 和 servlet-mapping

<servlet>
	<servlet-name>myServlet</servlet‐name>
	<servlet-class>com.caochenlei.MyServlet</servlet‐class>
	<init-param>
		<param-name>fileName</param‐name>
		<param-value>init.conf</param‐value>
	</init‐param>
	<load-on-startup>1</load‐on‐startup>
	<enabled>true</enabled>
</servlet>
<servlet-mapping>
	<servlet-name>myServlet</servlet‐name>
	<url-pattern>*.do</url‐pattern>
	<url-pattern>/myservet/*</url‐pattern>
</servlet‐mapping>

标签属性和子元素:

servlet:

  • servlet-name:指定servlet的名称, 该属性在web.xml中唯一。
  • servlet-class:用于指定servlet类名。
  • init-param:用于指定servlet的初始化参数, 在应用中可以通过 HttpServlet.getinitParameter 获取
    • param-name:初始化参数名称
    • param-value:初始化参数的值。
  • load-on-startup:用于控制在Web应用启动时,Servlet的加载顺序, 值小于0,web应用启动时,不加载该servlet,第一次访问时加载。
  • enabled:若为false,表示Servlet不处理任何请求。

servlet-mapping

  • servlet-name:你想要让哪个servlet处理,这里就写哪个servlet名称
  • url-pattern:用于指定URL表达式,一个 servlet‐mapping可以同时配置多个 url‐ pattern。

servlet 中文件上传配置:

<servlet>
	<servlet-name>uploadServlet</servlet‐name>
	<servlet-class>com.caochenlei.UploadServlet</servlet‐class>
	<multipart-config>
		<location>C://path</location>
		<max-file-size>10485760</max‐file‐size>
		<max-request-size>10485760</max‐request‐size>
		<file-size-threshold>0</file‐size‐threshold>
	</multipart‐config>
</servlet>

标签属性和子元素:

  • multipart-config上传的配置
    • location:存放生成文件地址。
    • max-file-size:允许上传文件最大值。 认值为‐1, 表示没有限制。
    • max-request-size:针对该 multi/form‐data 请求的最大数量认值为‐1, 表示无限制。
    • file-size-threshold:当数量量大于该值时, 内容会被写入文件

3.3.4、Listener配置

Listener用于监听servlet中的事件,例如context、request、session对象的创建、修改删除,并触发响应事件。Listener是观察者模式的实现,在servlet中主要用于对context、request、session对象的生命周期进行监控。在servlet2.5规范中共定义了8中Listener。在启动时,servletcontextlistener 的执行顺序与web.xml 中的配置顺序一致, 停止时执行顺序相反。

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener‐class>
</listener>

标签属性和子元素:

  • listener-class:用于指定监听的类,该类必须实现Listener接口。

3.3.5、Filter配置

filter 用于配置web应用过滤器, 用来过滤资源请求及响应。 经常用于认证、日志、加密、数据转换等操作, 配置如下:

<filter>
	<filter-name>myFilter</filter‐name>
	<filter-class>com.caochenlei.MyFilter</filter‐class>
	<async-supported>true</async‐supported>
	<init-param>
		<param-name>language</param‐name>
		<param-value>CN</param‐value>
	</init‐param>
</filter>
<filter-mapping>
	<filter-name>myFilter</filter‐name>
	<url-pattern>/*</url‐pattern>
</filter‐mapping>

标签属性和子元素:

filter:

  • filter-name:用于指定过滤器名称,在web.xml中,过滤器名称必须唯一。
  • filter-class:过滤器的全限定类名,该类必须实现Filter接口。
  • async-supported:该过滤器是否支持异步。
  • init-param:用于配置Filter的初始化参数, 可以配置多个, 可以通过FilterConfig.getinitParameter获取
    • param-name:初始化参数名称
    • param-value:初始化参数的值。

filter-mapping:

  • filter-name:这里指的是你想使用哪个过滤器进行过滤就写哪个过滤器的名称
  • url-pattern:指定该过滤器需要拦截的URL。

3.3.6、欢迎页面配置

welcome-file-list 用于指定web应用的欢迎文件列表。尝试请求的顺序,从上到下。

<welcome-file-list>
	<welcome-file>index.html</welcome-file>
	<welcome-file>index.htm</welcome-file>
	<welcome-file>index.jsp</welcome-file>
	<welcome-file>default.html</welcome-file>
	<welcome-file>default.htm</welcome-file>
	<welcome-file>default.jsp</welcome-file>
</welcome-file-list>

3.3.7、错误页面配置

error-page 用于配置Web应用访问异常时定向到的页面支持HTTP响应码和异常类两种形式。

<error-page>
	<error-code>404</error‐code>
	<location>/404.html</location>
</error‐page>
<error-page>
	<error-code>500</error‐code>
	<location>/500.html</location>
</error‐page>
<error-page>
	<exception-type>java.lang.Exception</exception‐type>
	<location>/error.jsp</location>
</error‐page>

第四章 Tomcat高可用集群

4.1、环境准备

  • 虚拟机的版本:VMware-workstation-full-15.5.6-16341506.exe
  • 系统镜像版本:CentOS-6.10-x86_64-bin-DVD1.iso,全新安装,桌面版,可上网
  • 系统内存大小:1GB
  • 系统硬盘大小:20GB
  • 连接工具版本:SecureCRTSecureFX_HH_x64_7.0.0.326.zip

4.2、集群概述

由于单台Tomcat的承载能力是有限的,当我们的业务系统用户量比较大,请求压力比较大时,单台Tomcat是扛不住的,这个时候,就需要搭建Tomcat的集群,而目前比较流程的做法就是通过Nginx来实现Tomcat集群的负载均衡。

4.3、集群架构

image-20200906181424604

4.4、安装第一台tomcat服务器

解压:

[root@caochenlei ~]# tar -zxvf apache-tomcat-8.5.57.tar.gz

安装:

[root@caochenlei ~]# mv apache-tomcat-8.5.57 /usr/local/tomcat1

创建测试目录和页面

[root@caochenlei ~]# mkdir -p /usr/local/tomcat1/webapps/edu
[root@caochenlei ~]# echo "<h1>This is 8080 Port</h1>" > /usr/local/tomcat1/webapps/edu/a.html

开放防火墙:

[root@caochenlei ~]# /sbin/iptables -I INPUT -p tcp --dport 8080 -j ACCEPT
[root@caochenlei ~]# /etc/rc.d/init.d/iptables save

启动:

[root@caochenlei ~]# /usr/local/tomcat1/bin/startup.sh

4.5、安装第二台tomcat服务器

解压:

[root@caochenlei ~]# tar -zxvf apache-tomcat-8.5.57.tar.gz

安装:

[root@caochenlei ~]# mv apache-tomcat-8.5.57 /usr/local/tomcat2

修改端口:

[root@caochenlei ~]# rm -f /usr/local/tomcat2/conf/server.xml
[root@caochenlei ~]# vi /usr/local/tomcat2/conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8015" shutdown="SHUTDOWN">
	<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
	<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
	<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
	<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
	<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

	<GlobalNamingResources>
		<Resource   name="UserDatabase" 
					auth="Container" 
					type="org.apache.catalina.UserDatabase" 
					description="User database that can be updated and saved" 
					factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 
					pathname="conf/tomcat-users.xml" />
	</GlobalNamingResources>

	<Service name="Catalina">
		<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
		<Engine name="Catalina" defaultHost="localhost">
			<Realm className="org.apache.catalina.realm.LockOutRealm">
				<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" />
			</Realm>
			<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
				<Valve  className="org.apache.catalina.valves.AccessLogValve" 
						directory="logs" 
						prefix="localhost_access_log" 
						suffix=".txt" 
						pattern="%h %l %u %t &quot;%r&quot; %s %b" />
			</Host>
		</Engine>
	</Service>
</Server>

创建测试目录和页面

[root@caochenlei ~]# mkdir -p /usr/local/tomcat2/webapps/edu
[root@caochenlei ~]# echo "<h1>This is 8081 Port</h1>" > /usr/local/tomcat2/webapps/edu/a.html 

开放防火墙:

[root@caochenlei ~]# /sbin/iptables -I INPUT -p tcp --dport 8081 -j ACCEPT
[root@caochenlei ~]# /etc/rc.d/init.d/iptables save

启动:

[root@caochenlei ~]# /usr/local/tomcat2/bin/startup.sh

4.6、安装Nginx反向代理服务器

安装依赖:

[root@caochenlei ~]# yum install -y gcc gcc-c++ make libtool wget pcre pcre-devel zlib zlib-devel openssl openssl-devel

Nginx下载:

[root@caochenlei ~]# wget http://Nginx.org/download/Nginx-1.18.0.tar.gz

Nginx解压:

[root@caochenlei ~]# tar -zxvf Nginx-1.18.0.tar.gz

Nginx安装:

[root@caochenlei ~]# cd Nginx-1.18.0
[root@caochenlei Nginx-1.18.0]# ./configure
[root@caochenlei Nginx-1.18.0]# make && make install

注意:安装完成后的路径为:/usr/local/Nginx

Nginx配置:

[root@caochenlei Nginx-1.18.0]# vi /usr/local/Nginx/conf/Nginx.conf
#在 server 外边配置,负载均衡服务列表
upstream myserver {
	server 192.168.239.144:8080;
	server 192.168.239.144:8081;
}

#在 location / { 里边配置,路径请求转发规则
	proxy_pass http://myserver;
[root@caochenlei Nginx-1.18.0]# /usr/local/Nginx/sbin/Nginx

Nginx命令:

开放防火墙:

[root@caochenlei ~]# /sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT
[root@caochenlei ~]# /etc/rc.d/init.d/iptables save
iptables:将防火墙规则保存到 /etc/sysconfig/iptables:[确定]

4.7、访问测试

打开IE浏览器输入:http://192.168.206.128/edu/a.html

4.8、如何保证主Nginx高可用

请您参考:学习Nginx这一篇就够了中的高可用集群如何搭建的。

原理说明:准备两台Nginx,一主一备,每一台上安装一个keepalived,由keepalived负责监测当前机器上的Nginx是否可用(其实是调用一个shell脚本不停的查看进程信息),同时也会在两个keepalived之间进行心跳监测,如果主节点宕机,则从节点自动跟上,访问是通过虚拟IP进行访问的。

4.9、如何解决session共享问题

方案一:ip_hash 策略

通过修改Nginx配置文件,让每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决 session 的问题。 例如:

image-20200829001054083

方案二:session 复制

在每台Tomcat的 conf/server.xml 配置如下:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

在每台Tomcat部署的应用程序中,例如:servlet_demo 的 web.xml 中加入如下配置 :

<distributable/>

注意:上述方案,适用于较小的集群环境(节点数不超过4个),如果集群的节点数比较多的话,通过这种广播的形式来完成Session的复制,会消耗大量的网络带宽,影响服务的性能

第五章 Tomcat安全问题

5.1、配置安全

5.2、应用安全

在大部分的Web应用中,特别是一些后台应用系统,都会实现自己的安全管理模块(权 限模块),用于控制应用系统的安全访问,基本包含两个部分:认证(登录/单点登录) 和授权(功能权限、数据权限)两个部分。对于当前的业务系统,可以自己做一套适用 于自己业务系统的权限模块,也有很多的应用系统直接使用一些功能完善的安全框架, 将其集成到我们的web应用中,如:SpringSecurity、Apache Shiro等。

5.3、传输安全

HTTPS的全称是超文本传输安全协议(Hypertext Transfer Protocol Secure),是一种网络安全传输协议。在HTTP的基础上加入SSL/TLS来进行数据加密,保护交换数据不被泄露、窃取。

SSL 和 TLS 是用于网络通信安全的加密协议,它允许客户端和服务器之间通过安全链接 通信。SSL 协议的3个特性:

  • 保密:通过SSL链接传输的数据时加密的。
  • 鉴别:通信双方的身份鉴别,通常是可选的,单至少有一方需要验证。
  • 完整性:传输数据的完整性检查。

性能角度考虑,加解密是一项计算昂贵的处理,因为尽量不要将整个Web应用采用SSL 链接, 实际部署过程中, 选择有必要进行安全加密的页面(存在敏感信息传输的页面) 采用SSL通信。

HTTPS和HTTP的区别主要为以下四点:

  • HTTPS协议需要到证书颁发机构CA申请SSL证书,然后与域名进行绑定,HTTP不用申请证书。
  • HTTP是超文本传输协议,属于应用层信息传输,HTTPS则是具有SSL加密传安全性传输协议,对数据的传输进行加密,相当于HTTP的升级版。
  • HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是8080,后者是8443。
  • HTTP的连接很简单,是无状态的、HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。

HTTPS协议优势:

  • 提高网站排名,有利于SEO。谷歌已经公开声明两个网站在搜索结果方面相同,如果 一个网站启用了SSL,它可能会获得略高于没有SSL网站的等级,而且百度也表明对安装了SSL的网站表示友好。因此,网站上的内容中启用SSL都有明显的SEO优势。
  • 隐私信息加密,防止流量劫持。特别是涉及到隐私信息的网站,互联网大型的数据泄露的事件频发发生,网站进行信息加密势在必行。
  • 浏览器受信任。 自从各大主流浏览器大力支持HTTPS协议之后,访问HTTP的网站都会提示“不安全”的警告信息。

Tomcat支持HTTPS:这里采用单实例版本的Tomcat进行配置测试

第一步:查找秘钥库生成器,这个工具是Java提供的

[root@caochenlei ~]# find / -name keytool
/usr/bin/keytool
/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/bin/keytool
/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.181.x86_64/jre/bin/keytool
/etc/alternatives/keytool

第二步:生成秘钥库文件

[root@caochenlei ~]# /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.181.x86_64/jre/bin/keytool -genkey -alias tomcat -keyalg RSA -keystore tomcatkey.keystore
输入密钥库口令:  
再次输入新口令: 
您的名字与姓氏是什么?(这里使用的必须是域名)
  [UnkNown]:  www.abc.com
您的组织单位名称是什么?
  [UnkNown]:  abc
您的组织名称是什么?
  [UnkNown]:  abc
您所在的城市或区域名称是什么?
  [UnkNown]:  BeiJing
您所在的省/市/自治区名称是什么?
  [UnkNown]:  BeiJing
该单位的双字母国家/地区代码是什么?
  [UnkNown]:  CN
CN=www.abc.com,OU=abc,O=abc,L=BeiJing,ST=BeiJing,C=CN是否正确?
  []:  Y

输入 <tomcat> 的密钥口令
        (如果和密钥库口令相同,按回车):  

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore tomcatkey.keystore -destkeystore tomcatkey.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

第三步:将秘钥库文件 tomcatkey.keystore 复制到tomcat/conf 目录下

[root@caochenlei ~]# cp tomcatkey.keystore /usr/local/tomcat/conf/

第四步:配置tomcat/conf/server.xml

删除

[root@caochenlei ~]# rm -rf /usr/local/tomcat/conf/server.xml

添加

[root@caochenlei ~]# vi /usr/local/tomcat/conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
	<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
	<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
	<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
	<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
	<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

	<GlobalNamingResources>
		<Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" />
	</GlobalNamingResources>



	<Service name="Catalina">
		<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

		<!-- 主要新增的代码 -->
		<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" schema="https" secure="true" SSLEnabled="true">
			<SSLHostConfig certificateVerification="false">
				<Certificate certificateKeystoreFile="/usr/local/tomcat/conf/tomcatkey.keystore" certificateKeystorePassword="123456" type="RSA" />
			</SSLHostConfig>
		</Connector>

		<Engine name="Catalina" defaultHost="localhost">
			<Realm className="org.apache.catalina.realm.LockOutRealm">
				<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" />
			</Realm>
			<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
				<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
			</Host>
		</Engine>
	</Service>
</Server>

第五步:关闭服务器,防止启动失败,然后再启动访问

[root@caochenlei ~]# /usr/local/tomcat1/bin/shutdown.sh 
[root@caochenlei ~]# /usr/local/tomcat2/bin/shutdown.sh  
[root@caochenlei ~]# /usr/local/Nginx/sbin/Nginx -s quit
[root@caochenlei ~]# /usr/local/tomcat/bin/startup.sh

第六步:配置本机域名映射

复制一份 C:\Windows\System32\drivers\etc\hosts 到桌面,然后新增一条记录,然后再替换回去。

#虚拟机的IP地址       虚拟域名
192.168.239.144     www.abc.com

打开浏览器输入:https://www.abc.com:8443/

image-20200906212256130

第六章 Tomcat原理分析

6.1、Http工作原理

HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件图片查询结果等),HTTP协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式。它的整个过程如下图所示:

image-20200907081429760

  1. 用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览器获取了这个事件。
  2. 浏览器向服务端发出TCP连接请求。
  3. 服务程序接受浏览器的连接请求并经过TCP三次握手建立连接。
  4. 浏览器将请求数据打包成一个HTTP协议格式的数据包。
  5. 浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。
  6. 服务端程序拿到这个数据包后,同样以HTTP协议格式解包,获取到客户端的意图。
  7. 得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。
  8. 服务器将响应结果(可能是HTML或者图片等)按照HTTP协议格式打包。
  9. 服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。
  10. 浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是 HTML。
  11. 浏览器将HTML文件展示在页面上。

那我们想要探究的Tomcat作为一个HTTP服务器,在这个过程中都做了些什么事情呢?

主要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。

6.2、Tomcat整体架构

Tomcat要实现两个核心功能

  1. 处理Socket连接,负责网络字节流与Request和Response对象的转化。
  2. 加载和管理Servlet,以及具体处理Request请求。

因此Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这 两件事情。连接器负责对外交流,容器负责内部处理。

image-20200907081935758

6.3、Coyote连接器架构

Coyote是Tomcat的连接器框架的名称,是Tomcat服务器提供的供客户端访问的外部接口。客户端通过Coyote与服务器建立连接、发送请求并接受响应 。

Coyote封装了底层的网络通信(Socket请求及响应处理),为Catalina容器提供了统一的接口,使Catalina容器与具体的请求协议及IO操作方式完全解耦。Coyote 将Socket输入转换封装为Request对象,交由Catalina容器进行处理,处理请求完成后,Catalina通过Coyote提供的Response对象将结果写入输出流 。

Coyote作为独立的模块,只负责具体协议和IO的相关操作,与Servlet规范实现没有直接关系,因此即便是Request和Response对象也并未实现Servlet规范对应的接口, 而是在Catalina中将他们进一步封装为ServletRequest和ServletResponse。

image-20200907082342461

在Coyote中,Tomcat支持的IO模型,请看下表:

IO模型 描述
NIO 非阻塞I/O,采用Java NIO类库实现。
NIO2 异步I/O,采用JDK 7最新的NIO2类库实现。
APR 采用Apache可移植运行库实现,是C/C++编写的本地库。如果选择该方案,需要单独安装APR库。

在Coyote中,Tomcat支持的应用层协议,请看下表:

应用层协议 描述
HTTP/1.1 这是大部分Web应用采用的访问协议。
AJP/1.3 用于和Web服务器集成(如Apache),以实现对静态资源的优化以及集群部署。
HTTP/2 HTTP 2.0大幅度的提升了Web性能。下一代HTTP协议 , 自8.5以及9.0版本之后支持

协议分层 :

image-20200907082850508

在8.0之前,Tomcat 认采用的I/O方式为 BIO,之后改为 NIO。 无论 NIO、NIO2还是 APR,在性能方面均优于以往的BIO。 如果采用APR,甚至可以达到 Apache HTTP Server的影响性能

Tomcat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器,就好比一个房间有多个门。但是单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作Service组件。这里请你注意,Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat内可能有多个Service,这样的设计也是出于灵活性的考虑。通过在Tomcat中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

Coyote的主要组件结构如下:

image-20200907083219856

Coyote的各个组件的作用如下:

  • EndPoint
    EndPoint:Coyote 通信端点,即通信监听的接口,是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的。
    Tomcat 并没有EndPoint接口,而是提供了一个抽象类AbstractEndpoint,里面定义了两个内部类:Acceptor和SocketProcessor。Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法调用协议处理组件Processor进行处理。为了提高处理力,SocketProcessor被提交到线程池来执行,而这个线程池叫作执行器(Executor)。

  • Processor
    Processor:Coyote 协议处理接口,如果说EndPoint是用来实现TCP/IP协议的,那么Processor用来实现HTTP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。

  • ProtocolHandler

    ProtocolHandler:Coyote 协议接口,通过Endpoint和Processor,实现针对具体协议的处理能力。Tomcat按照协议和I/O 提供了6个实现类 : AjpNioProtocol、AjpNio2Protocol、AjpAprProtocol、Http11NioProtocol、Http11Nio2Protocol、Http11AprProtocol。我们在配置tomcat / conf / server.xml时,至少要指定具体的ProtocolHandler,当然也可以指定协议名称,如:HTTP/1.1,如果安装了APR,那么将使用Http11AprProtocol,否则使用 Http11NioProtocol 。

  • Adapter

    由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类 来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。 但是这个Request对象不是标准的ServletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。Tomcat设计者的解决方案是引入CoyoteAdapter,这是 适配器模式的经典运用,连接器调用CoyoteAdapter的Sevice方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法

6.4、Catalina容器架构

Tomcat的模块分层结构如下:

image-20200907084122883

Tomcat本质上就是一款 Servlet 容器,因此Catalina 才是 Tomcat 的核心,其他模块都是为Catalina提供支撑的。比如:通过Coyote模块提供连接通信,Jasper 模块提供JSP引擎,Naming 提供JNDI 服务,Juli提供日志服务。

Catalina的主要组件结构如下:

image-20200907084424992

Catalina的各个组件的作用如下:

  • Catalina

    负责解析Tomcat的配置文件,以此来创建服务器Server组件,并根据 命令来对其进行管理。

  • Server

    服务器表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlet引擎、Tomcat连接器。Server通过实现Lifecycle接口,提供了 一种优雅的启动和关闭整个系统的方式。

  • Service

    服务是Server内部的组件,一个Server包含多个Service。它将若干个Connector组件绑定到一个Container(Engine)上。

  • Connector

    连接器主要是处理与客户端的通信,它负责接收客户请求,然后转给相关的容器处理,最后向客户返回响应结果。

  • Container

    容器负责处理用户的Servlet请求,并返回对象给web用户的模块。

Container的主要组件结构如下:

Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper。这4种容器不是平行关系,而是父子关系。Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。

image-20200907085102815

Container的各个组件的作用如下:

  • Engine

    表示整个Catalina的Servlet引擎,用来管理多个虚拟站点一个Service最多只能有一个Engine,但是一个引擎可包含多个Host。

  • Host

    代表一个虚拟主机或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context。

  • Context

    表示一个Web应用程序, 一个Web应用可包含多个Wrapper。

  • Wrapper

    表示一个Servlet,Wrapper 作为容器中的最底层,不能包含子容器。

我们也可以再通过Tomcat的server.xml配置文件来加深对Tomcat容器的理解。Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。

image-20200907085454219

那么,Tomcat是怎么管理这些容器的呢?你会发现这些容器具有父子关系,形成一个树形结构,你可能马上就想到了设计模式中的组合模式。没错,Tomcat就是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的Wrapper,组合容器对象指的是上面的Context、Host或者Engine。

image-20200907085721607

6.5、Jasper处理流程

Tomcat在认的web.xml中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所有的.jsp或 .jspx结尾的请求,该Servlet 实现即是运行时编译的入口。

image-20200907090131426

JspServlet 处理流程图:

image-20200907090204389

  • 如果在 tomcat/conf/web.xml 中配置了参数scratchdir,则jsp编译后的结果,就会存储在该目录下 。
  • 如果没有配置该选项,则会将编译后的结果,存储在Tomcat安装目录下的 work/Catalina(Engine名称)/localhost(Host名称)/Context名称

除了运行时编译,我们还可以直接在Web应用启动时, 一次性将Web应用中的所有的JSP页面一次性编译完成。在这种情况下,Web应用运行过程中,便可以不必再进行实时编译,而是直接调用JSP页面对应的Servlet完成请求处理, 从而提升系统性能

Tomcat 提供了一个Shell程序JspC,用于支持JSP预编译,而且在Tomcat的安装目录下提供了一个 catalina-tasks.xml 文件声明了Tomcat 支持的Ant任务, 因此,我们很容易使用 Ant 来执行JSP 预编译。(要想使用这种方式,必须得确保在此之前已经下载并安装了Apache Ant)。

6.6、JSP编译过程

image-20200907090746567

Compiler 编译工作主要包含代码生成和编译两部分:

  • 代码生成

    • 1) Compiler 通过一个 PageInfo 对象保存JSP 页面编译过程中的各种配置,这些配置可能来源于 Web 应用初始化参数, 也可能来源于JSP页面的指令配置(如 page , include)。
    • 2) 调用ParserController 解析指令节点, 验证其是否合法,同时将配置信息保存到 PageInfo 中, 用于控制代码生成
    • 3) 调用ParserController 解析整个页面, 由于 JSP 是逐行解析, 所以对于每一行会创建一个具体的Node 对象。如静态文本(TemplateText)、Java代码(Scriptlet)、定制标签(CustomTag)、Include指令(IncludeDirective)。
    • 4) 验证除指令外其他所有节点的合法性, 如脚本、定制标签、EL表达式等。
    • 5) 收集除指令外其他节点的页面配置信息。
    • 6) 编译并加载当前 JSP 页面依赖的标签
    • 7) 对于JSP页面的EL表达式,生成对应的映射函数
    • 8) 生成JSP页面对应的Servlet类源代码 编译 代码生成完成后,Compiler还会生成 SMAP信息。 如果配置生成 SMAP信息,Compiler则会在编译阶段将SMAP信息写到class文件中 。
  • 编译阶段

    Compiler的两个实现 AntCompiler 和 JDTCompiler 分别调用先关框架的 API 进行源代码编译。

    对于 AntCompiler 来说, 构造一个 Ant 的javac 的任务完成编译。

    对于 JDTCompiler 来说, 调用 org.eclipse.jdt.internal.compiler.Compiler 完成编译。

6.7、Tomcat启动流程

image-20200907085753038

6.8、Tomcat请求处理流程

image-20200907085815693

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐