首页 > 代码库 > Tomcat
Tomcat
1. Tomcat 总体结构
从上图中可以看出 Tomcat 的心脏是两个组件:Connector 和 Container。Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。多个 Connector 和一个 Container 就形成了一个 Service,Service 的概念大家都很熟悉了,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了。所以整个 Tomcat 的生命周期由 Server 控制。目前咱们公司用的tomcat的版本Server version:Apache Tomcat/7.0.52.
2. Tomcat相关接口
2.1 Tomcat接口视图
Catalina:与开始/关闭shell脚本交互的主类,因此如果要研究启动和关闭的过程,就从这个类开始看起。
Server:是整个Tomcat组件的容器,包含一个或多个Service。
Service:Service是包含Connector和Container的集合,Service用适当的Connector接收用户的请求,再发给相应的Container来处理。
Connector:实现某一协议的连接器,如默认的有实现HTTP、HTTPS、AJP协议的。
Container:可以理解为处理某类型请求的容器,处理的方式一般为把处理请求的处理器包装为Valve对象,并按一定顺序放入类型为Pipeline的管道里。Container有多种子类型:Engine、Host、Context和Wrapper,这几种子类型Container依次包含,处理不同粒度的请求。另外Container里包含一些基础服务,如Loader、Manager和Realm。
Engine:Engine包含Host和Context,接到请求后仍给相应的Host在相应的Context里处理。
Host:就是我们所理解的虚拟主机。
Context:就是我们所部属的具体Web应用的上下文,每个请求都在是相应的上下文里处理的。
Wrapper:Wrapper是针对每个Servlet的Container,每个Servlet都有相应的Wrapper来管理。
可以看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper这些核心组件的作用范围是逐层递减,并逐层包含。
下面就是些被Container所用的基础组件:
Loader:是被Container用来载入各种所需的Class。
Manager:是被Container用来管理Session池。
Realm:是用来处理安全里授权与认证。
2.2 Service 和 Server接口重要方法
1 public abstract interface Service extends Lifecycle 2 { 3 public abstract Container getContainer(); 4 public abstract void setContainer(Container paramContainer); 5 public abstract String getInfo(); 6 public abstract String getName(); 7 public abstract void setName(String paramString); 8 public abstract Server getServer(); 9 public abstract void setServer(Server paramServer); 10 public abstract ClassLoader getParentClassLoader(); 11 public abstract void setParentClassLoader(ClassLoader paramClassLoader); 12 public abstract void addConnector(Connector paramConnector); 13 public abstract Connector[] findConnectors(); 14 public abstract void removeConnector(Connector paramConnector); 15 public abstract void addExecutor(Executor paramExecutor); 16 public abstract Executor[] findExecutors(); 17 public abstract Executor getExecutor(String paramString); 18 public abstract void removeExecutor(Executor paramExecutor); 19 }
从 Service 接口中定义的方法中可以看出,它主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,注意接口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的生命周期在一个 Lifecycle 的接口中控制。Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。 StandardService 中主要的几个方法实现的代码,下面是 setContainer 和 addConnector 方法的源码:
1 public void setContainer(Container container) { 2 Container oldContainer = this.container; 3 if ((oldContainer != null) && (oldContainer instanceof Engine)) //判断当前的这个 Service 有没有已经关联了 Container,如果已经关联了,那么去掉这个关联关系 4 ((Engine) oldContainer).setService(null); 5 this.container = container; 6 if ((this.container != null) && (this.container instanceof Engine)) 7 ((Engine) this.container).setService(this); 8 if (getState().isAvailable() && (this.container != null)) { 9 try { 10 this.container.start(); 11 } catch (LifecycleException e) { 12 // Ignore 13 } 14 } 15 if (getState().isAvailable() && (oldContainer != null)) { 16 try { 17 oldContainer.stop(); //如果这个 oldContainer 已经被启动了,结束它的生命周期 18 } catch (LifecycleException e) { 19 // Ignore 20 } 21 } 22 // Report this property change to interested listeners 23 support.firePropertyChange("container", oldContainer, this.container); 24 }
1 public void addConnector(Connector connector) { 2 synchronized (connectorsLock) { 3 connector.setService(this); 4 Connector results[] = new Connector[connectors.length + 1]; 5 System.arraycopy(connectors, 0, results, 0, connectors.length); 6 results[connectors.length] = connector; 7 connectors = results; 8 if (getState().isAvailable()) { 9 try { 10 connector.start(); 11 } catch (LifecycleException e) { 12 log.error(sm.getString( 13 "standardService.connector.startFailed", 14 connector), e); 15 } 16 } 17 // Report this property change to interested listeners 18 support.firePropertyChange("connector", null, connector); 19 } 20 }
|
Server类是提供一个接口让其他程序能够访问到这个service集合,同时要维护它所包含的所有的Service的生命周期,包括如何初始化,如何结束服务,如何找到别人要访问的Service。它的标准实现类 StandardServer 实现server这些方法,同时也实现了LifecycleMBeanBase接口的所有方法,LifecycleMBeanBase接口实现了MBeanRegistration并继承了LifecycleBase接口。LifecycleBase接口实现了Lifecycle接口。所以Lifecycle接口的方法实现都在其它组件中,所以组件的生命周期由包含它的父组件控制。如Server的Start方法和Stop方法就会调用Service组件的相关方法。下面主要看一下 StandardServer 重要的一个方法 addService 的实现:
1 @Override 2 public void addService(Service service) { 3 service.setServer(this); //关联Server和Service 4 synchronized (servicesLock) { 5 Service results[] = new Service[services.length + 1]; 6 System.arraycopy(services, 0, results, 0, services.length); 7 results[services.length] = service; 8 services = results; 9 if (getState().isAvailable()) { 10 try { 11 service.start(); 12 } catch (LifecycleException e) { 13 // Ignore 14 } 15 } 16 // Report this property change to interested listeners 17 support.firePropertyChange("service", null, service); 18 } 19 }
3. Tomcat重要配置文件Server.xml
3.1 server.xml
Tomcat服务器是由一系列可配置的组件构成,其中核心的组件是Catalina Servlet容器,它是所有其他Tomcat组件的顶层容器。Tomcat的组件在<CATALINA_HOME>/conf/server.xml文件中进行配置,每个Tomcat组件在server.xml文件中对应一种配置元素。一下代码以XML的形式展示各种Tomcat组件之间的关系(以下是fat环境的tomcat的server.xml配置):
1 <?xml version=‘1.0‘ encoding=‘utf-8‘?> 2 <Server port="8005" shutdown="SHUTDOWN"> 3 <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> 4 <Listener className="org.apache.catalina.core.JasperListener" /> 5 <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> 6 <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> 7 <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> 8 <GlobalNamingResources> 9 <Resource name="UserDatabase" auth="Container" 10 type="org.apache.catalina.UserDatabase" 11 description="User database that can be updated and saved" 12 factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 13 pathname="conf/tomcat-users.xml" /> 14 </GlobalNamingResources> 15 <Service name="Catalina"> 16 <Connector port="8080" protocol="HTTP/1.1" 17 socketBuffer="9000" 18 enableLookups="false" 19 tcpNoDelay="true" 20 minSpareThreads="20" 21 maxThreads="1024" 22 connectionTimeout="5000" 23 maxHttpHeaderSize="32768" 24 acceptCount="150" 25 redirectPort="8443" /> 26 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> 27 <Engine name="Catalina" defaultHost="localhost"> 28 <Realm className="org.apache.catalina.realm.LockOutRealm"> 29 <Realm className="org.apache.catalina.realm.UserDatabaseRealm" 30 resourceName="UserDatabase"/> 31 </Realm> 32 <Host name="localhost" appBase="/opt/ctrip/web/work" 33 unpackWARs="true" autoDeploy="true"> 34 35 <Context path="/btbjob" crossContext="true" reloadable="true" docBase="/opt/ctrip/web/jobws.btborder.flight.charter-jobws-btborder/work"/> 36 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="/opt/logs/tomcat" 37 prefix="access." suffix=".log" 38 pattern="%h %l %u %t "%r" %s %b %T" /> 39 </Host> 40 </Engine> 41 </Service> 42 </Server>
|
3.2 Server.xml元素详解
元素名
|
属性
|
解释
|
server |
port |
指定一个端口,这个端口负责监听关闭tomcat的请求 |
shutdown |
指定向端口发送的命令字符串 |
|
service |
name |
指定service的名字 |
Connector(表示客户端和service之间的连接) |
port |
指定服务器端要创建的端口号,并在这个断口监听来自客户端的请求 |
protocol |
浏览器请求必须为HTTP1.1 如果使用AJP处理器,该值必须为AJP/1.3 |
|
socketBuffer |
设Socket输出缓冲区的大小(以字节为单位),-1表示禁止缓冲,默认值为9000字节 |
|
tcpNoDelay |
为true时,可以提高性能。默认值为true |
|
minSpareThreads |
设当连接器第一次启协创建线程的数目,确保至少有这么多的空闲线程可用。默认值为4 |
|
maxHttpHeaderSize |
HTTP请求和响应头的最大量,以字节为单位,默认值为4096字节 |
|
maxSpareThreads |
允许存在空闲线程的最大数目,默认值为50 |
|
enableLookups |
如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址 |
|
redirectPort |
指定服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号 |
|
acceptCount |
指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理 |
|
connectionTimeout |
指定超时的时间数(以毫秒为单位) |
|
Engine(表示指定service中的请求处理机,接收和处理来自Connector的请求) |
defaultHost |
指定缺省的处理请求的主机名,它至少与其中的一个host元素的name属性值是一样的 |
Context(表示一个web应用程序,通常为WAR文件,关于WAR的具体信息见servlet规范) |
docBase |
应用程序的路径或者是WAR文件存放的路径 |
path |
表示此web应用程序的url的前缀,这样请求的url为http://localhost:8080/path/**** |
|
reloadable |
这个属性非常重要,如果为true,则tomcat会自动检测应用程序的/WEB-INF/lib 和/WEB-INF/classes目录的变化,自动装载新的应用程序,我们可以在不重起tomcat的情况下改变应用程序 |
|
host(表示一个虚拟主机) |
name |
指定主机名 |
appBase |
应用程序基本目录,即存放应用程序的目录 |
|
autoDeploy |
指示Tomcat运行时,如有新的WEB程序加开appBase指定的目录下,是否为自动布署,默认值为true |
|
unpackWARs |
如果为true,则tomcat会自动将WAR文件解压,否则不解压,直接从WAR文件中运行应用程序 |
|
Logger(表示日志,调试和错误信息) |
className |
指定logger使用的类名,此类必须实现org.apache.catalina.Logger 接口 |
prefix |
指定log文件的前缀 |
|
suffix |
指定log文件的后缀 |
|
timestamp |
如果为true,则log文件名中要加入时间,如下例:localhost_log.001-10-04.txt |
|
Realm(表示存放用户名,密码及role的数据库) |
className |
指定Realm使用的类名,此类必须实现org.apache.catalina.Realm接口 |
Valve(功能与Logger差不多,其prefix和suffix属性解释和Logger 中的一样) |
className |
指定Valve使用的类名,如用org.apache.catalina.valves.AccessLogValve类可以记录应用程序的访问信息 |
directory |
指定log文件存放的位置 |
|
pattern |
有两个值,common方式记录远程主机名或ip地址,用户名,日期,第一行请求的字符串,HTTP响应代码,发送的字节数。combined方式比common方式记录的值更多 |
3.4 配置HTTPS连接器
4. Tomcat部署方式
4.1 Tomcat部署
tomcat部署方式有三种:
- 在tomcat中的conf目录,修改server.xml的<host/>节点添加<Context />配置.
<Context path="/hello" docBase="D:eclipse3.2.2forwebtoolsworkspacehelloWebRoot" debug="0" privileged="true">
</Context> - 将web项目文件拷贝到webapps目录下.
- 在conf目录中,新建Catalina\localhost目录,在该目录创建一个xml文件,名字随意,只要和当前文件不重名,该xml文件的内容为:
<Context path="/hello" docBase="D:eclipse3.2.2forwebtoolsworkspacehelloWebRoot" debug="0" privileged="true">
</Context>
注:一个Tomcat若要部署多个项目,在Server.xml文件添加<Context/>如:<Context path="/btbjob_one" crossContext="true" reloadable="true" docBase="/opt/ctrip/web/jobws.btborder.flight.charter-jobws-btborder/work"/>
<Context path="/btbjob_two" crossContext="true" reloadable="true" docBase="/opt/ctrip/web/jobws.btborder.flight.charter-jobws-btborder/work"/>
4.2 Tomcat热部署
热部署是指在你对项目代码(不论是JSP、JAVA类,甚至是配置文件)进行了修改时,在不重启WEB服务器前提下能让修改生效。
- 修改Tomcat安装目录下的conf下的context.xml,在<Context>节点上添加reloadable="true"属性
-
-
进入myEclipse - > preference -> Tomcat 7.x -> JDK , 在Optional Java VM arguments中填入 -Dcom.sun.management.jmxremote=true 如下图:
5. Tomcat 处理Http请求流程
Tomcat Server处理请求的过程,如下以HTTP请求为例:
由上图可见,Request的解析和加工过程不是在一个方法里搞定,而是信息流动过程中逐步解析的,不同层次的处理器解析不同层次的信息,在解析过程同时做了些判断和拦截的工作,比如当发现是要访问WEB-INF的资源,会直接返回错误给客户端等等。
6. Tomcat开发中常见问题分享
6.1 Tomcat常见错误
tomcat启动时错误
1 The JAVA_HOME environment variable is not defined This environment variable is needed to run this program;
Re:没有在tomcat的配置文件.bash_profile中设置环境变量JAVA_HOME,具体设置方法为:加入如下几行:
JAVA_HOME=/home/tomcat/j2sdk1.4.2_08(具体值要以实际的jdk安装路径为准)
export JAVA_HOMECLASSPATH=/home/tomcat/j2sdk1.4.2_08/lib/tools.jar:/home/tomcat/j2sdk1.4.2_08/lib/dt.jar
export CLASSPATH
2 Error occurred during initialization of VM Could not reserve enough space for object heap
Re:在tomcat的bin目录下,catalina.sh文件的tomcat内存参数配置过大,超过机器可用内存总数造成,修改到适当的值即可,修改的参数为:JAVA_OPTS="-Xms50m -Xmx60m"
3 tomcat启动时报某个目录没有权限,启动失败,或者不能执行某些jsp页
Re:tomcat需要tomcat用户具有一些目录和文件的相应权限, 所有目录应该具有读写执行(浏览)的权限,jsp,class文件应该最少具有读权限, 一些文件需要写权限,下面是已知的需要读写权限文件的列表:
$CATALINA_HOME/logs下所有文件
$CATALINA_HOME/work下所有文件
$CATALINA_HOME/publish/main/count.txt文件
$CATALINA_HOME/publish/chatroom/resource下的所有.xml文件
所有上传图片目录都需要写权限。
改变文件目录权限的方法:执行下面命令,设置所有的tomcat安装下的文件和目录,可以保证执行,但是不是很安全。
1. 设置tomcat上级目录/opt所有用户都有读写执行权限:
chmod 777 [tomcat的上级目录]
2. 设置tomcat目录下的所有文件的属主为deploy:
chown -R tomcat [tomcat安装目录]
3. 设置所有tomcat下的所有文件和目录的属主(deploy)具有读写执行权限
chmod 700 -R /opt/tomcat
注意:公司测试环境tomcat所属主为deploy,切勿使用root主去启动tomcat.
4 执行startup.sh文件后告诉地址已经使用,导致tomcat不能启动
Re:可能是前一次执行./shutdown.sh文件关闭tomcat时没有停止已经启动的Java进程,而这个进程仍然在监听tomcat所使用的端口,或者有另外一个tomcat正在运行,server.xml文件中的监听端口和当前tomcat冲突。
使用下面命令查看当前系统是否有正在监听的端口(8080--webcache, 90--www或http).
1. 列出所有系统正在监听的端口和绑定的地址netstat -l
2. 找出当前系统中的所有进程,管道符号过滤输出显示包含java字符串的行
ps -A |grep java
3. 结束一个指定的进程:
kill [进程号]
4. 强制杀死一个进程:
kill -9 [进程号]
如果确定不是别的tomcat运行导致的冲突或者没有java进程运行可以再次运行startup.sh文件启动tomcat
tomcat运行时错误
1. org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (Network error IOException: Connection refused: connect)
2. Caused by: java.sql.SQLException: Network error IOException: Connection refused: connect
3. Caused by: java.net.ConnectException: Connection refused: connect
Re:数据库未开情况下运行tomcat出现的问题,把数据库打开就行了
4.java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit(IILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V
at net.sf.cglib.core.ClassEmitter.begin_class(ClassEmitter.java:77) 这个问题是最最常见的,第一次整合ssh的时候会发现这个问题,有时候删除掉相关的包还是会冲突。所以解决办法我一般是:
Re:把MyEclipse中的hibernate中的有关ASM的包全部删除,将spring中asm包拷贝进去重启就Ok了,有时候需要删除其他文件,到网上找找吧;
还有个解决办法:Spring 和 Hibernate 共用的一些 jar 文件发生了版本冲突, 删除 WEB-INF/lib/asm-2.2.3.jar 然后重启 Tomcat.
5 .javax.servlet.jsp.JspException: Invalid argument looking up property usersVO.account of bean loginForm
Re,一般提示这种错误表示VO中的form没有实例化,在reset方法中new一下就Ok了。养成良好的编码习惯可以避免这种简单错误。
6.严重: Exception loading sessions from persistent storage
Re:tomcat安装目录\work\Catalina\localhost\{webAppName}\SESSIONS.ser,删除此文件
6.2 Tomcat 常见的错误码
出现404错误是因为你所请求的页面不可用!这是response对象的http响应中的状态行404表示当前请求的页面不可用!200表示测试通过。500表示服务器内部发生错误等!
具体错误码对用的信息参见:http://blog.csdn.net/lidawei201/article/details/8482006
6.3 Tomcat 开发中遇中文乱码问题
tomcat8之前的默认编码是ISO-8859-1,tomcat8的默认编码是UTF-8.
解决中文乱码的问题:如果请求方式GET.可以更改${CATALINA_HOME}/conf/server.xml中对应的Connector中指定URIEncoding="UTF-8"参数.
如果请求方式POST.可以通过Filter来设置编码.Tomcat7.x (已经将这个Filter加入Tomcat内置了,直接复制一下代码到你的项目web.xml中)
1 <filter> 2 <filter-name>setCharacterEncodingFilter</filter-name> 3 <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> 4 <init-param> 5 <param-name>encoding</param-name> 6 <param-value>UTF-8</param-value> 7 </init-param> 8 </filter> 9 <filter-mapping> 10 <filter-name>setCharacterEncodingFilter</filter-name> 11 <url-pattern>/*</url-pattern> 12 </filter-mapping>
如果这样设置还是有乱码问题,需要考虑下数据库的编码.
Tomcat