博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
tomcat处理请求的流程
阅读量:4190 次
发布时间:2019-05-26

本文共 11156 字,大约阅读时间需要 37 分钟。

tomcat处理请求的流程

第一步:从SocketChannel到NioSocketWrapper

首先分析Acceptor,在tomcat9之前,Acceptor类是属于NioEndpoint的内部类。

如下图所示:

下面的类为AbstractEndpoint

在这里插入图片描述

下面的类为NioEndpoint类

在这里插入图片描述

下面的图片是tomcat9Acceptor类,是一个独立的类,不是某个类的内部类。

在这里插入图片描述

接下来看Acceptor代码

在这里插入图片描述

从上面的代码中,可以看到this.endPoint.serverSocketAccept();获取到了socket链接对象。我们深入看下serverSocketAccept()方法返回的是个什么对象。

serverSocketAccept()方法如下图所示

在这里插入图片描述

从上面的图片中我们可以看到是返回的SocketChannel对象,需要关注的是this.serverSocket这个属性对象什么时候产生的呢。

赋值serverSocket属性,如下图所示,

在这里插入图片描述在这里插入图片描述

从上面的图中可以看到,serverSocket 实质是ServerSocketChannel对象。并且这个对象在NioEndpoint对象里面初始化的。

获取了ServerSocket对象后,怎样进行处理了,继续往下走。如下图所示

在这里插入图片描述

从上面的图片中的代码我们知道,接下来的处理流程走到了this.endpoint.setSocketOptions(socket),设置Socket操作,具体处理流程看如下的图片代码,代码流程又进入了Endpoint对象里面

在这里插入图片描述

在这里插入图片描述

从上面图片的代码我们可以看到socket对象被NioChannel对象进行了包装,其实这里还设计到缓存技术,也就是对象重复利用技术,NioChannel对象会被放入到一个nioChannels的缓存池子里面。下次直接重复利用,避免了过多的产生niochannel对象,其实tomcat里面好多地方都用到了这个技术,因此就不一一举列了。从上面的图片代码,我们可以得出,最后niochannel对象又被NioSocketWrapper对象进行了包装。最后对Niochannel和SocketWrapper进行注册处理。到此,SocketChannel到SocketWrapper的流程已经处理完成。

第二步,分析PollerEvent的事件,SocketWrapper,NioChannel与PollerEvent事件之间有什么关系了。

看下面的事件注册图片。

在看注册图片前,先引入几个问题,什时候开启的事件注册,注册事件的方法由那个对象执行了。

注册驱动代码,代码如下

在这里插入图片描述

从上面的代码可以到在Endpoint的中驱动,在SocketWrapper被打包之后,就立马进行事件注册,在注册那行代码里面有this.poller从而可以得出,是由Poller对象执行的注册操作。那么poller对象又是什么时候被初始化的呢,请看下图代码。

在这里插入图片描述在这里插入图片描述

从上面的代码可以,Poller对象在startInternal方法里面初始化,并且改对象里面的run方法,又单独线程执行。

接下来我们看注册代码是怎么一回事。请看下图

在这里插入图片描述

从上面的代码我们看出,其实上面的代码,SocketWrapper只注册了感兴趣的操作等于1,后续代码都没用SocketWrapper,在看后面的代码又用到缓存对象重用技术,这里不做介绍,PollerEvent对象里面对NioChannel进行封装。最后调用addEvent的方法,将的Poller时间对象放入到PollerEvent池子里面。PollerEvent: private final SynchronizedQueue<NioEndpoint.PollerEvent> events = new SynchronizedQueue(); 下面是addEvent方法

在这里插入图片描述

上述还有个wakeup()到目前本人也不理解,后续再花时间研究,到此,socket链接已经处理完毕,后续讨论,怎样把socket的处理传(request,response)递到Containner的容器。

第三步,分析Poller对象里面的run方法

我们从上述的代码分析,已经知道了,Poller的run方法已经被启动了,有一个单独的线程处理Poller的方法,其实,这个Poller的方法,在tomcat9之前是有多个线程执行,tomcat9之后变成单独线程来执行了。

下图是tomcat8.5版本的,poller对象的个数,是多个线程执行,单独线程执行对应poller对象的run方法。

在这里插入图片描述

那就引起我们对之前的思考,之前tomcat9的是this.poller获取poller的方法,获取是固定的Poller,而,tomcat8是随机获取Pollers[]数组中的一个poller,代码如下

在这里插入图片描述

继续回到tomcat9版本的Poller,只有单线程处理Poller的run方法。现在来Poller里面的run方法里面到底做了什么。

在这里插入图片描述

暂时先看run方法里面的this.events();到底干了什么。

在这里插入图片描述

我们看到Poller的run方法调用了events()方法,在events()方法里面,PollerEvent时间调用了自己的run方法进行运行,运行之后,重置PollerEvent对象,从而达到PollerEvent对象达到复用的情况。最后返回true.那我们接下来看到PollerEvent对象里面的run方法又干了什么事情。

在这里插入图片描述

上面被标注的那一行代码很重要,主要干了什么呢?
Poller处理的核心是启动执行事件队列中的PollerEvent,接着从selector中遍历已经就绪的key,一旦发生了感兴趣的事件,则交由processSocket方法处理。PollerEvent的作用是向socket注册或更新感兴趣的事件:
public void run() {      //socket第一次注册到selector中,完成对socket读事件的注册      if ( interestOps == 256) {          try {              socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);          } catch (Exception x) {              log.error("", x);          }      } else {          // socket之前已经注册到了selector中,更新socket所感兴趣的事件          final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());          try {              boolean cancel = false;              if (key != null) {                  final KeyAttachment att = (KeyAttachment) key.attachment();                  if ( att!=null ) {                      //handle callback flag                      if (att.isComet() && (interestOps & OP_CALLBACK) == OP_CALLBACK ) {                          att.setCometNotify(true);                      } else {                          att.setCometNotify(false);                      }                      interestOps = (interestOps & (~OP_CALLBACK));//remove the callback flag                      // 刷新事件的最后访问时间,防止事件超时                       att.access();//to prevent timeout                      //we are registering the key to start with, reset the fairness counter.                      int ops = key.interestOps() | interestOps;                      att.interestOps(ops);                      key.interestOps(ops);                  } else {                      cancel = true;                  }              } else {                  cancel = true;              }              if ( cancel ) socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);          }catch (CancelledKeyException ckx) {              try {                  socket.getPoller().cancelledKey(key,SocketStatus.DISCONNECT,true);              }catch (Exception ignore) {}          }      }//end if  }//run  @Override  public String toString() {      return super.toString()+"[intOps="+this.interestOps+"]";  }

}

事件注册完成之后,继续回到Poller对象里面的run方法。看下图

在这里插入图片描述

在上图中我们可以到selector一直在遍历selector中维持的ServerSocket的对象,看那个ServerSocke对象出现了可读,或者可写事件,如果发现了有可读事件后,里面获取了SocketWrapper,然后将SocketWrapper以及SelectKey交由processkey处理。那么接下来看processKey究竟做了什么样的处理。

在这里插入图片描述

Poller处理流程的分析中看到它的run方法最后会调用processKey()处理selector检测到的通道事件,而在这个方法最后会调用processSocket来调用具体的通道处理逻辑,从上面的代码可以到最后processKey的处理变成了调动了processSocket的处理,那么接下来看看,processSocket的方法道理又干了些什么事情了。

在这里插入图片描述

上面的代码中我们可以看到我们会生成一个SocketProcessor对象,接下来看下这个对象是怎样产生的。

在这里插入图片描述

从上面的代码看到SocketProcessor对象是通过new产生的。其实这个对象后续也会进行重复利用,这个对象将只有SocketWrapper对象和Socket的事件对象,那么接下来一看SocketProcessor对象的原貌。

在这里插入图片描述

下面是SocketProcessor类,继承了SocketProcessorBase类,父类集成Runnale, 父类里面的run方法调用了doRun();doRun()由子类SocketProcessor去实现的。
protected class SocketProcessor extends SocketProcessorBase
{ public SocketProcessor(SocketWrapperBase
socketWrapper, SocketEvent event) { super(socketWrapper, event); } protected void ***doRun***() { NioChannel socket = (NioChannel)this.socketWrapper.getSocket(); SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector()); NioEndpoint.Poller poller = NioEndpoint.this.poller; if (poller == null) { this.socketWrapper.close(); } else { try { int handshake = -1; try { if (key != null) { if (socket.isHandshakeComplete()) { handshake = 0; } else if (this.event != SocketEvent.STOP && this.event != SocketEvent.DISCONNECT && this.event != SocketEvent.ERROR) { handshake = socket.handshake(key.isReadable(), key.isWritable()); this.event = SocketEvent.OPEN_READ; } else { handshake = -1; } } } catch (IOException var13) { handshake = -1; if (NioEndpoint.log.isDebugEnabled()) { NioEndpoint.log.debug("Error during SSL handshake", var13); } } catch (CancelledKeyException var14) { handshake = -1; } if (handshake == 0) { SocketState state = SocketState.OPEN; if (this.event == null) { state = NioEndpoint.this.getHandler().process(this.socketWrapper, SocketEvent.OPEN_READ); } else { ***state = NioEndpoint.this.getHandler().process(this.socketWrapper, this.event);*** } if (state == SocketState.CLOSED) { poller.cancelledKey(key, this.socketWrapper); } } else if (handshake == -1) { poller.cancelledKey(key, this.socketWrapper); } else if (handshake == 1) { this.socketWrapper.registerReadInterest(); } else if (handshake == 4) { this.socketWrapper.registerWriteInterest(); } } catch (CancelledKeyException var15) { poller.cancelledKey(key, this.socketWrapper); } catch (VirtualMachineError var16) { ExceptionUtils.handleThrowable(var16); } catch (Throwable var17) { NioEndpoint.log.error(AbstractEndpoint.sm.getString("endpoint.processing.fail"), var17); poller.cancelledKey(key, this.socketWrapper); } finally { this.socketWrapper = null; this.event = null; if (NioEndpoint.this.running && !NioEndpoint.this.paused && NioEndpoint.this.processorCache != null) { NioEndpoint.this.processorCache.push(this); } } } } }
我们从上面的斜体代码中看到,在doRun的方法里面有这样一行代码state = NioEndpoint.this.getHandler().process(this.socketWrapper, this.event);
这一行代码很关键,在讲这一行代码之前,我想讲一下,doRun()方法是被线程池执行的,如下图所示。

在这里插入图片描述

那么这个线程池有多大了。看下图见知晓。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

从上面的流程中我们可以看到 在Endpoint对象的startInternal方法里面会初始化线程池的大小,具体多大了,

this.executor = new org.apache.tomcat.util.threads.ThreadPoolExecutor(this.getMinSpareThreads(), this.getMaxThreads(), 60L, TimeUnit.SECONDS, taskqueue, tf);

this.getMinSpareThreads(), this.getMaxThreads()

public int getMinSpareThreads() {

return Math.min(this.getMinSpareThreadsInternal(), this.getMaxThreads());
}
private int getMinSpareThreadsInternal() {
return this.internalExecutor ? this.minSpareThreads : -1;
}
protected volatile boolean internalExecutor = true;
private int minSpareThreads;

目前从tomcat9中没有找到最小的线程数,没有初始化值,那么接下来看下最大线程数 也没发现最大线程数,这个很定是在NioEndPoint初始化的时候会有一个默认值,后续再深究,现在继续往下走,知道有线程池这么一回事就可以啦。

现在继续看NioEndpoint.this.getHandler().process(this.socketWrapper, this.event); 方法,这个方法最后调了handler处理SocketWrapper和SocketEvent 的事件,其实,tomcat9之后,只有ConnectionHandler实现了Handler的接口,那我们看下Handler和ConnectionHandler在什么地方了。

在这里插入图片描述

从上面的代码我们可以看出,Handler接口是在AbstractEndpoint里面,然而其实现类已经到AbstactProtocol抽闲类里面了,那么我们接下来看下。ConnectionHandler.process()又做了什么处理。

在这里插入图片描述

我们从SocketWrapper里面拿出了Socket了,其次,我们通过conneciton获取Porcess处理器。我很好奇connection又是什么对象了。

private final Map<S, Processor> connections = new ConcurrentHashMap();

揭开面纱之后,原来是个map,存放的是socket的与处理之间的对应关系。

在这里插入图片描述

继续往下看

在这里插入图片描述

从上面的图片代码我们看到Processor对象的process方法。其实Processor对象是个抽象的接口,其有很多实现类,AbstractProcessorLight,Http11Processor ,AjpProcessor等。继续往下分析,这个process()方法会转到AbstractProcessorLight子类的方法中,然后我们看看AbstractProcessorLight的子类又干了什么了。

在这里插入图片描述

个人感觉AbstractProcessorLight有做了一次转发,最后调用子类的service方法。这里主要是Http11Processor 对象的service();然后我们再来看下Http11Processor 对象的service()方法又干了些什么事情呢。看下面的图

在这里插入图片描述

在这里插入图片描述

从Http11Processor的service中,我们看到adaptor,而Adaptor唯一的实现类就是CoyoteAdapter.那么接下来看看,CoyoteAdapter的service()方法做了什么东东。

在这里插入图片描述

在这里插入图片描述

从上面的代码终于看到了socket到container容器的调用了。this.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

第四步,分析容器Engine到dispathcerServlet的调用方法。

在这里插入图片描述

暂且先用标准的StandardEngineValue引擎来处理,看到StandardEngine引擎。上面的代码,request对象里面就已经包含了host对象,并且还支持是否异步调用的方法。异步调用接下来需要好好研究,现在只需要把整个流程走下去,继续往下跑。

接下来请求从StandardEngineValue调到了StandardHostValue,来看下StandardHostValue对象的代码

在这里插入图片描述

从上面的代码我们可以看到,标准的StandardHostValue又从请求中拿出了Context的内容,接下来就调转到标准的Context的Value来处理,我们来看下面的代码。

在这里插入图片描述

从上面的代码我们可以看到,标准的StandardContextValue又从请求中拿出了Wrapper的内容,接下来就调转到标准的Wrapper的Value来处理,我们来看下面的代码。

在这里插入图片描述

在这里插入图片描述

上面的allocate() 方法获取到了dispatchServlet,接着继续向下走,

在这里插入图片描述

上面的代码,我们看到出现了应用程序过滤链。

在这里插入图片描述

从上面的代码,我们就看到了开始走过滤链了。doFilter();继续往下走。

在这里插入图片描述

从上面的代码我们可以看到,有五个过滤器,具体过滤器干了什么,我这里就不深究了,最后还是在ApplicationFilterChain里面调用DispatchServlet的service方法。

在这里插入图片描述

最后就是进行SpringMVC的处理了

在这里插入图片描述

至此整个从浏览器或者客户端发送请求,经过tomcat服务器之后怎样到dispatch的servlet的流程已经分析完成。

转载地址:http://tusoi.baihongyu.com/

你可能感兴趣的文章
线程的优先级
查看>>
Khronos 官方新闻 Windows Vista 和 OpenGL 的事实 ZT
查看>>
HLSL编程实现PhotoShop滤镜效果
查看>>
如果我恨一个人,我就领他到中关村买相机。
查看>>
装ubuntu碰到一件BT的事情
查看>>
关于NVIDIA 的 OpenGL回退到软件模式的问题。
查看>>
OpenGL和D3D中Cubemap的图象方向问题
查看>>
XREAL3D开发转移到csdn的svn服务器上。
查看>>
Mozilla XULRunner 的编译。
查看>>
GUISystem设计思路之三:HotArea的概念。
查看>>
GUI设计思路之二:Blender -- WinstateBlender/WinTransBlender
查看>>
新瓶灌旧酒,Hugo老师的Fire算法的GPU版本.
查看>>
前世,是谁埋的我。
查看>>
J2ME技术实现的RPG游戏的DEMO(含源代码)
查看>>
J2ME开发中常见属性(Property)及其作用列表
查看>>
【文章汇总】J2ME程序开发全方位基础讲解
查看>>
MIDP2.1规范文档
查看>>
两个基础的算法题目
查看>>
编程是什么——写给编程的初学者
查看>>
Flash Lite作为S40和S60系列上的特性出现在技术规范中
查看>>