Tomcat NIO模式源码解读

19 篇文章 1 订阅
1 篇文章 0 订阅


做那么久web开发工程师,却一直没时间亲自研究tomcat中间件到底是个啥,网络传输怎么玩法,对于程序员的好奇心非常重要,针对于此,个人亲自研究一下tomcat NIO启动模式(tomcat9具有四种模式NIO、NIO2、BIO、Apr)

前提

这里提及前提,那是因为如果没有前提知识,要读懂tomcat是有一定的困难,个人认为要解读tomcat源码之前,最好具备以下前提知识(可自行选择阅读,若有时间均阅读更佳)

  • 三次握手和TCP原理 https://blog.csdn.net/lijin_12456/article/details/84887878
  • Linux下网络编程 https://blog.csdn.net/weixin_44895651/article/details/108163533
  • http协议 https://www.cnblogs.com/an-wen/p/11180076.html
  • 网络拓扑和安全 https://blog.csdn.net/soft_z1302/article/details/114678095
  • Java NIO Tutorial http://tutorials.jenkov.com/java-nio/index.html
  • TCP和NIO流程 https://blog.csdn.net/u011381576/article/details/79876754
  • java常见各种锁 https://www.jianshu.com/p/e6e794b64f80
  • tomcat简单配置和spring mvc以及过滤器拦截器的基本理念
  • 线程池多线程 https://blog.csdn.net/soft_z1302/article/details/110440449

Non-blocking Server讲解

为了增加阅读兴趣,个人来看一下Jakob Jenkov写的文章描述样例源码,Jakob Jenkov编写的源码服务器,由两个线程协同处理socket请求,一个接收socket,存放到队列,一个处理socket队列,并输出流(图片引用Jakob Jenkov描述
在这里插入图片描述
创建两个线程
1.线程监听serversocketChannel,存放socketChannel到queue中
2.线程不停轮询queue,若有则注册SelectionKey.OP_READ中,通过selectedKeys读取通信(信号读半消息,存放到list中,最终合并),
然后注册到读代理队列,从读代理队列获取注册到写中,写入返回数据
3.buffer缓存循环利用,每个线程占用一段byte【begin,end】,不相干扰。
接下来执行一下代码(源码可自行下载,上面链接),查看请求报文和返回报文
在com.jenkov.nioserver.Message#writePartialMessageToMessage方法中增加输出读取的缓存数据

 public void writePartialMessageToMessage(Message message, int endIndex) {
        int startIndexOfPartialMessage = message.offset + endIndex;
        int lengthOfPartialMessage = (message.offset + message.length) - endIndex;

        // 读取报文数据
        System.arraycopy(message.sharedArray, startIndexOfPartialMessage, this.sharedArray, this.offset, lengthOfPartialMessage);
        for (int i = message.offset; i < endIndex; i++) {
            System.out.print((char)message.sharedArray[i]);
        }
        System.out.println();
    }

启动程序com.jenkov.nioserver.example.Main#main
使用postman或者curl请求http://localhost:9999/ ,报文体随便填写,这里只是样例报文

{
  "password": "string",
  "phone": "string"
}

查看查看请求结果
在这里插入图片描述
可以看到服务器能接受前端请求,同时查看返回数据易能解析报文。
在这里插入图片描述
综合上面可知,其实我们所有网络传输都只是使用了操作系统的tcp三次握手建立连接,即tcp_connect,传输符合http协议报文的二进制数据,最终按照http协议处理数据和返回数据,即网络通讯流程,其实Tomcat亦如此,不过tomcat设计比这个复杂一些,有以上理念,更加容易读懂源码

Tomcat总览

在这里插入图片描述

我们来看一个简单tomcat配置文件server.xml如上图,由图看见,tomcat不是一个工具,是一个容器也是一个中间件。个人总结tomcat组成为

  1. Server全局服务,也就是服务器
  2. Listener 监听器,一序列全局监听器
  3. GlobalNamingResources全局配置文件
  4. Service对外提供服务,也就是我们web服务关注地方
  5. Connector连接,对于tomcat来说,每次请求过来一个socket处理连接,配置端口协议等
  6. Engine 主机,可配置域名访问等
  7. Host应用host,若没有,使用engine
  8. Context 应用,全局应用,一个context一个应用(源码解读得知
  9. Wrapper即包装的servlet,记录所有实例化的servlet(源码解读得知
  10. Pipeline(value),其中Engine、Host、Context 均具有通讯管道。(源码解读得知
  11. tomcat应用部署方式四种:war、文件夹、jar包、节点Context。不管是哪一种均需要servlet,遵循servlet协议。

Spring Boot内置tomcat源码NIO解读

启动流程

个人比较懒,这里不详细编写图,大体流程如下(整个流程中Context传递bean工厂):
初始化beanFactory-》onRefresh()-》createWebServer()-》Tomcat-》StandardServer-》StandardService-》Connector-》Http11NioProtocol-》TomcatWebServer tomcat.start()-》startInternal()->NioEndpoint#startInternal()->
createExecutor()->PollerEvent->Poller->startAcceptorThread()

请求返回流程

在这里插入图片描述

NIO有buffer、channel、selector组成。channel网络传输用ServerSocketChannel,调用accept()方法,获取SocketChannel,通过通过注册到selector监控数据到达,完成以及传输过程,因此tomcat设计也因此设计

  1. Acceptor监听网络传输
  2. 添加处理同步队列事件PollerEvent
  3. 监听同步队列事件若有只则注册到Channel到Selector中,操作方式为SelectionKey.OP_READ
  4. selector.selectedKeys()处理信号指令,拿到通讯socketChannel
  5. 拿到SocketProcessor,通过ThreadPoolExecutor执行SocketProcessor线程
  6. Http11Processor处理协议,处理Connector、Engine 、Host、Context 、Wrapper、Pipeline
  7. 处理完后执行完请求和返回,返回Http11OutputBuffer,执行输出流NioSocketWrapper#doWrite,终止请求request.finishRequest()和返回response.finishResponse()

总结

总而言之,学会tomcat,你将会明白计算机如何传输数据,网络如何通讯,以及web服务器如何处理请求数据,解析数据,包装请求设计等。
1.了解tomcat只不过是针对网络传输协议http进行解析和包装而已。
2.tomcat的NIO处理请求数据时,最终交给线程池去处理,这里可以优化tomcat线程池配置,至于线程池优化,可查阅java多线程-学习总结(完整版)

server:
  tomcat:
    min-spare-threads: 16
    max-threads: 150
    max-connections: 200000
    accept-count: 128
    accesslog:
      enabled: true
      pattern: "%h %l %u %t %r %s %b %D"

3.部署方式有四种,也就是最终都是tomcat的nioendpoint处理,即可以设置部署路径

management:
  endpoints:
    web:
      base-path: /

4.tomcat其实就是ServerSocketChannel使用Poller和worker进行双线程处理。
5.可以自定义请求方法,自定义servlet,指定请求方法。
6.你将会了解,spring boot的web应用,不仅仅是一个spring的封装而已,也做了大量tomcat和启动相关协议封装。此外,程序都是一个规范,规范的好坏要经过数年岁月的验证。

笔记

tomcat底层原理
1.tomcat超强的容器也就是一个中间件
2.四大组件servlet和context
3.wrapper容器详解
4.tomcat的bio和nio
5.tomcat和socket的关系

应用部署方式:war、文件夹、jar包、节点Context

org.apache.catalina.mbeans.MBeanFactory#createStandardServiceEngine

servlet容器
org.apache.catalina.startup.Tomcat#getServer

启动流程
onRefresh()-》createWebServer()-》Tomcat-》StandardServer-》StandardService-》Connector-》
Http11NioProtocol-》TomcatWebServer tomcat.start()-》startInternal()->NioEndpoint#startInternal()->
createExecutor()->PollerEvent->Poller->startAcceptorThread()

请求接受
Acceptor<Thread> ->  NioEndpoint.serverSocketAccept()
NioEndpoint#setSocketOptions 设置SocketChannel
NioSocketWrapper#register(SelectionKey.OP_READ) 作为标志,超时使用
Poller#wakeupCounter increment 增加记录
selector.wakeup() 唤醒selector

Poller#run#events()(loop检查队列,若有信号,则sc.register注册读管道到selector)
Poller#events#eventCache.push(PollerEvent)
NIO(selector.selectedKeys())

Executor.execute(SocketProcessor)处理请求数据
AbstractProtocol.ConnectionHandler#process
coyote.AbstractProcessorLight#process

coyote.http11.Http11Processor#service
==================
setSocketWrapper(socketWrapper);  设置缓存大小8 * 1024开始 headerBufferSize,若超限,则扩容
	SocketProperties.appReadBufSize和SocketProperties.appWriteBufSize
protected final void setSocketWrapper(SocketWrapperBase<?> socketWrapper) {
        super.setSocketWrapper(socketWrapper);
        inputBuffer.init(socketWrapper);
        outputBuffer.init(socketWrapper);
}
Http11InputBuffer#parseRequestLine 处理请求行,读取数据,只操作头,包装request
NioSocketWrapper#fillReadBuffer(boolean, java.nio.ByteBuffer) 读取全部数据
prepareRequestProtocol() 设置请求响应信息
prepareRequest() 解析主机域名等相关信息
getAdapter().service(request, response) 调用servelt

connector-》StandardService[Tomcat]-》StandardEngine[Tomcat]-》StandardPipeline[StandardEngine[Tomcat]]
-》StandardEngineValve[StandardEngine[Tomcat]].invoke()-》StandardEngine[Tomcat].StandardHost[localhost]
->StandardPipeline[StandardEngine[Tomcat].StandardHost[localhost]]
->ErrorReportValve[StandardEngine[Tomcat].StandardHost[localhost]]->
StandardHostValve.invoke()->Context->
org.apache.catalina.core.StandardWrapperValve#invoke() -------------------------- tomcat调用servlet
StandardWrapper[dispatcherServlet]
DispatcherServlet(servlet)

ApplicationFilterChain.doFilter().doFilterInternal() -------------------------- 处理过滤器开始
OncePerRequestFilter
CharacterEncodingFilter
WebMvcMetricsFilter
FormContentFilter
RequestContextFilter
WsFilter
internalDoFilter(servlet.service(request, response))
servlet
controller
输出结果
org.apache.catalina.connector.OutputBuffer#close()关闭输出流
org.apache.coyote.http11.Http11OutputBuffer.SocketOutputBuffer#end() socketWrapper.flush(true);
org.apache.tomcat.util.net.SocketWrapperBase#doWrite(boolean)
org.apache.tomcat.util.net.NioEndpoint.NioSocketWrapper#doWrite
 
--------------------------处理过滤器结束

------------------

================



Pipeline
	List<Value> value;
Engine
	List<Host> hosts;
Host
	List<Context> contexts;

Context
	list<Wrapper> wrappers;
Wrapper
	List<Servlet> servlets

讲解NIO
http://tutorials.jenkov.com/java-nio/index.html
TCP和NIO流程
https://blog.csdn.net/u011381576/article/details/79876754

创建两个线程
1。线程监听serversocketChannel,存放socketChannel到queue中
2。线程不停轮询queue,若有则注册SelectionKey.OP_READ中,通过selectedKeys读取通信(信号半读取,存放到list中,最终合并),
然后注册到读代理队列,从读代理队列获取注册到写中,写入返回数据
每个线程占用一段byte【begin,end】
https://github.com/jjenkov/java-nio-server

需要三次握手,tcp传输原理、https协议组成、操作系统流程、IO、NIO、java一些理念和数据结构

参考文献

【1】tomcat接受、分配连接(socket)解析
【2】三次握手和TCP原理
【3】Linux下网络编程
【4】Java NIO Tutorial
【5】TCP和NIO流程
【6】java常见各种锁

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值