终于进入到Netty框架的环节了,前面介绍了大量的Java-NIO的内容,核心的内容Selector、Channel、Buffer、Reactor掌握了,那么学起来Netty也是水到渠成的事情。如果没有掌握前面的内容那么学Netty会非常吃力,下面讲解Netty核心原理与概念。
Netty是一个Java NIO客户端/服务器框架,是一个为了快速开发可维护的高性能、高可扩展的网络服务器和客户端程序而提供的异步事件驱动基础框架和工具。如果要开发一个网络通信模块,那么Netty是首选,知名的Dubbo、Zookeeper、RocketMQ 网络通信模块全都是Netty实现的,我发现阿里的分布式框架网络通信模块很喜欢使用Netty作为网络通信框架。Netty的目标之一,是使通信开发可以做到“快速和轻松”。使用Netty除了能“快速和轻松”的开发TCP/UDP等自定义协议的通信程序之外,使用Netty还可以做到“快速和轻松”地开发应用层协议的通信程序,如FTP, SMTP, HTTP以及其他的传统应用层协议。 总之一句话,Netty是一款高性能、高可扩展性、能进行快速扩展以支持不同协议通信、完成不同业务处理的网络通信框架。
在使用Netty前,首先需要考虑一下JDK的版本, 建议使用JDK1.8。然后是Netty的版本,建议使用Netty 4.0以上的版本:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.66.Final</version>
</dependency>
Netty
中的Reactor
反应器
前面一篇Reactor的文章不知道强调了多少次Reactor的概念,如果还不清楚,这里再次强调:Reactor就是一个单独的线程执行的Selector组件接受客户端IO事件的实例。粗暴理解成 Reactor = Thread + Selector。在Netty的实现中Reactor角色叫做NioEventLoop(事件循环),它就是封装了Selector组件对象和Thread线程实例。NioEventLoop类有两个重要的成员属性:一个是Thread线程类的成员,一个是Java NIO选择器的成员属性。 NioEventLoop的继承关系和主要的成员属性,如下图所示。
NioEventLoop和前面章节讲到反应器实现,在思路上是一致的:一个NioEventLoop拥有一个Thread线程,负责一个Java NIO Selector选择器的IO事件轮询。其实可以简单吧Reactor理解成Selector就行,在Netty中, EventLoop反应器和Channel通道的关系是啥呢?理论上来说,一个EventLoop反应器和NettyChannel通道是一对多的关系:一个反应器可以注册成千上万的通道。
后面系列文章将结合Channel、Handler等知识通过代码进行介绍,目前只要理解了NioEventLoop就是Reactor反应器即可,理解Reactor是什么就行。
Netty
中的Channel
通道
前面也讲过NIO中的Channel通道,反应器模式和通道紧密相关,反应器的查询和分发的IO事件都来自于Channel通道组件。Netty中不直接使用Java NIO的Channel通道组件,对Channel通道组件进行了自己的封装。Netty实现了一系列的Channel通道组件,为了支持多种通信协议,换句话说,对于每一种通信连接协议, Netty都实现了自己的通道。Netty中不直接使用Java NIO的Channel通道组件,对Channel通道组件进行了自己的封装。Netty实现了一系列的Channel通道组件,为了支持多种通信协议,换句话说,对于每一种通信连接协议, Netty都实现了自己的通道。
Netty中常见的通道类型如下:
NioSocketChannel
:异步非阻塞TCP Socket传输通道。NioServerSocketChannel
:异步非阻塞TCP Socket服务器端监听通道。NioDatagramChannel
:异步非阻塞的UDP传输通道OioSocketChannel
:同步阻塞式TCP Socket传输通道。OioServerSocketChannel
:同步阻塞式TCP Socket服务器端监听通道。OioDatagramChannel
:同步阻塞式UDP传输通道。
不论是那种通道类型,在主要的API和使用方式上和NioSocketChannel类基本是相同的,更多是底层的传输协议不同,而Netty帮大家极大的屏蔽了传输差异,所以,如果没有特殊情况,的很多通道都可以参考NioSocketChannel通道。Netty只是做了封装,底层还是Java-NIO的Channel通道,继承关系如下:
Netty
中的Handler
处理器
在Netty中, EventLoop反应器内部有一个线程负责Java NIO选择器的事件的轮询,然后进行对应的数据分发。这里和经典Reactor模式的区别: Netty的IO事件分发(Dispatch) ,属于EventLoop的内部分发, 并没有直接将IO事件分发到EventLoop的外部。如何理解?假设发生了IO读事件,那么EventLoop将输入的数据读取到ByteBuf中。EventLoop读取到数据之后,再将输入数据分发到通道的Pipeline, 此次数据分发的目标,才是Netty的Handler处理器。也就是说IO事件还是自己读,只是将读到的数据放到Pipeline中,供程序员对数据进行处理,而这个处理也就是Handler处理器类要做的事情。Netty的Handler处理器分为两大类:第一类是ChannelInboundHandler入站处理器;第二类是ChannelOutboundHandler出站处理器,二者都继承了ChannelHandler处理器接口。
Ok@! 很多读者读到这开始懵逼了,什么出站入站嘛!我连Handler都不知道是啥呢?你又引入了什么出站处理器和入站处理器,还有什么乱七八糟的Pipeline是啥? 我是懂读者心里状态的,因为我学习的时候也是带着这些疑问硬啃完的,相信我,继续往下看你的疑问将柳暗花明。
前面介绍过Selector反应器,其中有个Selector选择器,如果客户端A已经连接了服务器,此时向服务器发送数据,那么对于服务器而言将产生一个IO读事件,通过Selector的select方法将查询到这个读事件,然后接下来对这个读事件进行处理。而对IO事件的处理就是处理器,处理器在前面我们讲解Reactor反应器中没有特意定义为一个类,而只是一个处理方法。而在Netty中,如果我们需要对已经查询到的IO事件进行处理,我们需要重写处理器类的一些读数据方法或者写数据方法,而这些处理器又分为ChannelOutboundHandler出站处理器和ChannelInboundHandler入站处理器。
那么什么是入站处理器呢?什么是出站处理器呢?Netty的出入站处理指的的API调用的方向是应用层开发维度的,你可以理解为当发生了IO读事件会被EventLoop查询到,然后分发到内部的IO事件处理方法, 之后把读取到的客户端数据发射到通道的Pipeline, 这里还没有讲解Pipeline的概念,你可以理解为一个数据流水线。然后这个数据就会在数据流水线中往下传播,其中就有ChannelInboundHandler入站处理器,处理器的方法read将被调用读取流进来的数据,然后程序员就可以在这里实现对数据的处理,例如解码、显示到界面,然后保存到数据库等操作。IO事件触发了,然后EventLoop读取数据把数据往Pipeline发送,其中经过入站处理器。这不就和废水处理厂一样的逻辑吗?数据就是废水,废水入站进行处理,处理完之后再向下传播或者终止。
明白了上述入站处理器之后,出站处理器就不难理解了吧!Netty中的出站处理具体指的是什么呢?指的是从ChannelOutboundHandler处理器到通道的某次IO操作,例如,在应用程序完成业务处理后,可以通过ChannelOutboundHandler出站处理器将处理的结果写入底层通道。它的最常用的一个方法就是write()方法,把数据写入到通道。最直观的理解就是通过出站处理器将服务器的数据进行处理,例如从数据库中查询,然后编码,然后再将处理的数据写到底层Channel通道中发送给客户端。Netty中的出站处理,不仅仅包括write()方法,还包括从Handler处理器到底层Channel的方向的其他操作。 Netty出站和Java NIO的出站在概念上有细微的区别,其实不需要分的这么清楚,入站就是读客户端的数据进行处理,出站就是服务器的数据处理完后发送出去,就这样理解。
无论是入站还是出站, Netty都提供了各自的默认适配器实现:
ChannelInboundHandler
的默认实现为ChannelInboundHandlerAdapter
(入站处理适配器) ;ChannelOutboundHandler
的默认实现为ChanneloutBoundHandlerAdapter
(出站处理适配器)。
这两个默认的通道处理适配器,分别实现了基本的入站操作和出站操作功能。如果要实现自己的业务处理器,不需要从零开始去实现处理器的接口,只需要继承通道处理适配器即可。
Netty
的Pipeline
通道处理流水线
学习到现在,我们知道了Reactor是和Channel进行绑定的,但是上面的Handler和Channel如何建立连接关系呢?Netty设计了一个特殊的组件,叫做ChannelPipeline(通道处理流水线),它像一条管道,将一个通道的多个Handler处理器实例串在一起,形成一条流水线。ChannelPipeline(通道流水线)的默认实现,实际上被设计成一个双向链表。所有的Handler处理器实例被包装成了双向链表的节点,被加入到了ChannelPipeline(通道流水线)中。一个 Netty 通道拥有一个 ChannelPipeline 通道流水线类型的成员属性,该属性的名称叫做 pipeline。以入站处理为例。每一个来自通道的IO数据,都会进入一次ChannelPipeline通道流水线。在进入第一个Handler处理器后,这个IO数据将按照既定的从前往后次序,在流水线上不断地向后流动,流向下一个Handler处理器。如果后面没有其他的入站处理器,这就意味着这个IO数据在此次流水线中的处理结束了。如果在中间需要终止流动,可以选择将当前处理器的结果,不再交给下一个Handler处理器,流水线的执行也被截断了。
Netty的通道流水线与普通的流水线不同, Netty的流水线不是单向的,而是双向的,而普通的流水线基本都是单向的。 Netty是这样规定的:入站处理器Handler的执行次序,是从前到后,或者说从头到尾;出站处器Handler的执行次序,是从后到前。总之, IO事件在流水线上的执行次序,与IO事件的类型是有关系的 。除了流动的方向与IO操作类型有关之外,流动过程中所经过的处理器类型,也是与IO操作的类型有关。入站类型的IO操作, 只能从Inbound入站处理器类型的Handler向后传播;出站的IO操作, 只能从Outbound出站处理器类型的Handler向前传播。
到这里,Netty的各个组件就基本上介绍完毕,后面几期文章就会从代码的实现来讲解如何使用Netty框架。