Netty初识Hello World 事件循环对象(EventLoop) 事件循环组 (EventLoopGroup)

本文主要是介绍Netty初识Hello World 事件循环对象(EventLoop) 事件循环组 (EventLoopGroup),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

初始Netty-HelloWorld

Netty在网络通信中的地位就如同Spring框架在JavaEE开发中的地位。
基于Netty网络通信开发简易的服务端、客户端,以实现客户端向服务端发送hello world,服务端仅接收不返回数据。
服务端代码:

@Slf4j
public class HelloServer {public static void main(String[] args) {new ServerBootstrap() // 服务器端的启动器,负责组装netty组件,启动服务器// Group组,BossEventLoop,WorkerEventLoop(selector, thread).group(new NioEventLoopGroup()) // 1.channel(NioServerSocketChannel.class) // 2// boss 负责处理连接,worker(child)负责处理读写,决定了worker(child)能执行哪些操作(handler).childHandler(// channel代表和客户端进行数据读写的通道 Initializer初始化,负责添加别的handlernew ChannelInitializer<NioSocketChannel>() { // 3protected void initChannel(NioSocketChannel ch) {// 添加具体的handler, StringDecoder字符串解码,将传输的ByteBuf转换为字符串ch.pipeline().addLast(new StringDecoder()); // 5ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { // 6,自定义handler@Override	// 读事件protected void channelRead0(ChannelHandlerContext ctx, String msg) {// 打印上一步转换的字符串System.out.println(msg);}});}}).bind(8080); // 4,绑定监听端口}
}

客户端代码:

public class HelloClient {public static void main(String[] args) throws InterruptedException {new Bootstrap() // 创建启动器// 添加EventLoop.group(new NioEventLoopGroup()) // 1// 选择客户端的channel.channel(NioSocketChannel.class) // 2// 添加处理器.handler(new ChannelInitializer<Channel>() { // 3@Override // 在连接建立后被调用protected void initChannel(Channel ch) {// 对发送的数据进行编码ch.pipeline().addLast(new StringEncoder()); // 8}})// 连接到服务器.connect("127.0.0.1", 8080) // 4//.sync() // 5.channel() // 6.writeAndFlush(new Date() + ": hello world!"); // 7 向服务器发送数据}
}

主要执行流程:
图片
可以理解如下:

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

EventLoop详解

EventLoop基础知识

事件循环对象

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

它的继承关系比较复杂

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

事件循环组

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

  • 继承自 netty 自己的 EventExecutorGroup
    • 实现了 Iterable 接口提供遍历 EventLoop 的能力
    • 另有 next 方法获取集合中下一个 EventLoop

EventLoop使用示例

// 创建事件循环组
// NioEventLoopGroup可以处理io事件、普通任务、定时任务
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
// DefaultEventLoopGroup可以处理普通任务、定时任务   
EventLoopGroup eventLoopGroup = new DefaultEventLoopGroup();

创建事件循环组时,若不指定线程个数,则创建个数为机器可用CPU * 2,若指定个数,则按照指定个数进行创建。
在这里插入图片描述

        // 处理普通任务eventLoopGroup.next().submit(()->{ // 或者使用.execute()try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}log.info("ok");});// 执行定时任务eventLoopGroup.next().scheduleAtFixedRate(()->{log.info("test schedule task ...");}, 0, 1, TimeUnit.SECONDS);

使用NioEventLoopGroup处理IO事件

采用多个NioEventLoopGroup来分别处理不同的事件,如boss专门处理accept事件,worker专门处理read事件。并且对于耗时较长的handler交给专门的EventLoopGroup来处理,从而不阻塞原有的NioEventLoopGroup监听事件的正常运行。
在这里插入图片描述
优化一:

上述图片中第23行为初始代码,一个NioEventLoopGroup处理所有的事件,包括accept、read
等,显然不符合各司其职的功能,将其优化为第24行所示,明确NioEventLoopGroup负责处理的
事件类别。
1.boss只负责NioServerSocketChannel上的accept事件。
2.worker负责NioSocketChannel上的read事件。

在这里插入图片描述
优化二:

对于比较耗时的handler,可以将其将给其他EventLoopGroup创建handler来执行。

服务端代码:

@Slf4j
public class EventLoopServer {public static void main(String[] args) {// 2.细分:如果某个handler执行事件比较长,可以独立出一个eventloopgroup进行处理EventLoopGroup group = new DefaultEventLoopGroup();new ServerBootstrap()// 1.进行优化,明确eventloop负责处理的事件类别,boss和worker// boss只负责NioServerSocketChannel上的accept事件,worker负责NioSocketChannel上的read事件// 是否需要指定第一个负责accept的NioEventLoopGroup的适量,不需要,只会有一个NioServerSocketChannel
//                .group(new NioEventLoopGroup()).group(new NioEventLoopGroup(), new NioEventLoopGroup(2)).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;// 转为toString的时候需要指定字符集,此处选择使用默认字符集// 若在网络通信中,需要客户端和服务器协商字符集的使用,使用同一个标准进行处理log.info(buf.toString(Charset.defaultCharset()));ctx.fireChannelRead(msg);   // 将消息传递给下一个handler}}).addLast(group, "handler2", new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;log.info(buf.toString(Charset.defaultCharset()));}});}}).bind(8080);}
}

客户端代码:

@Slf4j
public class EventLoopClient {public static void main(String[] args) throws InterruptedException {Channel channel = new Bootstrap() // 创建启动器// 添加EventLoop.group(new NioEventLoopGroup()) // 1// 选择客户端的channel.channel(NioSocketChannel.class) // 2// 添加处理器.handler(new ChannelInitializer<Channel>() { // 3@Override // 在连接建立后被调用protected void initChannel(Channel ch) {// 对发送的数据进行编码ch.pipeline().addLast(new StringEncoder()); // 8}})// 连接到服务器.connect("127.0.0.1", 8080) // 4//.sync() // 5.channel();// 6
//        for (int i = 0; i < 5; i++) {
//            channel.writeAndFlush("Hello eventloop_" + i + "   ");
//        }log.info("{}",channel);System.out.println("");}
}

EventLoop、Channel和Handler之间的关系

建立连接之后,channel和一个EventLoop进行绑定,并且一个线程可以管理多个Channel。
图片

同一个Channel绑定多个不同的EventLoop(也就不同EventLoopGroup中对应的EventLoop),此时如何进行不同handler之间的切换。

图片
  • 如果两个 handler 绑定的是同一个线程,那么就直接调用
  • 否则,把要调用的代码封装为一个任务对象,由下一个 handler 的线程来调用
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);// 下一个 handler 的事件循环是否与当前的事件循环是同一个线程EventExecutor executor = next.executor();// 是,直接调用if (executor.inEventLoop()) {next.invokeChannelRead(m);} // 不是,将要执行的代码作为任务提交给下一个事件循环处理(换人)else {executor.execute(new Runnable() {@Overridepublic void run() {next.invokeChannelRead(m);}});}}

这篇关于Netty初识Hello World 事件循环对象(EventLoop) 事件循环组 (EventLoopGroup)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1004023

相关文章

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

Python循环缓冲区的应用详解

《Python循环缓冲区的应用详解》循环缓冲区是一个线性缓冲区,逻辑上被视为一个循环的结构,本文主要为大家介绍了Python中循环缓冲区的相关应用,有兴趣的小伙伴可以了解一下... 目录什么是循环缓冲区循环缓冲区的结构python中的循环缓冲区实现运行循环缓冲区循环缓冲区的优势应用案例Python中的实现库

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

Java实现将byte[]转换为File对象

《Java实现将byte[]转换为File对象》这篇文章将通过一个简单的例子为大家演示Java如何实现byte[]转换为File对象,并将其上传到外部服务器,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言1. 问题背景2. 环境准备3. 实现步骤3.1 从 URL 获取图片字节数据3.2 将字节数组

Javascript访问Promise对象返回值的操作方法

《Javascript访问Promise对象返回值的操作方法》这篇文章介绍了如何在JavaScript中使用Promise对象来处理异步操作,通过使用fetch()方法和Promise对象,我们可以从... 目录在Javascript中,什么是Promise1- then() 链式操作2- 在之后的代码中使

MyBatis的配置对象Configuration作用及说明

《MyBatis的配置对象Configuration作用及说明》MyBatis的Configuration对象是MyBatis的核心配置对象,它包含了MyBatis运行时所需的几乎所有配置信息,这个对... 目录MyBATis配置对象Configuration作用Configuration 对象的主要作用C

Java嵌套for循环优化方案分享

《Java嵌套for循环优化方案分享》介绍了Java中嵌套for循环的优化方法,包括减少循环次数、合并循环、使用更高效的数据结构、并行处理、预处理和缓存、算法优化、尽量减少对象创建以及本地变量优化,通... 目录Java 嵌套 for 循环优化方案1. 减少循环次数2. 合并循环3. 使用更高效的数据结构4