classLoader
commonLoader:加载的jar可以提供给tomcat本身和其下面各个项目使用
catalinaLoader:加载的jar只能提供给tomcat本身
sharedLoader:加载的jar包可以给各个项目使用
他们三个都是urlclassloader类型
在bootStrap启动期间 将线程上下文的classloader设置为catalinaLoader,
并使用catalinaLoader加载我们的catalina这个类,这就意味着这个类不会被我们项目中使用
然后后期都是调用catalina方法的load和start方法来启动tomcat,此外我们还将catalina的parentClassLoader设置为sharedLoader
这是为了后期设置weappclassloader的父类埋下伏笔
相关名词解释
catalina.home:tomcat产品的安装目录,bin 和 lib 目录被多个tomcat示例公用
catalina.base:是tomcat启动过程中需要读取的各种配置及日志的根目录,其它目录conf、logs、temp、webapps和work 每个Tomcat实例必须拥有其自己独立的备份。
实现在一台机器上运行多个tomcat实例的目的。
主要就是利用catalina.base,因为它是Tomcat启动过程中读取各自配置的根目录
java.io.tmpdir:获取操作系统缓存的临时目录,不同操作系统的缓存临时目录不一样。
user.dir:则是获取当前tomcat容器启动的位置
SystemLogHandler:
startCapture方法:首先从resume(stack)中获取一个captureLog对象,如果该stack为空则new一个captureLog对象然后从logs(threadLocal)获取stack<caputure>对象,如果为空则创建一个
stopCapture方法:从threadlocal获取stack<caputure>,然后弹出一个CaptureLog 然后写出期间的打印信息
重置CaptureLog存入reuse。
这个SystemLogHanlder,可以收集一段代码内的打印信息,然后统一输出且不影响性能。
appBase和docBase的区别
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="D:\WebContent" sessionCookiePath="/" sessionCookieName="JSESSIONID" />
appBase:
1.appBase是host的属性,其会自动部署appbase指定的目录的项目
docBase
1.docBase是context的属性,指定这个项目的路径(绝对或相对路径)
2.访问路径我们可以通过path指定
外置的Tomcat的启动过程:
一、bootStrap的静态代码块和初始化:
1.静态代码块:主要就是设置catalina.home和catalina.base,其中获取了user.dir 作为上述2个参数未设置时候的备选方案
2.init方法:初始化三个主要的classloader,设置线程上下文的classloader为catalinaLoader生成catalina对象,并将sharedLoader设置为其parentClassLoader
二、调用catalina的load方法
1.初始化工作时候的temp目录-java.io.tmpdir
2.初始化jndi相关的目录
3.创建Digester对象,并使用该对象解析server.xml文件,最终创建Server对象
4.设置server的属性,catalina,catalinaHome和catalinaBase
5.优化system.out和system.error
6.调用server的init方法
三、调用catalina的start方法
1.server的start方法调用
2.如果需要使用钩子则注册一个CatalinaShutdownHook,该钩子会在触发的时候调用catalina的stop方法
,该方法会先删除钩子(因为有可能不是钩子线程调用的stop方法,这样会导致stop调用两次)然后调用server的stop和destory方法。
3.然后调用await和stop方法
4.await方法主要就是根据通过循环判断标识位来判断是否需要结束tomcat或者通过socket监听关闭端口来判断
内置的Tomcat启动过程
一、创建WebServer(内部包含tomcat类,所以这个webServer类似于Facade)
1.创建tomcat类。该类主要是服务于内置或者测试的starter,包含我们基础配置信息,也是用来启动我们的server服务,并且可以添加container的类。
2.设置tomcat的baseDir
3.设置connector,默认协议是Http11NioProtocol类
4.创建一个standardserver,设置catalina.home和catalina.base
5.创建一个standardService,将connector和service互相绑定。
6.调整connector属性,比如端口,还有就是如果我们配置了地址,则协议需要绑定该地址,还有就是给connector
设置uriEncoding,ssl。还有是给协议设置compression。
7.我们也可以通过实现TomcatConnectorCustomizer接口来优化connector或者其内部协议,比如修改adpter或者endpoint
8.当然还有这个TomcatContextCustomizer接口 我们可以优化我们的context
9.上述的两个customizer,我们可以写个BeanPostProcessor,在TomcatServletWebServerFactory初始化之后,调用其add方法添加
10.将被自定义修改后的connector和service中的connector进行对比,如果对象被替换了 就将新的connector加入到service中否则就不用添加
11.禁止host自动部署,在设置engine的是设置了该engine的默认的host为localhost和realm
12.配置engine,首先设置backGroupProcessorDelay时长,这个会启动一个线程每隔一段时间执行后台任务
13.给engine设置valve
14.我们还可以给该service添加我们额外写的connector
15.设置我们context=TomcatEmbeddedContext,设置name和path,displayName,指定docBase
16.添加lifeCycleListeners和parentClassloader,设置webApploader(我们的webappclassLoader的创建者),以及webappclassLoader的类型TomcatEmbeddedWebappClassLoader
17.将webApploader的加载模式设置为true,这就代表我们的这边webappclassLoader采用的是双亲加载模式
18.注册DefaultServlet,将tomcatStarter和context绑定,配置context的valve,修改我们的context
19.最终将tomcat和port包装成tomcatWebServer返回,在tomcatWebServer的构造函数中好友初始化方法
20.该初始化方法主要是:启动tomcat的container,但是会删除connector这样就可以避免connector也被启动,启动一个后台线程每隔一段时间去检测是否关闭标识。
二、启动webServer
1.添加我们上述被删除的connector
2.启动我们的connector
tomcat启动过程就是 调用Server的init方法和start方法,所以我们着重分析这两个方法
一、server的init:
1.standardserver的init方法
- 设置JMX相关类
- 设置全局的字符串缓存
- 初始化globalNamingResources
- 从我们的sharedLoader中获取jar包资源时报包含MANIFEST,如果包含则添加到容器的集合中
- 初始化service的init方法
2.service的init方法
- engine的初始化
- 线程池的初始化
- mapperlistener的初始化
- connector的初始化
3.engine的初始化(少了host,context,wrapper的初始化,他们的初始化都是在各自start的时候先进行进行的)
- 创建Realm
- 重新配置startStopExecutor,其主要是帮忙启动和关闭子容器
4.host的初始化
- 配置startStopExecutor
5.context的初始化
- 配置startStopExecutor
- namingResources初始化
6.wrapper的初始化
- 配置startStopExecutor
7.connector的初始化
- 创建adapter并和protocolhandler绑定,其主要是用来获取httpServeletRequest和httpServeletResponse
- 设置默认的解析bodymethod==post
- protocolHandler的初始化
8.protocolHandler的初始化
- 判断是否需要升级协议
- 创建endpoint并初始化
- enpoint可以理解为专门负责处理socket,类似于netty的服务端或者客户端boostrap
- Nioendpoint的初始化-实际是调用bind方法
- 创建socket并给socket设置相关属性,比如接受和发送的缓冲区大小,超时,拒绝连接的地址等
- 让该socket绑定我们设置好的地址和端口,同时还设置了backlog=accpetAccount,也就是说最大可以有一定数目的
即将要连接的该socket,超过就拒绝。这个backlog对应的accept queue - 设置该通道为非阻塞模式
- 设置acceptorThreadCount(也就是acceptor线程默其专门获取socket连接)认为1,其对应我们的acceptor线程大小类似于nioeventloop,根据我们对netty的了解就算设置多了意义也不大
- 设置pollerThreadCount最小为1,其是专门接受acceptor线程传递过来的socket,然后将该scoekt注册到selector上面
- 初始化ssl
- 创建共享selector,然后创建一个NioBlockingSelector去包裹该selector
- NioBlockingSelector包含一个共享的多路复用器,BlockPoller线程
- BlockPoller线程的作用:专门处理ssl的channel上面注册的blocking的socket,其他的非ssl裁员poller线程去注册
二、server的start:
1.server的start方法
- 激活configure_start监听事件
- 设置状态为start
- 调用globalNamingResources的start方法
- 调用service的start方法
2.service的start方法
- 设置start启动标识
- 启动engine
- 启动线程池
- 启动mapperListener
- 启动connector
- engine的start
启动realm
- 迭代启动子容器
- 启动pipeline,设置启动标识,也就是给pipeline添加基本的vavle,并启动vavle(也就是设置启动标识)
- 每个容器都有一个starnardpipeline,每个容器都有一个baiscvavlve--standardXXvalve,他们的主要任务是
连接当前容器的子容器 - 启动ContainerBackgroundProcessor线程执行后台任务(目前只有engine容器可以启动该线程,其他子类没有)
- ContainerBackgroundProcessor的处理逻辑:对于container为context的容器 先调用其bind方法
- 调用所有container的backgroundProcess方法,而各个container的backgroundProcess内部还包含其他组建的backgroundProcess
比如cluster,realm,valve,Loader,Manager,WebResourceRoot,InstanceManager等 - 而context容器的bind方法的作用就是就是确保我们的webApploader中的webAppClassLoader与线程上下文的classloader一致
不一致的话就设置为一样的。这样的话我们热部署的时候classloader的变更 进而可以进行体会线程上下文中的classloader
- host的start方法
- 配置errorValve
- start子容器
- start我们的pipeline
- context的start方法
- 给该项目设置工作目录
- 设置WebResourceRoot并调用其start方法 主要是将这个context中的文件比如class和配置文件变成WebResource
留给classloader去加载 - 创建WebappLoader,并设置其内部的WebappClassLoader的加载模式为非双亲加载模式
- 检测context中的mainest文件,设置NamingContextListener
- 启动WebappLoader的start方法主要就是创建WebAppClassLoader,设置加载模式 classPaths,将classes和lib下面的
资源放入到localRepositories中 - 设置WebAppClassLoader的属性,然后将该classloader放入到线程上下文后期只要碰到任何需要加载的类都采用我们的classloader
- 启动子容器
- 启动pipeline
- 将sources,jarScanner放入到servletContext
- 执行ServletContainerInitializer的onStarup方法
- 启动filter和servlet
- 将webAppClassLoader替换为原始的classloader
-通过监听者ContextConfig的webConfig和applicationAnnotationsConfig方法加载项目 首先生成对应的web.xml文件(先从项目中寻找,没有在获取全局的) 然后获取利用spi机制去各个jar包中寻找META-INF/services/javax.servlet.ServletContainerInitializer 文件获取ServletContainerInitializer,这边使用webappclassloader去加载。 然后加载所有的/WEB-INF/classes的class,主要是处理这些class中的annotation/WebServlet ,annotation/WebFilter,annotation/WebListener。 然后在处理所有的lib目录的jar包,最后添加我们ServletContainerInitializer
- 回收resouce中的资源
- wrapper的start方法
- 启动器内部的cluster ,realm,pipeline和子容器的start方法
- 设置available=0代表可用
7.connector的start方法
- 检测端口是否符合要求
- 启动protocolHandler的start方法
8.protocolHandler的start方法
- 启动endpoint的start方法
- 启动一个异步线程AsyncTimeout,每隔1秒去检测我们的异步处理的Processor是否超时,如果超时则执行超时操作
- 该线程的优先级被设置为normal,且是后台线程
- Nioendpoint的start方法
- 设置nioendpoint的启动标识
- 设置processorCache,EventCache,nioChannels三个缓存
- 如果我们的tomcat没有设置线程池 则我们自己创建worker线程池
- 线程池的属性有minspareThreads,maxThreads
- 设置maxConnection的大小默认是1万
- 创建poller线程,数量可以设置
- 每个poller包含独立的多路复用器,他们从acceptor线程获取socket注册到自己的多路复用器,其类似于nioeventloop
- 开启acceptor线程
三、tomcat接受处理请求的全流程
-一、Acceptor线程携带endpoint,不断的循环获取socketChannel
- 1.首先判断endPoint是否还在running,如果不是则跳出循环
- 2.如果当前endpoint是暂停且还在running状态则现场沉睡50毫秒
- 3.在正式获取socketChannel的时候需要先去获取connection(通过maxConnection限制)
- 4.socketChannel是一个channel,获取的过程中如果发生io异常需要设置下errorDelay,即让主线程沉睡一会
- 5.当我们获取到socketchannel则需要绑定到poller线程上,绑定失败或者acceptor线程暂停或者关闭 则需要关闭该通道
-二、将socketChannel包装成Niochannel交给poller线程
- 1.将socketChannel设置为非阻塞
- 2.将socket的属性填充=超时,接受和发送buffer(属于socket的buffer),ReuseAddress ,tcpNoDelay等
- 3.尝试从缓存获取niochannel,并创建了一个SocketBufferHandler,其实作为applicaiton的buffer(包含发送,接受和堆外的buffer)
- 4.最终我们将SocketBufferHandler和socketchannel包装成niochannel注册到poller线程上
- 5.注册过程就是讲我们的niochannel和nioendpoint包装成NioSocketWrapper
- 6.然后给NioSocketWrapper设置poller线程,timeout,alive,ssl等属性,并注册read事件
- 从eventCache缓存中获取pollerEvent,并集成niochannel,NioSocketWrapper和注册事件。
- 8.然后将该pollerEvent放入poller线程的events内部集合,如果wakeupCounter=-1 则代表当前poller线程在沉睡我们需要唤醒
-三、Poller线程的run方法-将后续获取到的事件(链接事件)交给SocketProcessorBase处理
- 1.从我们的events集合中循环的执行PollerEvent的run和reset方法 然后将PollerEvent放入eventCache
- 2.run方法:第一次执行都是将我们的channel注册到该poller内部的多路复用器
- 3.reset方法:情况pollerEvent内部对象
- 4.设置wakeupCounter为-1 如果之前的值是大于0代表已经有其他的event加入进来 我们直接selectNow否则阻塞获取事件
- 5.设置wakeupCounter为0 然后判断是否该线程已经关闭如果是的话则cancel 所有已经发生的SelectionKey然后关闭多路复用器再跳出循环
- 6.否则判断是否有对应的SelectionKey或者其他的event加入
- 7.先处理SelectionKey,然后在查看是否超时
- 8.只有处理以下几种情况的timeout:nextExpiration已经过了,没有selectionKey或者其他event加入 或者socket已经开始clsoe
- 9.处理selectionkey的逻辑是:如果已经close了 则直接cancel,否则检测sk是否有效且需要携带附件,只处理读写事件
- 10.如果附件携带文件,则调用filechannel的transTo方法进行处理,其他事件则当成socket进行处理。
- 11.在处理sk中携带文件之前需要先取消之前该channel注册的事件,等待处理完成在且是支持keepalive的则重新注册该channel否则直接关闭
- 12.对于attacheMent是非文件的则先取消之前该channel注册的事件,并不会重新注册 因为事件已经处理完毕
- 13.上述attacheMent是非文件是先处理read在处理write,如果处理失败就直接cancel
- 14.然后从processorCache获取SocketProcessorBase,根据是否支持dispatch以及是否存在线程池来决定剩余的处理
是否在poller线程中做还是交给cotainer的线程池
-四、SocketProcessorBase的处理流程
- 1.首先进行ssl握手
- 2.获取对应的handler进行处理
- 3.对应的handler获取合适的Processor
- 4.调用Processor的process进行处理,最终交给service方法
- 五、Processor的service方法逻辑
- 1.获取requestIno,设置请求的stage,设置input和output的buffer
- 2.默认是支持keepAlive,读取请求的数据
- 3.最终通过adapter的service方法传递我们的原始request和response
- 六、然后将在第五步得到request和response交给Adapter(CoyoteAdapter),调用其service方法处理
- 1.首先将request和response转换成HttpServletRequest和HttpServletResponse
- 2.获取connector 通过connector得到container的vavle,然后交给他们执行
- 七、最终一层层调用到我们的standardWrapperValve的invoke方法
- 1.获取对应的servlet,这边就是我们的dispatchServlet
- 2.执行dispatchServlet的service方法,在此之前先执行过滤器
- 3.我们的controller方法被映射成地址和我们的请求地址匹配,最终可以得到method 进而反射执行方法完成调用
server.xml文件的各个标签的意义
我们的engine标签会配置一个默认的host
名称一般都是localhost,当我们用localhost去请求的时候
我们本地的系统有hosts配置文件可以将我们的localhost解析为实际的ip
默认都是127.0.0.1,当然我们也可以配置localhost1等127.0.0.1
MANIFEST
一般编写MANIFEST.MF文件只需要用到Manifest-Version(MF文件版本号)
、Main-Class(包含main方法的类)、Class-Path(执行这个jar包时的ClassPath,第三方依赖)
比如下面:
Manifest-Version: 1.0
Main-Class: test.Main
Class-Path: ./ ./lib/commons-collections-3.2.jar ./lib/commons-dbcp-1.2.2.jar ./lib/commons-lang-2.3.jar ./lib/commons-logging-1.1.jar
Tomcat中的backlog参数
在linux 2.2以前,backlog大小包括了半连接状态和全连接状态两种队列大小。
linux 2.2以后,分离为两个backlog来分别限制半连接SYN_RCVD状态的未完成连接队列大小
跟全连接ESTABLISHED状态的已完成连接队列大小。
当服务端接受到客户端的syn请求后放入syns的队列中,然后服务端回复syn+ack,等客户端收到ack后 再回复ack给服务端
则服务的就把sync中的半连接放入到accpet queue。一般backlog=完全队列
我们完全队列大小取值=min(backlog,somaxconn)
我们半完全队列大小=
table_entries = min(min(somaxconn,backlog),tcp_max_syn_backlog)
roundup_pow_of_two(table_entries + 1)
roundup_pow_of_two表示取最近的2的n次方的值,举例来说:假设somaxconn为128,backlog值为50,tcp_max_syn_backlog值为4096,则第一步计算出来的为50,
然后roundup_pow_of_two(50 + 1),找到比51大的2的n次方的数为64,所以最终半连接队列的长度是64。
connector容易混淆的名词解释
acceptorThreadCount:代表从socket的完全队列获取socket连接的线程数,其socket连接交给poller
pollerThreadCount:将该socket注册到selector上面,然后捕捉到后续的读写事件交给worker线程处理
maxThreads:则是指worker线程--真正去处理这些socket请求的线程个数
acceptCount: 代表acceptor线程从tcp完全队列里面取connection的限制(因为acceptorCount限制了队列大小)
maxConnections:每当获取到一个socket就代表得到一个connection,然后该connection只有在完成或者某些异常情况下才释放
,从这个角度来说如果不限制connection我们不停的拿socket处理也是不行的,这就是从java层面控制
Tomcat的reload机制
- 1.WebappLoader的backgroundProcess方法
- 2.首先检测是否支持热部署,其次检测项目是否有变化
- 3.变化的依据是检测jar包和我们的其他文件的变更时间,如果不一致则将当前的线程上下文中的classloader替换为加载
webApploader的classloader 也就是我们的sharedLoader - 4.然后调用standardContext的reload方法去加载
- 5.加载完成后将线程上下文的加载器设置为webAppclassloader
reload方法的主要逻辑
- 设置暂停标识,可以让request请求休息1秒
- 获取老的classloader 关闭后台线程(由于context没有后台线程所以不用关闭)
- 获取context的子容器进行stop
- 关闭过滤器和manager 等等
- 然后重新调用start方法 ,设置paused为false
网友评论