热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Netty介绍及其工作原理

资料总结来自MIC老师,仅供学习使用。为什么选择NettyNetty其实就是一个高性能NIO框架,所以它是基于NIO基础上的封装,本质

资料总结来自MIC老师,仅供学习使用。

为什么选择Netty

Netty其实就是一个高性能NIO框架,所以它是基于NIO基础上的封装,本质上是提供高性能网络IO通信的功能。由于前面的课程中我们已经详细的对网络通信做了分析,因此在学习Netty时,学习起来应该是更轻松的。

Netty提供了上述三种Reactor模型的支持,我们可以通过Netty封装好的API来快速完成不同Reactor模型的开发,这也是为什么大家都选择Netty的原因之一,除此之外,Netty相比于NIO原生API,它有以下特点:


  • 提供了高效的I/O模型、线程模型和时间处理机制


  • 提供了非常简单易用的API,相比NIO来说,针对基础的Channel、Selector、Sockets、Buffffers等api提供了更高层次的封装,屏蔽了NIO的复杂性


  • 对数据协议和序列化提供了很好的支持


  • 稳定性,Netty修复了JDK NIO较多的问题,比如select空转导致的cpu消耗100%、TCP断线重连、keep-alive检测等问题。


  • 可扩展性在同类型的框架中都是做的非常好的,比如一个是可定制化的线程模型,用户可以在启动参数中选择Reactor模型、 可扩展的事件驱动模型,将业务和框架的关注点分离。


  • 性能层面的优化,作为网络通信框架,需要处理大量的网络请求,必然就面临网络对象需要创建和销毁的问题,这种对JVM的GC来说不是很友好,为了降低JVM垃圾回收的压力,引入了两种优化机制


  • 对象池复用,

  • 零拷贝技术


Netty**的生态介绍**

首先,我们需要去了解Netty到底提供了哪些功能,如图2-1所示,表示Netty生态中提供的功能说明。

后续内容中会逐步的分析这些功能。

Netty**的基本使用**

需要说明一下,我们讲解的Netty版本是4.x版本,之前有一段时间netty发布了一个5.x版本,但是被官方舍弃了,原因是:使用**ForkJoinPool增加了复杂性,并且没有显示出明显的性能优势。**同时保持所有的分支同步是相当多的工作,没有必要。

添加**jar包依赖** :使用**4.1.66版本**

      io.nettynetty-all

创建**Netty Server服务**

大部分场景中,我们使用的主从多线程Reactor模型,Boss线程是住Reactor,Worker是从Reactor。他们分别使用不同的NioEventLoopGroup

主Reactor负责处理Accept,然后把Channel注册到从Reactor,从Reactor主要负责Channel生命周期内的所有I/O事件。

public class NettyBasicServerExample {
​/*** 开发一个主从多reactor多线程模型的服务* @param args*/public static void main(String[] args) {//主线程主EventLoopGroup bossGroup=new NioEventLoopGroup(1);//表示工作线程组(register)EventLoopGroup workGroup=new NioEventLoopGroup(4);//构建Netty Server的APIServerBootstrap bootstrap=new ServerBootstrap();//Bootstrapbootstrap.group(bossGroup,workGroup)//指定epoll模型.channel(NioServerSocketChannel.class)//具体的工作处理类,负责处理相关SocketChannel的IO就绪事件.childHandler(new ChannelInitializer() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {//心跳的hander//编解码//协议处理//消息处理ch.pipeline().addLast("h1",new NormalMessageHandler()).addLast("h2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("收到消息------------第二处理器");}}); //处理IO事件}});try {ChannelFuture channelFuture=bootstrap.bind(8080).sync(); //同步阻塞等到客户端连接System.out.println("Netty Server Started Success:listener port:8080");channelFuture.channel().closeFuture().sync();//同步等到服务端监听端口关闭} catch (InterruptedException e) {e.printStackTrace();}finally {//释放资源workGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}
}

上述代码说明如下:


  • EventLoopGroup,定义线程组,相当于我们之前在写NIO代码时定义的线程。这里定义了两个线程组分别是boss线程和worker线程,boss线程负责接收连接,worker线程负责处理IO事件。boss线程一般设置一个线程,设置多个也只会用到一个,而且多个目前没有应用场景。而worker线程通常要根据服务器调优,如果不写默认就是cpu的两倍。


  • ServerBootstrap,服务端要启动,需要创建ServerBootStrap,在这里面netty把nio的模板式的代码都给封装好了。


  • ChannelOption.SO_BACKLOG

设置**Channel类型**

NIO模型是Netty中最成熟也是被广泛引用的模型,因此在使用Netty的时候,我们会采用NioServerSocketChannel作为Channel类型。

bootstrap.channel(NioServerSocketChannel.class);

除了NioServerSocketChannel以外,还提供了


  • EpollServerSocketChannel,epoll模型只有在linux kernel 2.6以上才能支持,在windows和mac都是不支持的,如果设置Epoll在window环境下运行会报错。


  • OioServerSocketChannel,用于服务端阻塞地接收TCP连接


  • KQueueServerSocketChannel,kqueue模型,是Unix中比较高效的IO复用技术,常见的IO复用技术有select, poll, epoll以及kqueue等等。其中epoll为Linux独占,而kqueue则在许多UNIX系统上存在。

注册**ChannelHandler**

在Netty中可以通过ChannelPipeline注册多个ChannelHandler,该handler就是给到worker线程执行的处理器,当IO事件就绪时,会根据这里配置的Handler进行调用。

这里可以注册多个ChannelHandler,每个ChannelHandler各司其职,比如做编码和解码的handler,心跳机制的handler,消息处理的handler等。这样可以实现代码的最大化复用。

              //具体的工作处理类,负责处理相关SocketChannel的IO就绪事件.childHandler(new ChannelInitializer() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {//心跳的hander//编解码//协议处理//消息处理ch.pipeline().addLast("h1",new NormalMessageHandler()).addLast("h2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("收到消息------------第二处理器");}}); //处理IO事件}});

ServerBootstrap中的childHandler方法需要注册一个ChannelHandler,这里配置了一个ChannelInitializer的实现类,通过实例化ChannelInitializer来配置初始化Channel。

当收到IO事件后,这个数据会在这多个handler中进行传播。上述代码中配置了一个NormalMessageHandler,用来接收客户端消息并输出。

绑定端口

完成Netty的基本配置后,通过bind()方法真正触发启动,而sync()方法会阻塞,直到整个启动过程完成。

ChannelFuture channelFuture=bootstrap.bind(port).sync();

NormalMessageHandler

ServerHandler继承了ChannelInboundHandlerAdapter,这是netty中的一个事件处理器,netty中的处理器分为Inbound(进站)和Outbound(出站)处理器,后面会详细介绍。

public class NormalMessageHandler extends ChannelInboundHandlerAdapter {
​//channelReadComplete方法表示消息读完了的处理,writeAndFlush方法表示写入并发送消息@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//这里的逻辑就是所有的消息读取完毕了,在统一写回到客户端。// Unpooled.EMPTY_BUFFER表 示空消息,addListener(ChannelFutureListener.CLOSE)表示写完后,就关闭连接ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);}
​//channelRead方法表示读到消息以后如何处理,这里我们把消息打印出来@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf in = (ByteBuf) msg;byte[] req = new byte[in.readableBytes()];in.readBytes(req);//把数据读到byte数组中System.out.println("服务端收到的数据:" + new String(req, "utf-8"));//写回数据ByteBuf resp = Unpooled.copiedBuffer(("receive message:" + new String(req, "utf-8") + "").getBytes());
​ctx.write(resp);//ctx.write表示把消息再发送回客户端,但是仅仅是写到缓冲区,没有发送,flush才会真正写 到网络上去super.channelRead(ctx, msg);}
​//exceptionCaught方法就是发生异常的处理@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {super.exceptionCaught(ctx, cause);}
}

通过上述代码发现,我们只需要通过极少的代码就完成了NIO服务端的开发,相比传统的NIO原生类库的服务端,代码量大大减少,开发难度也大幅度降低

Netty**和NIO的api对应**

TransportChannel ----对应NIO中的channel

EventLoop---- 对应于NIO中的while循环

EventLoopGroup: 多个EventLoop,就是事件循环

ChannelHandler和ChannelPipeline---对应于NIO中的客户逻辑实现

handleRead/handleWrite(interceptor pattern)

ByteBuf---- 对应于NIO 中的ByteBuffffer

Bootstrap 和 ServerBootstrap ---对应NIO中的Selector、ServerSocketChannel等的创建、配置、启动等

Netty**的整体工作机制**

Netty的整体工作机制如下,整体设计就是前面我们讲过的多线程Reactor模型,分离请求监听和请求处理,通过多线程分别执行具体的handler。

 

网络通信层

网络通信层主要的职责是执行网络的IO操作,它支持多种网络通信协议和I/O模型的链接操作。当网络数据读取到内核缓冲区后,会触发读写事件,这些事件在分发给时间调度器来进行处理。

在Netty中,网络通信的核心组件以下三个组件


  • Bootstrap, 客户端启动api,用来链接远程netty server,只绑定一个EventLoopGroup


  • ServerBootStrap,服务端监听api,用来监听指定端口,会绑定两个EventLoopGroup,bootstrap组件可以非常方便快捷的启动Netty应用程序


  • Channel,Channel是网络通信的载体,Netty自己实现的Channel是以JDK NIO channel为基础,提供了更高层次的抽象,同时也屏蔽了底层Socket的复杂性,为Channel提供了更加强大的功能。

如图2-3所示,表示的是Channel的常用实现实现类关系图,AbstractChannel是整个Channel实现的基类,派生出了AbstractNioChannel(非阻塞io)、AbstractOioChannel(阻塞io),每个子类代表了不同的I/O模型和协议类型。

随着连接和数据的变化,Channel也会存在多种状态,比如连接建立、连接注册、连接读写、连接销毁。随着状态的变化,Channel也会处于不同的生命周期,每种状态会绑定一个相应的事件回调。以下是常见的时间回调方法。


  • channelRegistered, channel创建后被注册到EventLoop上

  • channelUnregistered,channel创建后未注册或者从EventLoop取消注册

  • channelActive,channel处于就绪状态,可以被读写

  • channelInactive,Channel处于非就绪状态

  • channelRead,Channel可以从源端读取数据

  • channelReadComplete,Channel读取数据完成

简单总结一下,Bootstrap和ServerBootStrap分别负责客户端和服务端的启动,Channel是网络通信的载体,它提供了与底层Socket交互的能力。

而当Channel生命周期中的事件变化,就需要触发进一步处理,这个处理是由Netty的事件调度器来完成。

事件调度器

事件调度器是通过Reactor线程模型对各类事件进行聚合处理,通过Selector主循环线程集成多种事件(I/O时间、信号时间),当这些事件被触发后,具体针对该事件的处理需要给到服务编排层中相关的Handler来处理。

事件调度器核心组件:


  • EventLoopGroup。相当于线程池

  • EventLoop。相当于线程池中的线程

EventLoopGroup本质上是一个线程池,主要负责接收I/O请求,并分配线程执行处理请求。为了更好的理解EventLoopGroup、EventLoop、Channel之间的关系,我们来看图2-4所示的流程。

从图中可知


  • 一个EventLoopGroup可以包含多个EventLoop,EventLoop用来处理Channel生命周期内所有的I/O事件,比如accept、connect、read、write等


  • EventLoop同一时间会与一个线程绑定,每个EventLoop负责处理多个Channel


  • 每新建一个Channel,EventLoopGroup会选择一个EventLoop进行绑定,该Channel在生命周期内可以对EventLoop进行多次绑定和解绑。

图2-5表示的是EventLoopGroup的类关系图,可以看出Netty提供了EventLoopGroup的多种实现,如NioEventLoop、EpollEventLoop、NioEventLoopGroup等。

从图中可以看到,EventLoop是EventLoopGroup的子接口,我们可以把EventLoop等价于EventLoopGroup,前提是EventLoopGroup中只包含一个EventLoop。

EventLoopGroup是Netty的核心处理引擎,它和前面我们讲解的Reactor线程模型有什么关系呢?其实,我们可以简单的把EventLoopGroup当成是Netty中Reactor线程模型的具体实现,我们可以通过配置不同的EventLoopGroup使得Netty支持多种不同的Reactor模型。


  • 单线程模型,EventLoopGroup只包含一个EventLoop,Boss和Worker使用同一个EventLoopGroup。


  • 多线程模型:EventLoopGroup包含多个EventLoop,Boss和Worker使用同一个EventLoopGroup。


  • 主从多线程模型:EventLoopGroup包含多个EventLoop,Boss是主Reactor,Worker是从Reactor模型。他们分别使用不同的EventLoopGroup,主Reactor负责新的网络连接Channel的创建(也就是连接的事件),主Reactor收到客户端的连接后,交给从Reactor来处理。

服务编排层

服务编排层的职责是负责组装各类的服务,简单来说,就是I/O事件触发后,需要有一个Handler来处理,所以服务编排层可以通过一个Handler处理链来实现网络事件的动态编排和有序的传播。

它包含三个组件


  • ChannelPipeline,它采用了双向链表将多个Channelhandler链接在一起,当I/O事件触发时,ChannelPipeline会依次调用组装好的多个ChannelHandler,实现对Channel的数据处理。

    ChannelPipeline是线程安全的,因为每个新的Channel都会绑定一个新的ChannelPipeline。一个ChannelPipeline关联一个EventLoop,而一个EventLoop只会绑定一个线程,如图2-6所示,表示ChannelPIpeline结构图。

     

从图中可以看出,ChannelPipeline中包含入站ChannelInBoundHandler和出站ChannelOutboundHandler,前者是接收数据,后者是写出数据,其实就是InputStream和OutputStream,为了更好的理解,我们来看图2-7。


  • ChannelHandler, 针对IO数据的处理器,数据接收后,通过指定的Handler进行处理。


  • ChannelHandlerContext,ChannelHandlerContext用来保存ChannelHandler的上下文信息,也就是说,当事件被触发后,多个handler之间的数据,是通过ChannelHandlerContext来进行传递的。ChannelHandler和ChannelHandlerContext之间的关系,如图2-8所示。

    每个ChannelHandler都对应一个自己的ChannelHandlerContext,它保留了ChannelHandler所需要的上下文信息,多个ChannelHandler之间的数据传递,是通过ChannelHandlerContext来实现的。

     

以上就是Netty中核心的组件的特性和工作机制的介绍,后续的内容中还会详细的分析这几个组件。可以看出,Netty的架构分层设计是非常合理的,它屏蔽了底层NIO以及框架层的实现细节,对于业务开发者来说,只需要关心业务逻辑的编排和实现即可。

组件关系及原理总结

如图2-9所示,表示Netty中关键的组件协调原理,具体的工作机制描述如下。


  • 服务单启动初始化Boss和Worker线程组,Boss线程组负责监听网络连接事件,当有新的连接建立时,Boss线程会把该连接Channel注册绑定到Worker线程


  • Worker线程组会分配一个EventLoop负责处理该Channel的读写事件,每个EventLoop相当于一个线程。通过Selector进行事件循环监听。


  • 当客户端发起I/O事件时,服务端的EventLoop讲就绪的Channel分发给Pipeline,进行数据的处理

  • 数据传输到ChannelPipeline后,从第一个ChannelInBoundHandler进行处理,按照pipeline链逐个进行传递


  • 服务端处理完成后要把数据写回到客户端,这个写回的数据会在ChannelOutboundHandler组成的链中传播,最后到达客户端。

 


每天努力一点,每天都在进步


推荐阅读
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 本文主要介绍了MySQL中子查询的基本用法和三种用法,包括生成参考值、内层查询与外层查询的比较操作以及使用事件号在成绩表中找到学生的分数记录。通过详细解析子查询的实例,帮助读者更好地理解和应用子查询。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
author-avatar
葛菁昱
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有