netty-02-EventLoop和channel相关

本文深入解析Netty中EventLoop的工作原理,包括EventLoop、EventLoopGroup的角色与职责,以及它们如何与Channel协同工作实现高效的I/O处理。通过具体示例展示了不同场景下EventLoop的绑定关系。

1-组件类比理解

  • channel 理解为数据的通道

  • msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf

  • handler 理解为数据的处理工序

- 工序有多道,合在一起就是 pipeline,pipeline 负责发布事件(读、读取完成...)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)

- handler 分 Inbound 和 Outbound 两类

  • 把 eventLoop 理解为处理数据的工人

- 工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)

- 工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务

- 工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人

2-常用的EventLoop

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

它的继承关系比较复杂

- 一条线是继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法

- 另一条线是继承自 netty 自己的 OrderedEventExecutor,

- 提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop

- 提供了 parent 方法来看看自己属于哪个 EventLoopGroup

EventLoopGroup group1 = new NioEventLoopGroup();不传递参数,线程数就当前计算cpu 核数 *2

super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);

private static final int DEFAULT_EVENT_LOOP_THREADS;

static {

DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(

"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

}

事件循环组

EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,

后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)

- 继承自 netty 自己的 EventExecutorGroup

- 实现了 Iterable 接口提供遍历 EventLoop 的能力

- 另有 next 方法获取集合中下一个 EventLoop

import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;

@Slf4j
public class EventLoopTest {
    public static void main(String[] args) {
        //DefaultEventLoopGroup:执行普通任务和定时任务;NioEventLoopGroup:执行普通任务和定时任务+IO事件
        EventLoopGroup group = new DefaultEventLoopGroup();
        EventLoopGroup group1 = new NioEventLoopGroup();
        for (EventExecutor eventLoop : group1) {
            System.out.println(eventLoop);//当前服务器4核,所以输出8次
        }

        // 执行普通任务
        group.next().execute(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("ok");
        });

        // 执行定时任务
        group.next().scheduleAtFixedRate(() -> {
            log.debug("ok");
        }, 0, 1, TimeUnit.SECONDS);

        log.debug("main");
    }


}

3-调试EventLoopGroup和channel绑定关系

3.1-单个channel多次发送消息

结论1:单个客户端channel发送消息,服务端始终用同一个EventLoopGroup来处理;

EventLoopServer.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class EventLoopServer {
    public static void main(String[] args) {
        //创建一个独立的普通DefaultEventLoopGroup 类型 EventLoopGroup,可以专门处理耗时操作,避免NioEventLoopGroup处理耗时操作,影响系统性能
        EventLoopGroup group = new DefaultEventLoopGroup();
        //bossGroup 只负责 ServerSocketChannel 上 accept 事件,一般设置为1
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //workGroup 只负责 socketChannel 上的读写,这里为了演示效果设置为2,实际项目根据自己的服务器状况设置
        NioEventLoopGroup workGroup = new NioEventLoopGroup(2);
        new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug(buf.toString(Charset.defaultCharset()));
                            }
                        });
               
                    }
                })
                .bind(8080);
    }
}

EventLoopClient.java

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;

@Slf4j
public class EventLoopClient {
    public static void main(String[] args) throws InterruptedException {
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override // 在连接建立后被调用
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect(new InetSocketAddress("localhost", 8080));


        channelFuture.sync(); // 阻塞住当前线程,直到nio线程连接建立完毕
        Channel channel = channelFuture.channel();
        log.debug("channel={}", channel);
        log.debug("################");
    }
}

3.2-多个channel发送消息

代码还是上面的代码,客户端启动多个,启动方式参见https://blog.csdn.net/ycmy2017/article/details/128777903 中的调试

结论:一个EventLoopGroup可以处理多个channel,并且如果EventLoopGroup处理了哪个channel,后续一直由当前的EventLoopGroup处理这个channel....

描述:client1发送了11,12,13;client2发送了21,22,23;client3发送了31;

现象:我们看到服务端,NioEventLoopGroup workGroup = new NioEventLoopGroup(2);NioEventLoopGroup参数传递2,服务端NioEventLoopGroup只有2个,客户端3个,其中client1交给nioEventLoopGroup-4-1处理了,client2交给nioEventLoopGroup-4-2处理了,client3又交给nioEventLoopGroup-4-1处理了...

3.3-多个channel和多个EventLoopGroup

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class EventLoopServer {
    public static void main(String[] args) {
        //创建一个独立的普通DefaultEventLoopGroup 类型 EventLoopGroup,可以专门处理耗时操作,避免NioEventLoopGroup处理耗时操作,影响系统性能
        EventLoopGroup group = new DefaultEventLoopGroup();
        //bossGroup 只负责 ServerSocketChannel 上 accept 事件,一般设置为1
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //workGroup 只负责 socketChannel 上的读写,这里为了演示效果设置为2,实际项目根据自己的服务器状况设置
        NioEventLoopGroup workGroup = new NioEventLoopGroup(2);
        new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug("handler1,msg={}",buf.toString(Charset.defaultCharset()));
                                ctx.fireChannelRead(msg); // 让消息传递给下一个handler
                            }
                        })
                        .addLast(group, "handler2", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                Thread.sleep(2000L);//这里模拟另外一个需要耗时很久,模拟2s
                                log.debug("handler2,msg={}",buf.toString(Charset.defaultCharset()));
                            }
                        });
                    }
                })
                .bind(8080);
    }
}

描述:client1发送了1001,1002,1003;client2发送了2001,2002;client3发送了3001,3002,3003;EventLoopGroup group = new DefaultEventLoopGroup();系统4核,所以这里有8个可以处理;因此defaultEventLoopGroup会有defaultEventLoopGroup-2-1,defaultEventLoopGroup-2-2,defaultEventLoopGroup-2-3

4-ChannelFuture

connect是个异步方法,所以 channelFuture.sync()阻塞;

   // 2. 带有 Future,Promise 的类型都是和异步方法配套使用,用来处理结果
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override // 在连接建立后被调用
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 1. 连接到服务器
                // 异步非阻塞, main线程 发起了调用,真正执行 connect 是 nio 线程
                .connect(new InetSocketAddress("localhost", 8080)); // 1s 秒后

        log.debug("channel1={}", channelFuture.channel());
        channelFuture.sync(); // 阻塞住当前线程,直到nio线程连接建立完毕
        Channel channel = channelFuture.channel();
        log.debug("channel2={}", channel);
        log.debug("################");

还可以采用回调的方式进行

  // 2. 带有 Future,Promise 的类型都是和异步方法配套使用,用来处理结果
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override // 在连接建立后被调用
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 1. 连接到服务器
                // 异步非阻塞, main线程 发起了调用,真正执行 connect 是 nio 线程
                .connect(new InetSocketAddress("localhost", 8080)); // 1s 秒后

        log.debug("channel1={}", channelFuture.channel());

//         2.2 使用 addListener(回调对象) 方法异步处理结果
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            // 在 nio 线程连接建立好之后,会调用 operationComplete
            public void operationComplete(ChannelFuture future) throws Exception {
                Channel channel = future.channel();
                log.debug("channel2={}", channel);
//                channel.writeAndFlush("hello, world");
            }
        });

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值