Jetty源码学习5-NIO架构网络服务器

引言

一个优秀的框架能从中学到很多东西,撇开代码不说,它所涉及到的技术也是非常通用的,藉此总结下NIO、JMX、HttpClient、Continunation在Jetty中的应用,当然对于Http的解析HttpParser也算是一个。

本文主要是总结网络服务器的架构与NIO在Jetty中的应用,关于NIO的操作系统底层原理并没有深入研究,只总结了我对于NIO的理解。

网络架构对于应用服务器而言还是比较重要的,在服务器中有一些“池”的概念,你立马会想到线程池,Jetty中还有缓存池、HttpClient连接池。而这里就出没了缓存池。

NIO简介

多数应用程序已不受cpu的束缚,而更多的受I/O的束缚,设想买火车票的时候,虽然前面只有10个人,但是每个人花上5分钟,你也快疯了。

NIO对于传统I/O的改进无非是缓冲区、通道和选择器。

1、缓冲区

一个Buffer对象是固定数量数据的容器,作用是个存储器或分段运输区,这里的数据可以被存储和检索。而缓冲区设计的目的就是能高效得传输数据,操作系统与java基于流的i/o模型有些格格不入,操作系统往往传送的都是大块的数据,而jvm的i/o类操作的是小数据,往往是单个字节或几行文本,结果就是操作系统送来的大数据将被分割处理。这个时候你可能会想到,如果使用基于数组的read或者write会不会效果更加好呢,但Buffer类可能更加的高效,因为其利用了本地代码及其他优化方法实现了数据的移动。

既然你已经邂逅了Buffer,那你毫无疑问需要拜访下Channel了,因为如果Buffer是载体的话,那么Channel便是传送带。

为了理解本文的内容,下面有几个知识点需要了解下:

1)JDK中NIO的Buffer:

InDirectByteBuffer是基于堆实现的,也就是利用JVM中字节数组byte[]实现了缓存;而DirectByteBuffer是基于Unix系统的MMap机制实现。

对于DirectBuffer,Java虚拟机会尽最大的努力通过它来执行本地IO操作。这意味着虚拟机将在每一次底层操作系统的IO操作调用前后,尝试避免将buffer中的内容拷贝到一个中间buffer。但是正因为如此,其内容会驻留在被垃圾收集器所管理的堆之外,所以其对于一个应用程序的内存占用的影响是显而易见的,创建和销毁的开销会比普通的InDirect来的更高,因此direct buffer仅在需要底层操作系统执行大量,长时间的IO操作时推荐使用。

2)Jetty中的Buffer


对于上图有几点需要留意的:

(1)Jetty的Buffer类型也可分为基于字节数组的IndirectNIOBuffer和基于Native MMap的DirectNIOBuffer

(2)Jetty主要用到了IndirectNIOBuffer、DirectNIOBuffer和非NIO的ByteArrayBuffer

(3)NIOBuffer与JDK的区别是什么?仅是封装了而已,一切为了框架嘛。

3)Jetty中的缓存池

HttpBuffers中持有缓存池中缓存的配置(类型,容量,大小等参数),还持有request和response缓存池,即上图的PooledBuffers。那么HttpBuffersImpl的身份如何,就要追溯到SelectChannelConnector了,它是以addBean的方式关联了SelectChannelConnector的生命周期中。

2、通道

通道是NIO的第二个创新,他提供了字节Buffer与I/O服务的直接连接,通常是Socket或者文件。通道是一种途径,借助该途径,可以用最小的开销来访问操作系统本身的服务,而Buffer则就是通道内部用来发送和接受数据的endpoint。

通道可以以阻塞或者非阻塞的模式运行,非阻塞的通道永远不会让调用的线程休眠,不过只有面向流的通道才支持非阻塞特性,正如socket。

ServerSocketChannel:该类是一个基于通道的socket监听器,区别于传统的serverSocket在于,它带有channel语义,具有非阻塞的特性。不过你设想下,如果accept都非阻塞了似乎不太优雅,因此jetty的NIO中对于该类的配置是阻塞的,只有accept到socket再注册在selectorSet中。

SocketChannel:使用最多的Channel,封装了点对点,有序的网络连接,每一个SocketChannel都是和一个对等的Socket串联的。

既然了解了通道怎样简单高效得访问本地I/O服务,那是该看看select是如何来管理这些通道的。

3、选择器

选择器是NIO第三个创新,它提供了可以选择已经就绪任务的能力,通过epool回调的方式获取已经就绪的任务,实现多路I/O复用。

Jetty中的NIO体系结构

上文中出现的各种Connection,Endpoint都属于NIO体系中的成员,初学者(即我)会犯迷糊,本段将带你扯清楚HttpParser、HttpBuffers、SelectChannelConnector、SelectChannelEndPoint、AsyncHttpConnection、SelectorManager之间乱七八糟的关系和Request和Response的缘由。

1、简介

1)HttpParser:从缓存池中取缓存,将channel的请求数据读取到缓存中,并解析成Request。

2)HttpBuffers:上面用到的那个缓存池

3)SelectChannelEndPoint:持有SocketChannel与基本的读写操作,正如其名:连接的端点。虽然不是请求的入口,但确是有效解析与处理操作的入口。

4)SelectorManager:持有Selector与SelectorSet,封装了NIO中的Selector,增强了其功能,也起到了负载均衡的效果。

5)AsyncHttpConnection:名字带有"Http",持有Httpparser,负责管理请求的解析和request、response的生产。

6)SelectChannelConnector:协调或生产上面那些玩意,控制并组装上面那些货的生命周期,名为:“SelectChannelConnectorControl”或许更为贴切。

2、主要组件的结构图

上图概括起来可分为三类:工具类(HttpBuffer,HttpParser),IO类(Select*),Server类(Server,SelectChannelConnector),Server类接受请求交给IO类来读取,IO类借助Server类和工具类解析并生产HttpConnection(里面持有Request、Response等产物),随后开始handler生产出来的Request。

3、时序图

阶段一:侦听请求

server启动的时候先是配置serverSocket,而后多线程doSelect(负载均衡),最后多线程accpet,serverSocketChannel设置为阻塞模式,有请求时将SocketChannel注册到SelectorManager,最终由上面的select线程来处理请求。

阶段二:建立连接

建立连接的意思是有read事件时生产EndPoint,HttpConnection,为解析请求和handler请求准备好环境。

阶段三:处理请求

主要是从SocketChannel中读取数据到缓存中,解析并生成Request,Response,最后handle该Request。

4、总结

概括起来,Jetty中的NIO架构大致模型如下图所示:

用户请求被侦听连接线程处理,平均注册读时间到两个SelectorSet中,分别为两个set起线程doSelect(),有Read事件时读取请求并解析Request,处理完成之后交给数据处理线程处理,因此对于servlet而言可没有复用的概念了,每个servlet消费一个线程,但是在消费的过程中如果有阻塞怎么搞呢,这就涉及到continunation和HttpClient了,下面的文章会做总结。

HttpClient的NIO

也许你已经习惯了HttpParser的作用就是解析Request,而HttpGenerator就是生产Response了。但是在HttpClient的应用中确截然相反。关于HttpClient的总结下文有详细介绍,这里简要介绍HttpClient中的NIO。

1、HttpConnection

AbstractHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
    {
        super(endp);

        _generator = new HttpGenerator(requestBuffers,endp);
        _parser = new HttpParser(responseBuffers,endp,new Handler());
    }

区别于上面的HttpConnection,parser主要是解析response;而generator则先生产request。

2、线程

1)HttpClient初始化时候线程池的配置

protected HttpClient createHttpClient(ServletConfig config) throws Exception
    {
        HttpClient client = createHttpClientInstance();
        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);

        String t = config.getInitParameter("maxThreads");

        if (t != null)
        {
            client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t)));
        }
        else
        {
            client.setThreadPool(new QueuedThreadPool());
        }

可以看到,HttpClient自身new的一份线程池。

2)HttpClient中NIO用到的线程池

class Manager extends SelectorManager
    {
        Logger LOG = SelectConnector.LOG;

        @Override
        public boolean dispatch(Runnable task)
        {
            return _httpClient._threadPool.dispatch(task);
        }

当然除了doSelect用到的线程,也有监控超时连接和超时请求的线程,不属于该NIO内容,细节由HttpClient章节总结。

Request&Response的前因后果

黑线表示默认走的流程,红线表示可选流程,完全由应用的具体业务逻辑而定。单纯的看代码挺烦的,这里就不多总结了,了解了这个图就差不多够了,如果您想具体了解下的话,可以参看Request-HttpParser-HttpConnection-Buffer,Response-HttpGenerator-HttpConnection-Buffer,这里对于协议的解析采用了注册事件回调函数的方式,不过仔细留意的话对于Request的解析Content解析完成后的事件回调函数没有对Content做任何处理,任由Content安静的呆在HttpParser中。

对于HttpClient的处理流程刚好相反,这里就不多画了。

转载于:https://my.oschina.net/tryUcatchUfinallyU/blog/110665

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值