框架源码之Tomcat原理

q1871901600 发布于 2024-11-15 16 次阅读


Tomcat底层支持NIO和BIO两种IO模式

在Tomcat7中,默认为BIO,可以通过如下配置改为NIO

<Connector port="8080" protocol="org.apache.coyote.http11.Http11Ni
oProtocol" connectionTimeout="20000" redirectPort="8443" />

BIO

BIO的模型⽐较简单。

1. JioEndpoint中的Acceptor线程负责循环阻塞接收socket连接

2. 每接收到⼀个socket连接就包装成SocketProcessor扔进线程池Executor中,SocketProcessor是⼀个

Runnable

3. SocketProcessor负责从socket中阻塞读取数据,并且向socket中阻塞写⼊数据

NIO

NIO最⼤的特性就是⾮阻塞,⾮阻塞接收socket连接,⾮阻塞从socket中读取数据,⾮阻塞从将数据写到

socket中。

但是在Tomcat7中,只有在从socket中读取请求⾏,请求头数据时是⾮阻塞的,在读取请求体是阻塞的,

响应数据时也是阻塞的。

为什么不全是⾮阻塞的呢?因为Tomcat7对应Servlet3.0,Servlet3.0规范中没有考虑NIO

Tomcat7中使⽤NIO处理请求的基本流程:

1. 利⽤Acceptor来阻塞获取socket连接,NIO中叫socketChannel。
2. 接收到socketChannel后,需要将socketChannel绑定到⼀个Selector中,并注册读事件,另外,基于NIO还需要⼀个Poller线程来轮询Selector的事件列表中是否存在就绪事件,如果存在就将就绪事件查出来,并处理该事件,在Tomcat中⽀持多个线程同时查询是否存在就绪事件,每个Poller中都包含⼀个Selector,这样每个Poller线程就负责轮询⾃⼰的Selector上就绪的事件,然后处理事件。

3. 当Acceptor接收到⼀个socketChannel后,就会将socketChannel注册到某⼀个Poller上,确定Polloer的逻辑⾮常简单,假设现在有3个Poller,编号为1,2,3,那么Tomcat接收到的第⼀个socketChannel注册到1号Poller上,第⼆个socketChannel注册到2号Poller上,第三个socketChannel注册到3号Poller上,第四个socketChannel注册到1号Poller上,依次循环。

4. 在某⼀个Poller中,除开有selector外,还有⼀个ConcurrentLinkedQueue队列events,events表示待执⾏事件,⽐如Tomcat要socketChannel注册到selector上,但是Tomcat并没有直接这么做,⽽是先⾃⼰⽣成⼀个PollerEvent,然后把PollerEvent加⼊到队列events中,然后这个队列中的事件会在Poller线程的循环过程中真正执⾏。

5. 上⾯说了,Poller线程中需要循环查询selector中是否存在就绪事件,⽽Tomcat在真正查询之前会先
看⼀下events队列中是否存在待执⾏事件,如果存在就会先执⾏,这些事件表示需要向selector上注册事件,⽐如注册socketChannel的读事件和写事件,所以在真正执⾏events队列中的事件时就会真正的向selector上注册事件。所以只有先执⾏events队列中的PollerEvent,Poller线程才能有机会从selector中查询到就绪事件。

6. 每个Poller线程⼀旦查询到就绪事件,就会去处理这些事件,事件⽆⾮就是读事件和写事件。
7. 处理的第⼀步就是获取当前就绪事件对应的socketChannel,因为我们要向socketChannel中读数据或写数据。
8. 处理的第⼆步就是把socketChannel和当前要做的事情(读或写)封装为SocketProcessor对象。
9. 处理的第三步就是把SocketProcessor扔进线程池进⾏处理。
10. 在SocketProcessor线程运⾏时,就会从socketChannel读取数据(假设当前处理的是读事件),并且是⾮阻塞读。

11. 既然是⾮阻塞读,⼤概的⼀个流程就是,某⼀个Poller中的selector查询到了⼀个读就绪事件,然后交给⼀个SocketProcessor线程进⾏处理,SocketProcessor线程读取数据之后,如果发现请求⾏和请求头的数据都已经读完了,并解析完了,那么该SocketProcessor线程就会继续把解析后的请求交给Servlet进⾏处理,Servlet中可能会读取请求体,可能会响应数据,⽽不管是读请求体还是响应数据都是阻塞的,直到Servlet中的逻辑都执⾏完后,SocketProcessor线程才会运⾏结束。假如SocketProcessor读到了数据之后,发现请求⾏或请求头的数据还没有读完,那么本次读事件处理完毕,需要Poller线程再次查询到就绪读事件才能继续读数据,以及解析数据。

12. 实际上Tomcat7中的⾮阻塞读就只是在读取请求⾏和请求体数据时才是⾮阻塞的,⾄于请求体的数据,
是在Servlet中通过inputstream.read()⽅法获取时才会真正的去获取请求体的数据,并且是阻塞的。

参考文章;

【Tomcat】深度解读Tomcat中的NIO模型 & NioEndpoint源码_tomcat nio-CSDN博客

Tomcat NIO(15)-长连接-腾讯云开发者社区-腾讯云

一个会写python的Java工程师
最后更新于 2024-11-20