Ntty组件-EventLoop

一、EventLoop - 事件循环对象

EventLoop

EventLoop 本质是一个单线程,维护了一个selector,处理多个channel上的各种IO事件。

继承关系:

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

EventLoopGroup

EventLoopGroup是一组EventLoop,Channel会调用EventLoopGroup的 ChannelFuture register(Channel channel);方法,同EventLoopGroup中的某个EventLoop进行绑定,这样channel中的全部io时间都由这个EventLoop进行处理,保证线程安全。

继承关系:

  • 继承 Iterable<EventExecutor> 提供了对EventLoop的迭代方法
  • 提供 EventExecutor next();获取下一个EventLoop

、EventLoop的简单使用

package com.test.netty.c2;

import io.netty.channel.DefaultEventLoop;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.NettyRuntime;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class TestEventLoop {

    public static void main(String[] args) {
        //1、创建时间循环组
        EventLoopGroup group = new NioEventLoopGroup(2);//io事件、普通任务、定时任务
        //EventLoopGroup group = new DefaultEventLoop();//普通任务、定时任务
        //2、获取下一个事件循环对象  负载均衡
        System.out.println(group.next());
        System.out.println(group.next());
        System.out.println(group.next());
        //3、执行普通任务,异步处理\代码执行权
        group.next().submit(()->{
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
            log.debug("ok");
        });
        //4、定时任务
        group.next().scheduleAtFixedRate(()->{
            log.debug("定时任务");
        }, 0,3, TimeUnit.SECONDS);
        log.debug("main");

    }
}

关闭EventLoopGroup使用 group.shutdownGracefully(); 方法,这个方法就是非强制性关闭,先拒绝其他channel的注册,等待io事件全部执行完成,最后关闭。

二、实例

服务器端:

package com.test.netty.c2;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
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) {
        //细分第二部:创建一个独立的eventLoop,处理耗时长的任务
        EventLoopGroup group = new DefaultEventLoop();
        new ServerBootstrap()
                //职责划分更细  boss、worker
                //第一个参数 boss 只负责  ServerSocketChannel accept之间
                //第二个参数 worker socketChannel 负责读写事件
                .group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast("handler-1", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf byteBuf = (ByteBuf) msg;
                                log.debug(byteBuf.toString(Charset.defaultCharset()));
                                //将消息传递给下一个handler
                                ctx.fireChannelRead(msg);
                            }
                        }).addLast(group, "handler-2", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf byteBuf = (ByteBuf) msg;
                                log.debug(byteBuf.toString(Charset.defaultCharset()));
                            }
                        });;
                    }
                }).bind(8080);
    }
}

客户端:

package com.test.netty.c2;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
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 java.net.InetSocketAddress;

public class EventLoopClient {
    public static void main(String[] args) throws Exception {
        //1、启动类
        Channel channel = new Bootstrap()
                //2、添加EventLoop
                .group(new NioEventLoopGroup())
                //3、选择客户端channel实现
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        //建立连接后调用
                        channel.pipeline().addLast(new StringEncoder());
                    }
                })
                //5、连接到服务器
                .connect(new InetSocketAddress("127.0.0.1", 8080))
                .sync()
                .channel();
        System.in.read();
    }
}

EventLoop分工

在ServerBootstrap 的 group在初始化的时候,可以传递两个EvenLoopGroup的实现类如下:

.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))

这种情况下,channel的事件就会区分到两个EventLoopGroup进行处理,如同之前的boss线程和worker线程:

  • 第一个参数 boss 只负责  ServerSocketChannel accept之间
  • 第二个参数 worker socketChannel 负责读写事件

注册的时候,可以执行线程个数,因为ServerSocketChannel中有且只有一个线程会监听accept,所以不用指定线程个数。同时可以知道,一个线程如果同channel绑定,那么之后这个channel所有的io事件都由这个线程负责。

如果出现了一个耗时比较长的操作,可能导致这个EventLoop上的其他channel都会阻塞,可以为耗时的handler指定一个EventLoop,使用DefaultEventLoop:

 这里可以看到,handler-2输出的数据是另一个线程:

 

 切换的实现

下面是切换EventLoopGroup,切换的实现原理如下:

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    // 获得下一个EventLoop, excutor 即为 EventLoopGroup
    EventExecutor executor = next.executor();
    
    // 如果下一个EventLoop 在当前的 EventLoopGroup中
    if (executor.inEventLoop()) {
        // 使用当前 EventLoopGroup 中的 EventLoop 来处理任务
        next.invokeChannelRead(m);
    } else {
        // 否则让另一个 EventLoopGroup 中的 EventLoop 来创建任务并执行
        executor.execute(new Runnable() {
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

 当两个handler绑定的是同一个EventLoopGroup,那么直接调用,否则把调用的代码交给下一个handler的EventLoopGroup来调用。

猜你喜欢

转载自blog.csdn.net/liming0025/article/details/120009669