Netty框架初探

Posted 中兴大数据

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty框架初探相关的知识,希望对你有一定的参考价值。

文 | 庄磊@中兴大数据

Netty简介

Netty作为当前最流行的NIO框架,在游戏、大数据通讯,互联网领域都有广泛的应用。它是一个异步非阻塞的、事件驱动型的高性能网络开发框架。通过对Socket的封装,简化和流线化了网络应用的编程开发过程。例如,TCPUDP和文件传输等网络应用的开发。Netty通过精心的项目设计,支持多种协议的开发,如FTPSMTPHTTP,各种二进制,自定义消息体结构的的开发。上面这些优点可以帮助我们快速开发出一个网络应用程序。

范例

下面我通过一个简单的helloworld程序,简单描述一下Netty的整体架构和工作流程。

服务端

启动程序:Netty框架初探

服务端Handler:Netty框架初探

客户端

客户端启动:Netty框架初探

客户端handler:Netty框架初探

范例讲解

BootStrap

Netty框架中。Netty程序的开始,用于配置EventLoopGroupChannelHandler,启动端口号,连接配置参数options

  1. group(...):定义通信框架使用何种线程模型,输入参数EventLoopGroup是Netty实现线程异步执行的基础。例如范例中服务端通过简单的设置两个NioEventLoopGroup,boss和worker就实现了一个Reactor的线程运行方式。

  2. .channel(...)定义了网络服务框架应用层的实现方式。范例中使用的是NIO方式。

  3. .handler(...):用于向Channl中添加用于ChannelHandler,范例中使用ChannelInitializer向Channel中的ChannelPipeline中添加ChannelHandler,每个ChannelHandler处理自己感兴趣的事件,我们的业务逻辑都是写在handler之中。

线程模型EventLoopGroup

EventLoopGroup,把这个词拆开Event(事件)Loop(循环)group()他就是一组事件的循环执行。根据是阻塞还是非阻塞的他有不同的实现子类。OioEventLoopGroup对应的是阻塞式,NioEventLoopGroup对应的非阻塞的,Netty官方也是推荐后者。如下图:Netty框架初探

服务端的范例中定义了bossworker两个NioEventLoopGroup,它的执行原理就是专门有个NIO线程boss用于监听服务端,接受client端的连接请求。当连接建立后就交给worker线程去负责消息的读取,解码,编码和发送。打个比方,就像现实生活中企业的老板负责谈业务接单子,有了订单就交给手下的工人去干。客户端使用的BootStrap只需定义一个Group线程。而通过这种方式就能实现了Reactor线程模型。不需要我们在做额外的开发工作。

它的线程模型如下图所示:Netty框架初探

Netty框架中,无论是I/O操作还是非I/O都会被放在EventLoopGroup中去执行,实际上EventLoopGroup是一组EventLoop集合。观察NioEventLoopGroupNioEventLoop的继承关系图可以看出他们两个有是一个共同的父类EventExecutorGroupNetty框架初探

EventLoopGroup的父类MultithreadEventExecutorGroup的构造函数中可以看出,他会根据nThreads,默认情况下nThreads的取值是是当前cpu的个数\*2,创建一个由NioEventExecutor组成的数组,每个元素都设置了selector选择器和拒绝策略。由此可见至少io操作是放在NioEventExecutor`中执行的。

流程控制的ChannelPipeline

ChannelPipeline实际上是ChannelHandler的容器,采用了职责链的设计模式。它负责ChannelHandler的增加,删除,修改和删除,通过ChannelHandlerContextChannelHandler组成一个双向链表。 

它还通过自身的inbound和outbound事件触发消息在依次在ChannelHandler的之间传输。但是pipeline并不是直接同chandler进行交互,而是通过ChannelHandlerContext控制handler的执行。 

如何构建pipeLine,范例中使用ChannelInitializer来构建一个简单的pipeline。Netty框架初探

pipeline中有两个内部channelHandlerContext:headtail。范例中通过ChannelInitializer类添加进来的ChannelHandler都在这两个内部channelHandlerContext之间。 

一个消息在ChannelPipeline中大致处理顺序是:

  1. 由前面提到的AbstractNioMessageChannel中的NioMessageUnsafe.read()方法触发I/O事件,然后调用ChannelPipeline.fireChannelRead方法,将消息传入到pipeline中,开始在ChannelHandler的链表表中执行。

  2. 消息的执行顺序是从headContext开始,然后依次执行自定义的ChannelInboundHandler1,ChannelInboundHandler2..ChannelInboundHandlerN中执行,最后到TailContext中收尾。

  3. 如果有事件调用了ChannelHandlerContext中的write方法,消息将会从tail开始,从ChannelOutboundHandlerN,ChannelOutboundHandlerN-1..ChannelOutboundHandler1,headHandler的顺序执行。

接口ChannelPipeline中的API中的一张图很清楚的说明了这种调度关系,如下图:Netty框架初探

当向pipeline中添加handler时,pipeline自动用ChannelHandlerContextChannelHandler封装起来,存放到吱声中。如下面的代码段,从代码中可以看出,会将NioEventLoopGroupEventExecutorGroup的子类)也封装到ChannelHandlerContext中去,供handler调用。Netty框架初探

ChannelHandlerContext

ChannelHandlerContext接口及其子类,我们在开发中不会直接接触到,但当我们向channelpipeline中添加handler的时候,Netty会将pipelinehandler绑定在一起。

Netty中代表上下文关系的一个接口。比如在其子类AbstractChannelHandlerContext中定义了:Netty框架初探

当我们开发时向pipeline中增加handler时,Netty会将这个handler通过context与前和后的handler相关联,组成一个双向链表。

在范例的ServerHandler中Netty框架初探

结合前面Handler中提到的,ctx.write调用的是ChannelOutboundHandler中的事件,实际上就是向pipeline中待发送区写入消息。比如ctx.write(in)就是向client端发送数据。如果把这句注释掉,client端就不会接收到任何数据。 

如果需要调用的是ctx.fireChannelRead(in)就会寻找下一个ChannelInboundHandler的ChannelRead方法。从下图的代码中可以看出,操作最终是放到EventExecutor中执行的,前面提到过EventExecutor又是EventExecutorLoop的父类,这里就解释了Netty的所有操作都是放到线程中去执行的。Netty框架初探

ChannelHandler

ChannelHandler类似拦截器,他是基于事件驱动的,他会根据自身的事件选择性地拦截在pipeline中传输的数据,比如ServerHandler中只对channelRead,channelReadCompleteexceptionCaught。也可以直接终止整个事件的传输,比如exceptionCaught方法中当发生异常情况就直接调用ctx.close()关闭连接。 

ChannelHandler有两个重要的子接口: 

  • ChannelInboundHandler

是指当某个I/O事件如链路的建立和关闭,数据的读取所触发事件。

为了方便用户开发,推荐直接继承ChannelInboundHandlerAdapter,然后对自己感兴趣的事件进行覆盖重写即可。比如范例中的ServerHandler,就覆盖了channelRead、channelReadComplete,exceptionCaught三个方法。 

  • ChannelOutboundHandler

Netty或者用户触发的I/O都被称为OutBound事件。比如bindconnectdisconnnectreadwrite事件等。

同样也有一个ChannelOutboundHandlerAdapter,方便自定义开发。

小结

ChannelPipelineChannelHandlerChannelHandlerContext三者的权责关系是非常清晰的,符合了单一职责的原则。他们就像一条流水线,ChannelPipeline负责整个流水线的正常运转,ChannelHandler就像一个工人随时准备处理流水线的产品,ChannelHandlerContext就像传送带,在工人之间传输产品。

Netty推荐我们在开发业务handler时直接继承ChannelOutboundHandlerAdapter和ChannelInboundHandlerAdapter,因为他们对每种事件都有默认的处理方法,不需要我们重写全部的时间方法。

Channel

NettyChannel采用了Facade的模式进行了统一封装。将JDKsocketChannelSeverSocketChannel统一封装在一起,同时为了适应前面提到的Netty框架中的ChannelPipeline,EventLoopGroup等功能,它使用聚合的方式把这些也归纳进来,为的就是为外界提供一个统一的方便的接口。比如范例中使用到的NioserverSocketChannel就是,它的关系继承图如下:Netty框架初探

通过观察下图中抽象类AbstractChannel的子类,就可以很清晰的看出每个子类对应的不同的应用场景。我们在开发的时候只需要根据不同的场景使用不同的子类即可。Netty框架初探

需要注意的是,每种Channel必须使用对应的EventLoopGroupAbstratctNioChannel只能使用NioEventLoopGroupAbstractOioSocket只能使用OioEventLoopGroup 

在channel中有一个重要的内部类Unsafe,每种channel的unsafe都不一样,channel使用unsafe来实现最底层的网络通信。如AbstractNioMessageChannel中的Unsafe。Netty取名unsafe不是说他不安全,我觉得而是让后面的开发者别乱改变他。Netty框架初探

总结

到此为止,我们把Netty框架中最基础的概念稍稍讲解了一遍。Netty作为一款优秀的网络开发的框架,我们可以通过阅读他的源码学习他在编程技巧和编程思想上优点,开拓自己的编程思维,并可以将这些思维应用到平时的开发过程当中。


 

大数据时代的思考和洞察

长按二维码关注

以上是关于Netty框架初探的主要内容,如果未能解决你的问题,请参考以下文章

BIO/NIO 线程模型以及高性能通讯框架 Netty Reactor 模型初探

NETTY框架的使用

Netty框架之Reactor线程模型

高性能通讯框架——Netty

Netty网络框架

学习010 Netty异步通信框架