Netty Learning (3)--EventLoop

1、EventLoop

EventLoop is essentially a single-threaded executor (while maintaining a Selector), which has a run method to handle the continuous IO events on the Channel.

package io.netty.channel;
import io.netty.util.concurrent.OrderedEventExecutor;
/**
 * 注册后将处理 Channel 的所有 I/O 操作。
 * 
 * 一个 EventLoop 实例通常会处理多个 Channel,但这可能取决于实现细节和内部结构。
 */
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
    
    
    @Override
    EventLoopGroup parent();
}

Its inheritance relationship is more complicated:

  • One is inherited from j.u.c.ScheduleExecutorServerand therefore contains all methods in the thread pool
  • One is inherited from Netty's ownOrderedEventExecutor
    • Provides boolean inEventLoop(Thread thread);a method to determine whether a thread belongs to this EventLoop
    • Provides EventLoopGroup parent();a method to see which EventLoop you belong to
package io.netty.channel;
import io.netty.util.concurrent.EventExecutorGroup;
/**
 * 特殊的 EventExecutorGroup 允许注册在事件循环期间处理以供以后选择的 Channel。
 */
public interface EventLoopGroup extends EventExecutorGroup {
    
    
    /**
     * 返回下一个要使用的 EventLoop
     */
    @Override
    EventLoop next();
    
    /**
	 * 用这个 EventLoop 注册一个 Channel。 
	 * 注册完成后,返回的 ChannelFuture 将收到通知。
     */
    ChannelFuture register(Channel channel);
    
    /**
	 * 使用 thisEventLoop 注册一个 Channel。 注册完成后,传递的 ChannelFuture 将收到通知,并且也会返回。
     */
    ChannelFuture register(Channel channel, ChannelPromise promise);
}

EventLoopGroup is a group of EventLoops. Channel generally calls the EventLoopGroup registermethod to bind one of the EventLoops, and subsequent IO events on this Channel are handled by this EventLoop (to ensure thread safety during IO event processing)

  • Inherit Netty's own EventExecutorGroup
    • Implemented iteratorthe interface to provide the ability to traverse EventLoop
    • Implemented nextthe method to get the next EventLoop in the collection

Code understanding implementation:

public class TestEventLoop {
    
    
    public static void main(String[] args) {
    
    
        // 1、创建时间循环组
        EventLoopGroup group = new NioEventLoopGroup(2);  // 处理IO事件、提交普通、定时任务
        /** new NioEventLoopGroup() 默认构造的线程数
         *  Runtime.getRuntime().availableProcessors() 电脑 CPU 的核心数
         *     static {
         *         DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
         *                 "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
         *         if (logger.isDebugEnabled()) {
         *             logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
         *         }
         *     }
         */
        // 2、获取下一个事件循环对象
        System.out.println(group.next());   // io.netty.channel.nio.NioEventLoop@6f2b958e
        System.out.println(group.next());   // io.netty.channel.nio.NioEventLoop@1eb44e46
        System.out.println(group.next());   // io.netty.channel.nio.NioEventLoop@6f2b958e (循环回第一个)

        // 3、执行普通任务
        // group.next().submit
        group.next().execute(()->{
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("[" + Thread.currentThread().getName() + "] ok");
        });
        System.out.println("[" + Thread.currentThread().getName() + "] main");

        // 4、执行定时任务
        // 固定频率执行任务 参数1: 任务对象,参数2:初始延时事件,参数3:间隔时间,参数4:时间单位
        group.next().scheduleAtFixedRate(()->{
    
    
            System.out.println("[" + Thread.currentThread().getName() + "] scheduleAtFixedRate");
        }, 0, 1, TimeUnit.SECONDS);
    }
}
1) EventLoop - IO task

Server code:

public class EventLoopServer {
    
    
    public static void main(String[] args) {
    
    
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
    
    
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
    
    
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
    
    
                            @Override   // msg ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println("[" + Thread.currentThread().getName() +"] "+ buf.toString(Charset.defaultCharset()));
                            }
                        });
                    }
                })
                .bind(8888);
    }
}

Client code:

public class EventLoopClient {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 1、客户端启动器
        Channel channel = new Bootstrap()
                // 2、添加 EventLoop
                .group(new NioEventLoopGroup())
                // 3、选择客户端 Channel 事件
                .channel(NioSocketChannel.class)
                // 4、添加处理器,
                .handler(new ChannelInitializer<NioSocketChannel>() {
    
    
                    @Override   // 连接建立后被调用
                    protected void initChannel(NioSocketChannel ch) throws Exception {
    
    
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 5、连接到服务器
                .connect(new InetSocketAddress("localhost", 8888))
                .sync()
                .channel();
        System.out.println(channel);
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
    
    
            String msg = scanner.nextLine();
            channel.writeAndFlush(msg);
        }
    }
}

run test

insert image description here

It can be seen that two workers (EventLoop) process the Channel in turn, but the worker (EventLoop) is bound to the Channel

insert image description here

Once the connection is established, the Channel will be bound to a NioEventLoopGroup, and subsequent requests will be processed by the same EventLoop.

2) EventLoop - division of labor and refinement

Refinement of labor 1: As in the previous example, there are two roles: boss (only responsible for accept events on ServerSocketChannel) and worker (only responsible for read and write events on SocketChannel)

        new ServerBootstrap()
                // 参数1: boss(只负责 ServerSocketChannel 上 accept 事件) 参数2: worker(只负责 SocketChannel 上的读写事件)
                // 只会占用 参数1(boss)里面的一个线程, worker 初始化两个线程(只有两个worker)一个 worker 可以管理多个 Channel
                .group(new NioEventLoopGroup(), new NioEventLoopGroup(2))

Division of labor and refinement 2: Handle time-consuming handlers separately

        // 细化2:创建一个独立的 EventLoopGroup
        EventLoopGroup group = new DefaultEventLoopGroup();
		...
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
    
    
            ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter(){
    
    
                @Override   // msg ByteBuf
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
                    ByteBuf buf = (ByteBuf) msg;
                    System.out.println("[" + Thread.currentThread().getName() +"] "+ buf.toString(Charset.defaultCharset()));
                    ctx.fireChannelRead(msg);   // 将消息传递给下一个 handler
                }
                // 使用 group 
            }).addLast(group, "handler2", new ChannelInboundHandlerAdapter(){
    
    
                @Override   // msg ByteBuf
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
                    ByteBuf buf = (ByteBuf) msg;
                    System.out.println("[" + Thread.currentThread().getName() +"] "+ buf.toString(Charset.defaultCharset()));
                }
            });
        ...

insert image description here

insert image description here

How to replace the handler during execution?

key codeio.netty.channel.AbstractChannelHandlerContext#invokeChannelRead()

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    // 下一个 handler 的事件循环是否与当前的事件循环是同一个线程(EventLoop)
    EventExecutor executor = next.executor();	// 返回下一个 handler 的 EventLoop
    // 是,直接使用
    if (executor.inEventLoop()) {
    
    	// 当前 handler 中的线程,是否和 EventLoop 是同一个线程(EventLoop)
        next.invokeChannelRead(m);
    } 
    // 不是,将要执行的代码作为一个任务提交给下一个事件循环处理(换人)
    else {
    
    
        executor.execute(new Runnable() {
    
    	/// executor 下一个 handler 线程(EventLoop)
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

Summary: If the two handlers are bound to the same thread (EventLoop), then call them directly; otherwise, encapsulate the code to be called into a task object, which will be called by the next handler thread (EventLoop).

Guess you like

Origin blog.csdn.net/weixin_43989102/article/details/126736290