tomcat-请求流程

204 阅读3分钟

模拟最简单的Web容器

// 创建socket服务
ServerSocket  serverSock  = new ServerSocket(port,backlog);
​
// 循环等待socket连接
while(shutdown){
   // 接收到socket连接
   Scoket socket = serverSock.accept();
   
   // 将Http报文解析为Request
   Request request = createRequest(socket.getInputStream);
   
   // 封装返回内容,Response
   Response response =  createResponse(request);
​
   // 创建Servlet
  URLClassLoader classLoader = new URLClassLoader();
  Class servletClass = classLoader.loadClasser(servletName);
  Servlet servlet =  servletClass.newInstance();
​
  // 执行业务逻辑
  servlet.service(request,response);  
​
  // socket关闭
  socket.close;
   
}

BIO模式

关键类:JIoEndpoint

BIO请求流程:
1. 启动Acceptor线程,创建ServerSocket#acceptor,循环等待请求;
2. 接收到请求后,交由Tomcat线程池,SocketProcessor#run 处理请求;
3. 转换请求,RequestResponseAbstractHttp11Processor#process#fillrecvBuf获取数据;
4. Adapter#service,准备交由容器进行处理;
5. 流向Engine管道的第一个阀门,StandardEngineValve#invokde6. 流向Host管道的第一个阀门,AccessLogValve->ErrorReportValve->StandardHostValve
   a. 设置ThreadContextClassLoaderWebappClassLoader
7. 流向Context管道的第一个阀门,AuthenticatorBase->StandardContextValve
8. 流向Wrapper管道的第一个阀门,StandardWrapperValve
   a. 创建Servlet对象,StandardWrapper#allocate
   b. 创建过滤器链,执行所有过滤器,ApplicationFilterFactory#createFilterChainfilterChain.doFilter
   c. servlet#service 
9. HttpServlet#service,判断请求方式,Get还是Post等等
   a. 如果是get,执行doGet,执行到业务定义的方法

NIO模式

Tomcat使用I/O多路复用,来代替传统BIO模型,利用少量的线程来管理大量的连接;

为什么有NIO模式?

  1. BIO模式下一个连接对应一个工作线程,之前默认最多只同时支持200个请求;
  2. 如果连接建立之后,tomcat会阻塞来获取请求数据,占用工作线程;
  3. http1.1之后,大部分请求都是长连接,很多情况下tomcat只能干等着请求到来,很多情况下都是等不到后再超时断开连接;

关键类:NioEndpoint、Poller

NIO请求流程

1. NioEndpoint#bind,绑定地址,将socket设为阻塞
2. NioEndpoint#startInternal,启动poller(多核2,单核1个)、acceptor线程
3. ServerSocket#accept接收到请求,设为非阻塞
4. SocketChannel 封装成 NioChannel
5. Poller#register
   1. 封装PollerEventKeyAttachment.interestOps:OP_REGISTERintOpsOP_READ,读事件
    2. 将PollerEvent添加到事件列表,Poller.events
6. Poller#run -> #events -> PollerEvent#run: 真正将读事件注册到当前poller中的selector对象上
7. Poller#run -> 循环就绪的事件 selector.selectedKeys() 
   1. #processKey -> 获取NioChannel -> #processSocket,从Socket中读/写数据
   2. 分派任务到线程池

Poller

NIO模式的实现类

selector

  • 一个Poller对应一个Selector,每个Poller负责轮询自己的Selector上就绪的事件,并处理

events

  • 存放PollerEvent的队列

tomcat要socketChannel注册到selector上,但是Tomcat并没有直接这么做,而是先自己生成一个PollerEvent,然后把PollerEvent加入到队列events中,然后这个队列中的事件会在Poller线程的循环过程中真正执行

#run
  • #events,循环 events,将事件注册到Select
  • #select、#selectKeys,循环就绪的事件,获取socket请求,分派到线程执行
#register

将请求封装成PollerEvent,放到events队列中,等待Poller#run,注册到Selector

PollerEvent

  • 是一个线程,impl Runnable
  • #run ,将事件注册到Selector上

解析Http报文

http协议分为: 请求行、请求头、请求体
请求行: 空格分割,有请求方法,url,协议版本
请求头: 冒号分割,key value形式
请求体: 前一行只有回车换行,取多少根据content-length决定

NIO知识

Selector

这个类是实现Java IO多路复用的重要类,它的功能在于两方面:

  • 非阻塞的Channel可以使用#register方法注册感兴趣的阻塞事件,
  • 可以通过select方法选出已经准备就绪的阻塞事件做进一步的处理
#register

注册阻塞事件;

SelectionKey:
OP_READ;
OP_WRITE;
OP_CONNECT;
OP_ACCEPT;
#select

返回已经就绪的事件数量;

最好指定时间,避免与#register发生死锁;

#selectedKeys

会返回一个就绪的事件集合,List< SelectionKey >,循环处理事件,如socket读取

image-20220222161556913