设为首页 收藏本站
查看: 400|回复: 0

[经验分享] how tomcat works 笔记

[复制链接]

尚未签到

发表于 2017-1-24 10:44:43 | 显示全部楼层 |阅读模式
  1 tomcat是模块化的服务器,由两大模块:connector和container。
  connector主要用来处理网络通讯,接收tcp请求,构造httprequest和httpresponse对象,然后传递给container。
  容器的主要任务是触发Servlet.service(ServletRequest arg0, ServletResponse arg1),响应用户请求。
  connector和container是解耦的,通过接口规范协作。
  org.apache.catalina.Container是一个接口,按照范围从大到小,他的实现类有四个,这几个实现类的关系类似于组合模式的树结构。范围最大的是Engine,他表示整个catalina servlet引擎,他包含一个或者多个Host、Context;Host表示一个虚拟主机,包含一个或多个Context(好比在一个tomacat服务器下部署多个应用);Context表示一个独立的ServletContext(也就是一个应用,对应的部署描述符是web.xml),他包含一个或多个Wrapper(当需要多个不同的servlet的时候,就会引用多个wrapper子容器)。wraper表示web应用中部署描述符定义的一个servlet,他控制这个servlet的生命周期,负责创建和销毁servlet。wraper不能再有子容器。要部署一个web应用,不一定需要所有的容器。
  一般来说,一个connector会引用一个容器,这个容器可能是层级结构的。当接受到客户端请求的时候,connector会调用容器的invoke,容器引用一个pipeline,把请求传递给pipeline,、在pipeline里面顺序执行valve链。如果容器是层级结构的,那么父容器的某个valve会根据某种映射选择一个子容器,子容器执行和父容器类似的过程,请求会横向或者纵向的传递下去。例如,当context接受到一个请求时,他会调用自身的pipeline,执行StandardContextValve,这个value会根据规则找到合适的子容器wrapper,再由wrapper处理请求。
  何时需要engine呢?当需要监控到整个engine的请求或者使用独立的connector却需要支持多个host的时候;
  用host的时机类似。当和web server 如apache结合使用的时候,一般不需要engine或者host,因为连接器会利用web server的功能确定合适的context或者wrapper.
  
DSC0000.jpg
  
DSC0001.png
  2 http 服务器实现的主体是Connector.start()(基于server socket),connector.CoyoteAdapter.service(Request
req, Response
res)内部实现http的处理逻辑。 
  3 官方网站的学习资料十分重要。不可不看
  4
  核心类图
  
DSC0002.png
  从类图结构可以看出,tomcat具有良好的模块化结构,注重针对接口编程,类的设计具有充分的可扩展性。抽象和实现分离,在通信层面支持bio,nio,apr。
  5 处理流程
  7.0.27版本的http请求处理流程
  Connector.initInternal()-》Connector.startInternal()
  -》AbstractProtocol.start()-》AbstractEndpoint.start()此处采用了模板方法,AbstractEndpoint.startInternal()具体实现由子类定义 -》以http11为例子进行推演JIoEndpoint.startInternal()-》AbstractEndpoint.startAcceptorThreads启动接收器-》JIoEndpoint.Acceptor.run()-》当有连接请求进来时处理请求,JIoEndpoint.processSocket(Socket
socket),以线程池的方式处理getExecutor().execute(new SocketProcessor(wrapper))-》JIoEndpoint.Handler.process(SocketWrapper<Socket>
socket, SocketStatus
status),此处handler的具体实现类对应Http11ConnectionHandler extends AbstractConnectionHandler-》
  AbstractProtocol.AbstractConnectionHandler.process(SocketWrapper<S>
socket, SocketStatus
status)对请求进行处理-》AbstractHttp11Processor.process(SocketWrapper<S>
socketWrapper)构造http请求和http响应,触发servlet的service-》CoyoteAdapter.service(Request
req, Response
res),CoyoteAdapter类作为请求处理器-》coyoteAdapter关联着一个org.apache.catalina.connector.Connector,由connector调用 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response),委托容器处理请求(webx的pipeline借鉴此处)-》pipeline执行至StandardWrapperValve.invoke(Request
request, Response
response)-》ApplicationFilterChain.doFilter(ServletRequest
request, ServletResponse
response),在这个filter里面进入我们熟悉的Servlet.service(ServletRequest
arg0, ServletResponse
arg1)过程。
  (小记,在分析代码过程中state = handler.process(socket,status);,直接在eclipse下面用ctrl+鼠标,没有直接显示出handler的实现类,而实际上是有实现类的,需要先点击到接口里面,在按F4
  6 tomcat的通讯层的实现主要在Endpoint,例如JIoEndpoint,在JIoEndpoint.Acceptor接受连接,拿到套接字后转交给线程池,由线程池执行SocketProcessor。
  acceptor接受一个请求的处理逻辑

            int errorDelay = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
countUpOrAwaitConnection();
Socket socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (running && !paused && setSocketOptions(socket)) {
// Hand this socket off to an appropriate processor
if (!processSocket(socket)) {
// Close socket right away
closeSocket(socket);
}
} else {
// Close socket right away
closeSocket(socket);
}
} catch (IOException x) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), x);
}
} catch (NullPointerException npe) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), npe);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}
state = AcceptorState.ENDED;

 

        // Process the request from this socket
try {
SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
// During shutdown, executor may be null - avoid NPE
if (!running) {
return false;
}
getExecutor().execute(new SocketProcessor(wrapper));
} catch (RejectedExecutionException x) {
log.warn("Socket processing request was rejected for:"+socket,x);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
log.error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;

 



  最后由协议处理器AbstractHttp11Processor.process(SocketWrapper<S>
socketWrapper),去进行底层io操作,读取输入流构造http请求。

RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// Setting up the I/O
setSocketWrapper(socketWrapper);
getInputBuffer().init(socketWrapper, endpoint);
getOutputBuffer().init(socketWrapper, endpoint);
   Http协议的解码由http11.InternalInputBuffer实现
  8 为了提供服务器性能,tomcat还提供基于java nio的NioEndpoint,还有基于APR(Apache Portable Runtime)技术,直接和操作系统交互,更好的集成一些现有的本地服务器技术。web 服务器通过ajp协议和应用服务器通信。
  参考(jni http://baike.baidu.com/view/1272329.htm;
  http://hi.baidu.com/ugo5/blog/item/96710efe34aa7e5cd7887de2.html
  http://www.itpub.net/thread-1064918-1-1.html
  http://guojuanjun.blog.iyunv.com/277646/688559
  http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html)
  9 tomcat使用自定义的classloader加载servlet,是出于安全考虑。如果采用系统类加载器统一加载,那么运行的恶意servlet就可以访问到classpath中所有的类库。而使用自定义class loader加载servlet类就可以做到代码访问的隔离,除非应用允许,否则一个类只能访问到同一命名空间的类(即同一个类加载器加载的class)。
  另外一个原因是为了支持动态类加载,例如reload功能,支持热部署。可以感知到web lib下的class文件修改,自动重新加载,这是系统类加载器做不到的。
    To specify certain rules in loading classes. 
    To cache the previously loaded classes. 
    To pre-load classes so they are ready to use.
  tomcat控制一个webapp只能访问到它自己的WEB-INF/lib 下面的class,不能访问tomcat所运行的jvm下面的classpath(由系统类加载器加载的)。
  它的默认class loader是WebappLoader,由下面步骤完成隔离加载和自动热部署的效果
    Creating a class loader 
    Setting repositories 
    Setting the class path 
    Setting permissions 
    Starting a new thread for auto-reload.
  10 servlet依据是否实现SingleThreadModel,分为单线程模型和多线程模型。实现SingleThreadModel(该模型的含义具有一定误导性,已经被废弃)的servlet的,容器会做同步,保证任意时刻只会有一个线程访问servlet(为了保证性能,容器会创建一个servlet实例池)。相反,另一种servlet的,容器将允许多线程访问,如果有需要,业务要自己做好同步。
  由于servlet具有两种含义的线程,所以wrapper需要根据自己所表示的servlet的线程安全性,在分配servlet的时候做更多的处理逻辑。
  StandardWrapper.allocate()  

@Override
public Servlet allocate() throws ServletException {
// If we are currently unloading this servlet, throw an exception
if (unloading)
throw new ServletException
(sm.getString("standardWrapper.unloading", getName()));
boolean newInstance = false;
// If not SingleThreadedModel, return the same instance every time
if (!singleThreadModel) {
// Load and initialize our instance if necessary
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
if (log.isDebugEnabled())
log.debug("Allocating non-STM instance");
instance = loadServlet();
if (!singleThreadModel) {
// For non-STM, increment here to prevent a race
// condition with unload. Bug 43683, test case
// #3
newInstance = true;
countAllocated.incrementAndGet();
}
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
}
}
}
if (!instanceInitialized) {
initServlet(instance);
}
if (singleThreadModel) {
if (newInstance) {
// Have to do this outside of the sync above to prevent a
// possible deadlock
synchronized (instancePool) {
instancePool.push(instance);
nInstances++;
}
}
} else {
if (log.isTraceEnabled())
log.trace("  Returning non-STM instance");
// For new instances, count will have been incremented at the
// time of creation
if (!newInstance) {
countAllocated.incrementAndGet();
}
return (instance);
}
}
synchronized (instancePool) {
while (countAllocated.get() >= nInstances) {
// Allocate a new instance if possible, or else wait
if (nInstances < maxInstances) {
try {
instancePool.push(loadServlet());
nInstances++;
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
} else {
try {
instancePool.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
if (log.isTraceEnabled())
log.trace("  Returning allocated STM instance");
countAllocated.incrementAndGet();
return instancePool.pop();
}
}
  11 tomcat的启动类位于stratup包下面,有Bootstrap创建一个Catalina实例,并调用Catalina的处理方法。之所以分为2个类,是因为在不同的系统下面,会有多个Bootstrap的实现。bin目录下面的shell程序就是用来启动tomcat.
  12 StringManager,当tomcat发生异常行为时,需要记录错误信息。tomcat采用properties文件来维护错误文案,每个package下面都有自己的一个properties文件。当需要获取错误文案时,直接调用StringManager.getManager(String
packageName)即可,这边使用了单例模式,每个package下面共享一个manager.
  


  /**
* Get the StringManager for a particular package. If a manager for
* a package already exists, it will be reused, else a new
* StringManager will be created and returned.
*
* @param packageName The package name
*/
public static final synchronized StringManager getManager(String packageName) {
StringManager mgr = managers.get(packageName);
if (mgr == null) {
mgr = new StringManager(packageName);
managers.put(packageName, mgr);
}
return mgr;
}
 

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-332875-1-1.html 上篇帖子: (原创)tomcat 的优化配置 下篇帖子: 配置Tomcat域名、项目映射
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表