概要:
- Tomcat各核心组件认知
- Tomcat server.xml 配置详解
- Tomcat IO模型介绍
一、Tomcat各组件认知
知识点:
- Tomcat架构说明
- Tomcat组件及关系详情介绍
- Tomcat启动参数说明
- Tomcat架构说明
1. Tomcat 安装配置
1.安装JAVA(因为tomcat依赖于java)
- 配置:JAVA_HOME
- D:\Program Files (x86)\Java\jdk1.7.0
- path
- %JAVA_HOME%\bin
- 验证:java -version
2.安装TOMCAT
- CATALINA_BASE
- D:\apache-tomcat-7.0.16
- CATALINA_HOME
- D:\apache-tomcat-7.0.16
- CATALINA_TMPDIR
- D:\apache-tomcat-7.0.16
- path
- %CATALINA_HOME%\bin
- 验证:
- startup
- localhost:8080
ps:**
- catalina.home(安装目录):指向公用信息的位置,就是 bin 和 lib 的父目录。
- catalina.base(工作目录):指向每个 Tomcat 目录私有信息的位置,就是 conf、logs、temp、webapps 和work 的父目录。
仅运行一个Tomcat实例时,这两个属性指向的位置是相同的。如果我们想要运行Tomcat的 多个实例,但是不想安装多个Tomcat软件副本。那么我们可以配置多个工作目录,每个运行实例独占一个工作目录,但是共享同一个安装目录。
Tomcat 的目录结构
- bin(运行脚本)
- conf(配置文件)
- lib (核心库文件)
- logs (日志目录)
- 存放tomcat运行过程中产生的日志文件,在linux环境中,控制台的输出日志在
catalina.out
文件中
- 存放tomcat运行过程中产生的日志文件,在linux环境中,控制台的输出日志在
- temp (临时目录)
- webapps (自动装载的应用程序的目录)
- 用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用。 当然,你也可以把应用程序放置在磁盘的任意位置,在配置文件中映射好就行。
- work (JVM临时文件目录[java.io.tmpdir])
- 存放tomcat在运行时的编译后文件,例如JSP编译后的文件。
- 清空work目录,然后重启tomcat,可以达到清除缓存的作用。
2.Tomcat 各组件及关系
Tomcat是一个基于JAVA的WEB容器,是一个免费的开放源码的 Web 应用服务器。
Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,它早期的名称为catalina,后来由Apache、Sun 和其他一些公司及个人共同开发而成,并更名为Tomcat。
最本质上讲,tomcat为一个jsp/servlet容器
其实现了JAVA EE中的 Servlet 与 jsp 规范,与Nginx apache 服务器不同在于一般用于动态请求处理。在架构设计上采用面向组件的方式设计。即整体功能是通过组件的方式拼装完成。另外每个组件都可以被替换以保证灵活性。
那么是哪些组件组成了Tomcat呢?
- 1. 顶级组件:Server 和 Service
- Server:表示一个Tomcat实例 (单例的);Server 代表整个 catalina servlet 容器;包含一个或多个service 子容器。主要是用来管理容器下各个 Serivce 组件的生命周期。
- Service:代表 Tomcat 中一组提供服务、处理请求的组件。是一个分组结构,包括多个 Connector 和一个 Container。
- 2. Connector 连接器
- Connector 是客户端连接到 Tomcat 容器的服务点,它为引擎提供协议服务来将引擎与客户端各种协议隔离开来。如HTTP、SSL HTTPS、AJP协议( Apache JServ Protocol) apache 私有协议(二进制协议),用于apache 反向代理 Tomcat)
- Connector的基本属性都是它所需要监听的IP地址及端口号,以及所支持的协议。还有一个关键属性就是并发处理传入请求的最大线程数。注意,Connector关键的有 连接器(HTTP HTTPS HTTP1.1 AJP SSL proxy) 运行模式(BIO NIO NIO2/AIO APR)多线程/线程池
- 一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
- 3. 容器组件 Container:
Container 是容器的父接口,用于封装和管理 Servlet,以及具体处理 Request 请求,该容器的设计用的是典型的责任链的设计模式,它由四个自容器组件构成,分别是 Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。只包含一个引擎。
- Engine 引擎 Catalina
- 表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。
- 在一个服务中只能有一个引擎。同时,作为一个真正的容器。
- Engine元素之下可以包含一个或多个虚拟主机。
- 它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。
- Host 虚拟主机 基于域名 分发请求
- 作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。
- 它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个 Web App,每个 Web App 对应于一个 Context,当 Host 获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。
- Host主要用来解析 web.xml。
- Context 上下文 隔离各个WEB应用 每个Context的 ClassLoader都是独立
- 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。
- Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context对应于一个 Web Application,一个 Web Application 由一个或者多个Servlet实例组成。
- Wrapper包装器: 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
- Component 嵌套组件
- Valve阀门:类似于Servlet规范中定义的过滤器,用来拦截请求并在将其转至目标之前进行某种处理操作。Valve可以定义在任何容器类的组件中。Valve常被用来记录客户端请求、客户端IP地址和服务器等信息,这种处理技术通常被称作请求转储(request dumping)。请求转储valve记录请求客户端请求数据包中的HTTP首部信息和cookie信息文件中,响应转储valve则记录响应数据包首部信息和cookie信息至文件中。
- Logger日志记录器:用于记录组件内部的状态信息,可被用于除Context之外的任何容器中。日志记录的功能可被继承,因此,一个引擎级别的Logger将会记录引擎内部所有组件相关的信息,除非某内部组件定义了自己的Logger组件。
- Loader类加载器:负责加载、解释Java类编译后的字节码。
- Realm领域:用于用户的认证和授权;在配置一个应用程序时,管理员可以为每个资源或资源组定义角色及权限,而这些访问控制功能的生效需要通过Realm来实现。Realm的认证可以基于文本文件、数据库表、LDAP服务等来实现。Realm的效用会遍及整个引擎或顶级容器,因此,一个容器内的所有应用程序将共享用户资源。同时,Realm可以被其所在组件的子组件继承,也可以被子组件中定义的Realm所覆盖
- Excutor执行器:执行器组件允许您配置一个共享的线程池,以供您的连接器使用。从tomcat 6.0.11版本开始。
- Listener监听器:监听已注册组件的生命周期。
- Manager会话管理器:用于实现http会话管理的功能,tomcat6种有5种会话管理的manager的实现(standardManager、persisentManager、DeltaManager、BackupManager、SimpleTcpReplicationManager)。会话让使用无状态HTTP协议的应用程序完成通信。会话表示客户端和服务器之间的通信,会话功能是由javax.servlet.http.HttpSession 的实例实现的,该实例存储在服务器上而且与一个唯一的标识符相关联,客户端在与服务器的每次交互中根据请求中的标识符找到它的会话。一个新的会话在客户端请求后被创建,会话一直有效直到一段时间后客户端连接超时,或者会话直接失效例如客户退出访问服务器。
- Cluster集群:专用于配置Tomcat集群的元素,可用于Engine和Host容器中。
架构好处: 1. 便于组件生命周期的管理; 2. 简化配置,允许子容器继承父容器的配置。
- 一个 Server 可以有多个 Service
- 一个 Service 有多个 连接器 Connector
- 一个 Service 有一个引擎 Engine
- 一个引擎有多个 Host
- 一个 Host 有多个 Context
3.Tomcat启动参数说明
我们平时启动Tomcat过程是怎么样的?
- 复制WAR包至Tomcat webapp 目录。
- 执行starut.bat 脚本启动。
- 启动过程中war 包会被自动解压装载。
但是我们在Eclipse 或idea 中启动WEB项目的时候 也是把War包复杂至webapps 目录解压吗?显然不是,其真正做法是在Tomcat程序文件之外创建了一个部署目录,在一般生产环境中也是这么做的 即:Tomcat 程序目录和部署目录分开 。
我们只需要在启动时指定CATALINA_HOME 与 CATALINA_BASE 参数即可实现。
启动参数 | 描述说明 |
---|---|
JAVA_OPTS | jvm 启动参数 , 设置内存 编码等 -Xms100m -Xmx200m -Dfile.encoding=UTF-8 |
JAVA_HOME | 指定jdk 目录,如果未设置从java 环境变量当中去找。 |
CATALINA_HOME | Tomcat 程序根目录 |
CATALINA_BASE | 应用部署目录,默认为$CATALINA_HOME |
CATALINA_OUT | 应用日志输出目录:默认$CATALINA_BASE/log |
CATALINA_TMPDIR | 应用临时目录:默认:$CATALINA_BASE/temp |
可以编写一个脚本 来实现自定义配置:
演示自定义启动Tomcat
- 下载并解压Tomcat
- 创建并拷贝应用目录
- 创建Tomcat.sh
- 编写Tomcat.sh
- chmod +x tomcat.sh 添加执行权限
- 拷贝conf 、webapps 、logs至应用目录。
- 执行启动测试。
#!/bin/bash
export JAVA_OPTS="-Xms100m -Xmx200m"
export JAVA_HOME=/root/svr/jdk/
export CATALINA_HOME=/usr/local/apache-tomcat-8.5.34
export CATALINA_BASE="`pwd`"
case $1 in
start)
$CATALINA_HOME/bin/catalina.sh start
echo start success!!
;;
stop)
$CATALINA_HOME/bin/catalina.sh stop
echo stop success!!
;;
restart)
$CATALINA_HOME/bin/catalina.sh stop
echo stop success!!
sleep 2
$CATALINA_HOME/bin/catalina.sh start
echo start success!!
;;
version)
$CATALINA_HOME/bin/catalina.sh version
;;
configtest)
$CATALINA_HOME/bin/catalina.sh configtest
;;
esac
exit 0
二、Tomcat server.xml 配置详解
Tomcat的配置文件默认存放在/usr/local/tomcat/conf
目录中,主要有以下几个:
server.xml: Tomcat的主配置文件,包含Service, Connector, Engine, Realm, Valve, Hosts主组件的相关配置信息;
web.xml:遵循Servlet规范标准的配置文件,用于配置servlet,并为所有的Web应用程序提供包括MIME映射等默认配置信息;
tomcat-user.xml:Realm认证时用到的相关角色、用户和密码等信息;Tomcat自带的manager默认情况下会用到此文件;在Tomcat中添加/删除用户,为用户指定角色等将通过编辑此文件实现;
catalina.policy:Java相关的安全策略配置文件,在系统资源级别上提供访问控制的能力;
catalina.properties:Tomcat内部package的定义及访问相关的控制,也包括对通过类装载器装载的内容的控制;Tomcat6在启动时会事先读取此文件的相关设置;
logging.properties: Tomcat6通过自己内部实现的JAVA日志记录器来记录操作相关的日志,此文件即为日志记录器相关的配置信息,可以用来定义日志记录的组件级别以及日志文件的存在位置等;
context.xml:所有host的默认配置信息;
Server 的基本基本配置:
<Server>
<Listener /><!-- 监听器 -->
<GlobaNamingResources> <!-- 全局资源 -->
</GlobaNamingResources
<Service> <!-- 服务 用于 绑定 连接器与 Engine -->
<Connector 8080/> <!-- 连接器-->
<Connector 8010 /> <!-- 连接器-->
<Connector 8030/> <!-- 连接器-->
<Engine> <!-- 执行引擎-->
<Logger />
<Realm />
<host "www.tl.com" appBase=""> <!-- 虚拟主机-->
<Logger /> <!-- 日志配置-->
<Context "/luban" path=""/> <!-- 上下文配置-->
</host>
</Engine>
</Service>
</Server>
配置样例:
<Server port=”8005″ shutdown=”SHUTDOWN”>
<Listener className=”org.apache.catalina.core.JasperListener” />
<Listener className=”org.apache.catalina.mbeans.ServerLifecycleListener” />
<Listener className=”org.apache.catalina.mbeans.GlobalResourcesLifecycleListener” />
<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″
maxThreads=”150″ connectionTimeout=”20000″
redirectPort=”8443″/>
<Engine name=”Catalina” defaultHost=”localhost”>
<Host name=”localhost” appBase=”webapps”
unpackWARs=”true” autoDeploy=”true”
xmlValidation=”false” xmlNamespaceAware=”false”>
</Host>
</Engine>
</Service>
</Server>
<Server port=”8005″ shutdown=”SHUTDOWN”>
<Listener className=”org.apache.catalina.core.AprLifecycleListener” SSLEngine=”on” />
<Listener className=”org.apache.catalina.core.JasperListener” />
<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=”8009″ protocol=”AJP/1.3″ 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 "%r" %s %b” />
</Host>
</Engine>
</Service>
</Server>
元素说明:
server
- root元素:server 的顶级配置
主要属性:
- port:执行关闭命令的端口号
- shutdown:关闭命令
[ ] 演示shutdown的用法
<Server port="8005" shutdown="SHUTDOWN" debug="0">
1>className: 指定实现org.apache.catalina.Server接口的完全限定类的名称.默认值为org.apache.catalina.core.StandardServer
2>port: 服务器等待关机命令的TCP/IP端口号。设置为-1以禁用关闭端口。默认仅允许通过本机访问,默认为8005;
3>shutdown: 必须通过TCP/IP连接接收到指定端口号的命令字符串,以关闭Tomcat。默认为SHUTDOWN
4>address: 服务器等待关机命令的TCP/IP地址。如果没有指定地址,则使用localhost。
#基于telent 执行SHUTDOWN 命令即可关闭
telent 127.0.0.1 8005
SHUTDOWN
service
服务:将多个connector 与一个Engine组合成一个服务,可以配置多个服务。
**<Service name="Catalina"\>**
**<Service name="Apache"\>**
第一个<Service>处理所有直接由Tomcat服务器接收的web客户请求.
第二个<Service>处理所有由Apahce服务器转发过来的Web客户请求 .
1>className 指定实现org.apahce.catalina.Service接口的类.默认为org.apahce.catalina.core.StandardService
2>name: 定义此服务的名称,此名字也会在产生相关的日志信息时记录在日志文件当中。与特定服务器关联的每个服务的名称必须是唯一的。
Connector
连接器:用于接收 指定协议下的连接 并指定给唯一的Engine 进行处理。
主要属性:
- protocol 监听的协议,默认是http/1.1
- port 指定服务器端要创建的端口号
- minThread 服务器启动时创建的处理请求的线程数
- maxThread 最大可以创建的处理请求的线程数
- enableLookups 如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址
- redirectPort 指定服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号
- acceptCount 指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理
- connectionTimeout 指定超时的时间数(以毫秒为单位)
SSLEnabled 是否开启 sll 验证,在Https 访问时需要开启。
[ ] 演示配置多个Connector
在 /Users/zhangchuanqiang/work/ApacheTomcat/conf 下的 server.xml 文件里增加一个 Connector
每个增加的 Connector 对应的线程等参数都是独立的
<Connector port="8860" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8862"
URIEncoding="UTF-8"
useBodyEncodingForURI="true"
compression="on" compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
maxThreads="1024" minSpareThreads="200"
acceptCount="800"
enableLookups="false"
/>
Tomcat 支持三种协议:HTTP/1.1、HTTP/2.0、AJP
(1) BIO HTTP/1.1 Connector配置
一个典型的配置如下:
<Connector port=”8080” protocol=”HTTP/1.1” maxThreads=”150” conn ectionTimeout=”20000” redirectPort=”8443” />
其它一些重要属性如下:
acceptCount : 接受连接request的最大连接数目,默认值是10
address : 绑定IP地址,如果不绑定,默认将绑定任何IP地址
allowTrace : 如果是true,将允许TRACE HTTP方法
compressibleMimeTypes : 各个mimeType, 以逗号分隔,如text/html,text/xml
compression : 如果带宽有限的话,可以用GZIP压缩
connectionTimeout : 超时时间,默认为60000ms (60s)
maxKeepAliveRequest : 默认值是100
maxThreads : 处理请求的Connector的线程数目,默认值为200
如果是SSL配置,如下:
<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol = "TLS"
address="0.0.0.0"
keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks"
keystorePass="changeit" />
其中,keystoreFile为证书位置,keystorePass为证书密码
(2) NIO HTTP/1.1 Connector配置
<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11NioProtocol” maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”/>
(3) Native APR Connector配置
ARP是用C/C++写的,对静态资源(HTML,图片等)进行了优化。所以要下载本地库tcnative-1.dll与openssl.exe,将其放在%tomcat%\bin目录下。
下载地址是:http://tomcat.heanet.ie/native/1.1.10/binaries/win32/
在server.xml中要配置一个Listener,如下图。这个配置tomcat是默认配好的。
<!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
配置使用APR connector
<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11AprProtocol”
maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”/>
如果配置成功,启动tomcat,会看到如下信息: org.apache.coyote.http11.Http11AprProtocol init
(4) AJP Connector配置
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
Engine
引擎:用于处理连接的执行器,默认的引擎是catalina。一个service 中只能配置一个Engine。
主要属性:name 引擎名称 defaultHost 默认host
Host
虚拟机:基于域名匹配至指定虚拟机。类似于nginx 当中的server,默认的虚拟机是localhost.
主要属性:
- 演示配置多个Host
<Host name="www.luban.com" appBase="/usr/www/luban"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="www.luban.com.access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
Context
应用上下文:一个host 下可以配置多个Context ,每个Context 都有其独立的classPath。相互隔离,以免造成ClassPath 冲突。
主要属性:
- 演示配置多个Context
<Context docBase="hello" path="/h" reloadable="true"/>
Valve
阀门:可以理解成request 的过滤器,具体配置要基于具体的Valve 接口的子类。以下即为一个访问日志的Valve.
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="www.luban.com.access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
三、Tomcat IO模型介绍
知识点:
- Tomcat支持的IO模型说明
- BIO 与NIO的区别
- IO模型源码解读
1、Tomcat支持的IO模型说明
描述 | |
---|---|
BIO | 阻塞式IO,即Tomcat使用传统的java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数目小且固定架构。 |
NIO | 非阻塞式IO,jdk1.4 之后实现的新IO。该模式基于多路复用选择器监测连接状态在通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat 8.0之后默认采用该模式 |
APR | 全称是 Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。可以简单地理解为,Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作。使用需要编译安装APR 库 |
AIO | 异步非阻塞式IO,jdk1.7后之支持 。与nio不同在于不需要多路复用选择器,而是请求处理线程执行完程进行回调调知,已继续执行后续操作。Tomcat 8之后支持。 |
使用指定IO模型的配置方式:
配置 server.xml 文件当中的 修改即可。
默认配置 8.0 protocol=“HTTP/1.1” 8.0 之前是 BIO 8.0 之后是NIO
BIO
protocol=“org.apache.coyote.http11.Http11Protocol“
NIO
protocol=”org.apache.coyote.http11.Http11NioProtocol“
AIO
protocol=”org.apache.coyote.http11.Http11Nio2Protocol“
APR
protocol=”org.apache.coyote.http11.Http11AprProtocol“
2、BIO 与NIO有什么区别
分别演示在高并发场景下BIO与NIO的线程数的变化?
演示数据:
每秒提交数 | BIO执行线程 | NIO执行线程 | |
---|---|---|---|
预测 | 200 | 200 | 50 |
实验环境 | 200 | 48 | 37 |
生产环境 | 200 | 419 | 23 |
结论:
BIO 线程模型讲解
NIO 线程模型讲解
BIO 源码解读
- Http11Protocol Http BIO协议解析器
- JIoEndpoint
- Acceptor implements Runnable
- SocketProcessor implements Runnable
- Http11NioProtocol Http Nio协议解析器
- NioEndpoint
- Acceptor implements Runnable
- Poller implements Runnable
- SocketProcessor implements Runnable
四、Tomcat 处理一个Http请求的过程
假设来自客户的请求为:http://localhost:8080/test/index.jsp 请求被发送到本机端口8080
- 用户点击网页内容,请求被发送到本机端口 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
package Server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.StringTokenizer;
public class TomcatServer {
private final static int PORT = 8080;
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(PORT);// 根据端口号启动一个serverSocket
ServletHandler servletHandler = new ServletHandler(server);
servletHandler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
private static class ServletHandler extends Thread {
ServerSocket server = null;
public ServletHandler(ServerSocket server) {
this.server = server;
}
@Override
public void run() {
while (true) {
try {
Socket client = null;
client = server.accept();// ServerSocket阻塞等待客户端请求数据
if (client != null) {
try {
System.out.println("接收到一个客户端的请求");
// 根据客户端的Socket对象获取输入流对象。
// 封装字节流到字符流
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
// GET /test.jpg /HTTP1.1
// http请求由三部分组成,分别是:请求行、消息报头、请求正文。
// 这里取的第一行数据就是请求行。http协议详解可以参考http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html说的很详细
String line = reader.readLine();
System.out.println("line: " + line);
// 拆分http请求路径,取http需要请求的资源完整路径
String resource = line.substring(line.indexOf('/'), line.lastIndexOf('/') - 5);
System.out.println("the resource you request is: " + resource);
resource = URLDecoder.decode(resource, "UTF-8");
// 获取到这次请求的方法类型,比如get或post请求
String method = new StringTokenizer(line).nextElement().toString();
System.out.println("the request method you send is: " + method);
// 继续循环读取浏览器客户端发出的一行一行的数据
while ((line = reader.readLine()) != null) {
if (line.equals("")) {// 当line等于空行的时候标志Header消息结束
break;
}
System.out.println("the Http Header is : " + line);
}
// 如果是POST的请求,直接打印POST提交上来的数据
if ("post".equals(method.toLowerCase())) {
System.out.println("the post request body is: " + reader.readLine());
} else if ("get".equals(method.toLowerCase())) {
// 判断是get类型的http请求处理
// 根据http请求的资源后缀名来确定返回数据
// 比如下载一个图片文件,我这里直接给定一个图片路径来模拟下载的情况
if (resource.endsWith(".jpg")) {
transferFileHandle("d://1.jpg", client);
closeSocket(client);
continue;
} else {
// 直接返回一个网页数据
// 其实就是将html的代码以字节流的形式写到IO中反馈给客户端浏览器。
// 浏览器会根据http报文“Content-Type”来知道反馈给浏览器的数据是什么格式的,并进行什么样的处理
PrintStream writer = new PrintStream(client.getOutputStream(), true);
writer.println("HTTP/1.0 200 OK");// 返回应答消息,并结束应答
writer.println("Content-Type:text/html;charset=utf-8");
writer.println();
// writer.println("Content-Length:" +
// html.getBytes().length);// 返回内容字节数
writer.println("<html><body>");
writer.println("<a href='www.baidu.com'>百度</a>");
writer.println(
"<img src='https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png'></img>");
writer.println("</html></body>");
// writer.println("HTTP/1.0 404 Not
// found");// 返回应答消息,并结束应答
writer.println();// 根据 HTTP 协议, 空行将结束头信息
writer.close();
closeSocket(client);// 请求资源处理完毕,关闭socket链接
continue;
}
}
} catch (Exception e) {
System.out.println("HTTP服务器错误:" + e.getLocalizedMessage());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void closeSocket(Socket socket) {
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
System.out.println(socket + "离开了HTTP服务器");
}
private void transferFileHandle(String path, Socket client) {
File fileToSend = new File(path);
if (fileToSend.exists() && !fileToSend.isDirectory()) {
try {
// 根据Socket获取输出流对象,将访问的资源数据写入到输出流中
PrintStream writer = new PrintStream(client.getOutputStream());
writer.println("HTTP/1.0 200 OK");// 返回应答消息,并结束应答
writer.println("Content-Type:application/binary");
writer.println("Content-Length:" + fileToSend.length());// 返回内容字节数
writer.println();// 根据 HTTP 协议, 空行将结束头信息
FileInputStream fis = new FileInputStream(fileToSend);
byte[] buf = new byte[fis.available()];
fis.read(buf);
writer.write(buf);
writer.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
拓展资料