1.概述

Netty是一个异步的、基于事件驱动网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。

Netty: Home

消息驱动:鼠标自己点击不需要和系统有过多的交互,消息由系统(第三方)循环检测,来捕获并放入消息队列。消息对于点击事件来说是被动产生的,高内聚。

事件驱动:鼠标点击产生点击事件后要向系统发送消息 “我点击了” 的消息,消息是主动产生的。再发送到消息队列中。事件往往会将事件源包装起来。

事件驱动往往和轮询机制相关,它们通常被统称为 event loop。重点在于并不会给每一个事件分配一个轮询来探知其变化,而是设置一个中央轮询中心,用这个轮询中心去轮询每个注册的对象。轮询中心一旦检测到了注册其中的对象有事件发生,那么就通知对此事件感兴趣的对象。而对此事件感兴趣的对象此时会调用的方法被称为回调函数

参考:事件驱动和消息驱动_wjjiang2333的博客-CSDN博客_消息驱动和事件驱动

  • 事件队列(event queue):接收事件的入口,存储待处理事件
  • 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元
  • 事件通道(event channel):分发器与处理器之间的联系渠道
  • 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作

Netty在java网络应用框架中的地位就好比:spring在javaee开发中的地位

以下的框架都使用到了Netty因为他们都有网络通信需求:

  • Cassandra - nosql数据库
  • Spark - 大数据分布式计算框架
  • Hadoop - 大数据分布式存储框架
  • RocketMQ - ali开源的消息队列
  • ElasticSearch - 搜素引擎
  • gRPC - rpc框架
  • Dubbo - rpc框架
  • spring5.x - flux api完全抛弃了tomcat,使用Netty作为服务器端
  • Zookeeper - 分布式协调框架

Netty的优势:

Netty vs NIO

  • NIO工作量大,bug多
  • 需要自己构建协议
  • 解决TCP传输问题,如粘包、半包
  • epoll空轮训导致的cpu100%
  • 对API进行了增强,是指更易使用

Netty采用的模型:CSDN

2.HelloWorld

首先从HelloWorld入手,开发一个简单的服务器和客户端,客户端向服务器发送HelloWorld,服务器接收不返回。

加入Netty的依赖包(注意5.之后的版本已经被弃用)

    <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.69.Final</version></dependency>

服务器代码:

 public static void main(String[] args) {//服务端启动器,负责组装netty组件,协调他们的工作new ServerBootstrap()//BossEventLoop、WorkerEventLoop,  包涵线程和选择器.group(new NioEventLoopGroup())//选择基于NIO的ServerSocketChannel.channel(NioServerSocketChannel.class)//决定了worker(child)将来能执行哪些操作(handler).childHandler(//channel代表和客户端进行数据读写的通道 Initializer初始化器,负责添加别的handlernew ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel sc) throws Exception {//添加具体的handlersc.pipeline().addLast(new StringDecoder());//将ByteBuf转换为字符串//添加自定义handlersc.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Override//读事件                    ,msg为上一步被转换为字符串的对象public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(msg);}});}})//绑定监听端口     .bind(8080);}

客户端代码:

 public static void main(String[] args) throws InterruptedException, IOException {//创建启动器类,对应于服务器端的ServerBootstrapChannel channel = new Bootstrap()//添加EventLoop.group(new NioEventLoopGroup())//选择客户端channel实现.channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {@Override//在连接建立后调用protected void initChannel(NioSocketChannel ch) throws Exception {//编码器,把字符串变为字节数组,发送到服务器ch.pipeline().addLast(new StringEncoder());}})//连接到服务器.connect(new InetSocketAddress("127.0.0.1", 8080))//sync是为了让客户端先同步的方式连上然后再执行后面的信息发送逻辑.sync()//代表连接对象,也就是SocketChannel.channel();channel.writeAndFlush("hello world");}

服务端成功打印了HelloWorld。

理解:

  • 把channel理解为数据的通道
  • 把msg理解为流动的数据,最开始输入的是ByteBuf,但经过pipeline的加工,会变成其它类型的对象,最后输出又变为ByteBuf
  • 把handler理解为数据的处理工序
    • 工序有多道,合并在一起就是pipeline,pipeline负责发布事件(读、读取完成……)传播给每个handler,handler对自己感兴趣的事件进行处理(重写了相应事件处理方法)
    • handler分为Inbound和Outbound两类
  • 把eventLoop理解为处理数据的工人(线程)
    • 工人可以管理多个channel的 io操作,并且一旦工人负责了某个channel,就要负责到底(绑定)
    • 工人既可以执行io操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个channel的待处理任务,任务分为普通任务、定时任务
    • 工人按照pipeline顺序,依次按照 handler的规划(代码)处理数据,可以为每道工序指定不同的工人

3.组件

3.1EventLoop*

EventLoop本质是一个单线程执行器(同时维护了一个Selector),里面有run方法处理Channel源源不断的IO事件。

继承关系:

  • 一条线是继承自j.u.c.ScheduledExecutorService因此包含了线程池中所有的方法
  • 另一条线是继承自netty自己的OrderedEventExecutor
    • 提供了boolean inEventLoop(Thread thread)方法判断一个线程是否属于此EventLoop
    • 提供了parent方法来看看自己属于哪个EventLoopGroup
  • shutdownGracefully();  优雅的关闭事件循环组

EventLoopGroup是一组EventLoop,Channel一般会调用EventLoopGroup的 register方法来绑定其中一个EventLoop,后续这个Channel 上的 io事件都由此EventLoop来处理(保证了io事件处理时的线程安全)

  • 继承自netty自己的 EventExecutorGroup实现了lterable接口提供遍历EventLoop 的能力
  • 另有next方法获取集合中下一个 EventLoop
  • 创建时在构造方法可以指定一共多少个核心线程数,默认为下面的数量,根据Netty参数和物理机的线程数决定。建议IO密集型2*n的核心数,计算密集型n+1的核心数
private static final int DEFAULT_EVENT_LOOP_THREADS =
Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

演示EventLoopGroup执行普通任务和定时任务

public static void main(String[] args) {//创建EventLoopGroup,构造方法可以指定核心线程数,默认只有1个//NioEventLoopGroup实现类,功能最全,io事件、普通任务、定时任务都可以处理EventLoopGroup group=new NioEventLoopGroup(2);//DefaultEventLoop主要处理普通任务和定时任务EventLoopGroup group1=new DefaultEventLoop();//采用轮询的方式去获得事件循环组里的事件循环EventLoop next = group.next();//执行普通任务group.next().execute(()->{System.out.println(Thread.currentThread().getName());//nioEventLoopGroup-2-1});//执行定时任务group.next().scheduleAtFixedRate(()->{System.out.println(Thread.currentThread().getName());//nioEventLoopGroup-2-2},1, 1,TimeUnit.SECONDS);
}

演示EventLoopGroup执行IO事件

服务器:

public static void main(String[] args) {new ServerBootstrap()//细分任务。第一个表示boss处理accept事件,第二个是worker处理读写事件.group(new NioEventLoopGroup(),new NioEventLoopGroup(2)).channel(NioServerSocketChannel.class)//将来建立连接后给SocketChannel添加一些处理器.childHandler(new ChannelInitializer<NioSocketChannel>() {@Override//连接建立后执行protected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Override//关心channel的读事件,重写该方法,此时的msg是ByteBuf类型public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;//可以直接转为StringSystem.out.println(Thread.currentThread().getName()+buf.toString(StandardCharsets.UTF_8));}});}}).bind(8080);
}

客户端:

//创建启动器类,对应于服务器端的ServerBootstrap
Channel channel = new Bootstrap()//添加EventLoop.group(new NioEventLoopGroup())//选择客户端channel实现.channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {@Override//在连接建立后调用protected void initChannel(NioSocketChannel ch) throws Exception {//编码器,把字符串变为自己数组,发送到服务器ch.pipeline().addLast(new StringEncoder());}})//连接到服务器.connect(new InetSocketAddress("127.0.0.1", 8080)).sync().channel();
System.in.read();

调试:

可以看到初始时指定两个EventLoop线程,当有三个客户端时,共用这两个EventLoop。图解如下

其中的head和相当于哨兵

多reactor多线程:

任务再细分上面代码是多Reactor单线程IO读写,业务操作都是在Reactor线程中完成的。而多Reactor多线程是要将业务操作从(从Reactor)中分离,当一个客户端的工作量比较大需要花费较长的运行时间时,会影响到它对应的worker,我们此时需要创建一个独立的组处理。

public class HelloNetty {public static void main(String[] args) throws InterruptedException {//创建一个新的EventLoopGroup去专门处理耗时较长的操作,而不是让NioEventLoopGroup去执行耗时较长的任务EventLoopGroup d = new DefaultEventLoopGroup(3);NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup(2);ChannelFuture cf = new ServerBootstrap().group(boss, worker).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)//设置线程队列等待连接的个数.childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动连接状态.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast("handler1",new ChannelInboundHandlerAdapter(){@Override//这个方法就是上面的Nio事件循环的handler,public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf= (ByteBuf) msg;System.out.println("Nio"+buf.toString(StandardCharsets.UTF_8));//把消息传给下一个Handlerctx.fireChannelRead(msg);}}).addLast(d,"def",new ChannelInboundHandlerAdapter(){@Override//这个方法是默认事件循环需要做的操作public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf= (ByteBuf) msg;System.out.println("default"+buf.toString(StandardCharsets.UTF_8));}});}}).bind(8080).sync();cf.channel().closeFuture().sync();}
}

总结一波:也就是我创建了个Boss和两个Worker(即有一个WorkerGroup里面有两个Worker);Boss专门监听客户端连接,Worker专管客户端的读写事件;现在有个客户端发消息来了,我不仅要取到消息,还要处理对应消息的业务逻辑,业务逻辑处理的事件很长,如果按以前的只添加一个Handler,取消息和业务逻辑全部甩在这里面,那么这个绑定的worker就会去处理这个业务逻辑处理很久,后面还有客户也发了消息到这个worker,也就得不到及时的处理。此时我们用到的多reactor多线程就是我这个worker只取到消息,取到数据后脏话累活又甩出去了留给default那个事件循环组内的线程执行,这样worker处理可读channel的效率就提高了。如下图

多线程多Reactor图解

多个Handle如何切换的?

如果有多个handler,其中一个必须切换到下一个handler否则这个调用链就会断掉,需采用特定的方法去切换。

那么上面的多个不同的handler(即NioEventLoop->DefaultEventLoop)是如何切换的呢?

首先查看抽象类,ChannelInboundHandlerAdapter的channelRead方法(我们已经重写了该方法),看上面的代码我们也写上了该fireChannelRead(msg)方法。

往ChannelHandlerContext ctx继续走来到ChannelHandlerContext接口的fireChannelRead方法,实际,该接口的继承类为AbstractChannelHandlerContext,所以主要的代码在该类中,如下

//fireChannelRead方法的根源地,也就是在AbstractChannelHandlerContext抽象类中
public ChannelHandlerContext fireChannelRead(Object msg) {invokeChannelRead(this.findContextInbound(32), msg);//调用下面的方法return this;}static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);//返回下一个 handler的事件循环(EventLoop),因为EventLoop继承了EventExecutor EventExecutor executor = next.executor();//判断下一个EventLoop是否与当前的EventLoop是同一个线程//如果是的话,直接就可以调用了if (executor.inEventLoop()) {//这个方法里面会执行我们override的channelRead()方法next.invokeChannelRead(m);} //否则,将要执行的代码作为任务提交给下一个EventLoop处理(换人)else {//下一个handler提交任务到它的线程池executor.execute(new Runnable() {public void run() {next.invokeChannelRead(m);}});}}

除了fireChannelRead方法,super.channelRead(ctx, msg);方法也可以切换,因为该方法里面调用的就是fireChannelRead。

小伙伴们可以仔细品味这其中的源码,有一个更好的理解。

关闭:

group.shutdownGracefully();

用于关闭事件循环组,不是立即关闭,而是等到里面的数据全部处理完再关闭,期间不会接收新的任务。

受篇幅影响,还有几个组件为了方便我会写在另一篇博客:

Netty 组件 Channel 、Future 、Promise_清风拂来水波不兴的博客-CSDN博客

Netty入门 初识Netty helloword netty组件EventLoop源码分析相关推荐

  1. cl.zk0.info/index.php,兄弟连区块链入门到精通教程btcpool矿池源码分析环境搭建

    原标题:兄弟连区块链入门到精通教程btcpool矿池源码分析环境搭建 btcpool矿池-测试环境搭建及使用cgminer测试 本文档基于Ubuntu 16.04 LTS, 64 Bits. 安装Bi ...

  2. 《微信小程序-进阶篇》Lin-ui组件库源码分析-列表组件List(一)

    大家好,这是小程序系列的第二十篇文章,在这一个阶段,我们的目标是 由简单入手,逐渐的可以较为深入的了解组件化开发,从本文开始,将记录分享lin-ui的源码分析,期望通过对lin-ui源码的学习能加深组 ...

  3. 【Netty】对象重用的秘密:Recycler源码分析

    Netty作为一个高性能的网络IO框架,在代码层面做了大量的优化,为了减轻GC的压力,尽可能的使对象可以被重用,避免频繁的创建和销毁. ​ Recycler抽象类是Netty实现的,基于线程本地变量S ...

  4. vue实现消息badge 标记_Badge组件_element-ui源码分析笔记 - SegmentFault 思否

    Badge组件主要用于数字或状态的标记,对于消息类的提醒功能,使用这组件还是很常见的.具体显示效果如下图: 不管组件复杂还是简单,编码实现这个组件的都不是源码分析目的. 源码分析,在于通过一步步的实现 ...

  5. wpf popup 最前面_【第1988期】NutUI 组件 popup 源码分析

    前言 NutUI专题文来了.今日早读文章由京东用户体验设计部@杨凯旋投稿分享. 京东用户体验设计部-前端开发部现有前端开发人员 50 左右,主要为京东零售集团.京东健康提供 WEB 前端开发.APP ...

  6. Spring IOC容器和获取组件对象源码分析

    打上断点进行调试 1 第一步是进入了ClassPathXmlApplicationContext调用其构造参数,其中配置文件的内容被解析成了数组 public ClassPathXmlApplicat ...

  7. Ant Design源码分析(三):Wave组件

    Wave组件效果预览 在上一篇文章Button组件的源码分析中遇到了一个Wave组件, Wave组件在Ant design中提供了通用的表单控件点击效果,在自己阅读源码之前,也并没有过更多留心过在这些 ...

  8. netty源码分析系列——EventLoop

    2019独角兽企业重金招聘Python工程师标准>>> 前言 EventLoop也是netty作为一个事件驱动架构的网络框架的重要组成部分,netty主要通过它来实现异步编程,从前面 ...

  9. netty 5 alph1源码分析(服务端创建过程)

    研究了netty的服务端创建过程.至于netty的优势,可以参照网络其他文章.<Netty系列之Netty 服务端创建>是 李林锋撰写的netty源码分析的一篇好文,绝对是技术干货.但抛开 ...

  10. netty源码分析系列——Channel

    2019独角兽企业重金招聘Python工程师标准>>> 前言 Channel是netty中作为核心的一个概念,我们从启动器(Bootstrap)中了解到最终启动器的两个关键操作con ...

最新文章

  1. vue router 入门笔记
  2. Dalvik解释器源码到VMP分析
  3. 【合唱】男女差八度的科学解释
  4. 光纤布拉格光栅matlab,matlab对各种光纤光栅的仿真
  5. 数据科学入门与实战:玩转pandas之二
  6. Spring2.5注解事务配置
  7. 带你了解强大的Cadence家族,你可能只用到了它1/10的工具
  8. 小水智能-智能楼宇智慧建筑3D可视化系统,实现对实时数据的整合处理
  9. js获取粘贴的html,JS读取粘贴板内容
  10. 物联网全景动态图谱2.0|PaaS物联网平台汇总
  11. 【ftp的安装和使用】
  12. uniapp h5在浏览器唤起app
  13. MySQL教程——MySQL注释:单行注释和多行注释
  14. 数学建模学习笔记(清风)——插值算法
  15. 5G工业物联网环境下多方认证性能评估
  16. 《高效能人士的7个习惯》——习惯三:要事第一 之 独立意志的重要性
  17. 基于JavaSDK调用FISCO BCOS 区块链
  18. python安装talib
  19. EXCEL下拉框多选
  20. 有没有计算机专业的单词本,计算机专业单词完整版.doc

热门文章

  1. 如何查询统一社会信用代码
  2. 炎炎夏日需要一个清凉的地 - 自制水冷系统(七-改 PCB修正)
  3. iPhone6国行即将开抢,香港现在什么状态?
  4. 《天空账本》团队计划
  5. python在线翻译代码_python实现在线翻译
  6. 网工内推 | 网安专场,CISP认证优先,带薪年假,六险一金
  7. 超级简单:一个超轻量级的测试平台
  8. 虚继承是什么意思_面试题干货在此
  9. 牛客挑战赛59 —— A 木桩
  10. 【MySQL学习笔记】第10章 使用多个表(四 自连接)