Netty(2)Netty 组件

Netty组件

    • 一. EventLoop & EventLoopGroup
        • 1. EventLoop
        • 2. EventLoopGroup
    • 二. Channel
        • 1. ChannelFuture
    • 三. Pipeline & Handler
        • 一. 入站处理器

Netty核心组件

为了后期更好地理解和进一步深入 Netty,有必要总体认识一下 Netty 所用到的核心组件以及他们在整个 Netty 架构中是如何协调工作的。Nettty 有如下几个核心组件:

  • Channel
  • EventLoop
  • Handler
  • Pipeline

一. EventLoop & EventLoopGroup

这里简单说一下 Reactor 线程模型:

Reactor:把IO事件分配给对应的handler处理
Acceptor:处理客户端连接事件
Handler:处理非阻塞的任务

  1. 单线程模型
    Netty(2)Netty 组件_第1张图片

        只有一个Reactor线程负责新的客户端连接,还要负责客户端的读写请求。 弊端很明显:如果Reactor线程负载过重,会无法及时响应其它客户的服务要求,导致客户端连接超时

> 应用案例:Redis
  1. 多线程模型
    Netty(2)Netty 组件_第2张图片
            也是只有一个Reactor线程,但是它只负责事件的监听和响应,另外有一个线程池创建多个NIO线程去处理IO。虽然多线程模型从一定程度上减少了单Reactor线程的压力,但是还是无法解决高并发下单Reactor线程的的性能瓶颈

  2. 主从多线程模型

    主从线程模型:一组线程池接收请求,一组线程池处理IO
    Netty(2)Netty 组件_第3张图片
    原理是将Reactor线程拆分了mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理

这种模型使各个模块职责单一,降低耦合度,性能和稳定性都有提高
应用案例:netty

1. EventLoop

EventLoop 字面理解就是事件循环,事件循环的意思就是:它运行在一个循环中,直到它停止

Netty 中使用 EventLoop 接口代表事件循环,EventLoop 是从EventExecutor 和ScheduledExecutorService 扩展而来,所以可以将任务(Runnable 或者 Callable)直接交给 EventLoop 执行

类关系图如下:
Netty(2)Netty 组件_第4张图片
NioEventLoop (注意是NioEventLoop)继承于 SingleThreadEventLoop, 而 SingleThreadEventLoop 又继承于 SingleThreadEventExecutor。SingleThreadEventExecutor 是 Netty 中对本地线程的抽象, 它内部有一个 Thread thread 属性, 存储了一个本地 Java 线程. 因此我们可以认为, 一个 NioEventLoop 其实和一个特定的线程绑定, 并且在其生命周期内, 绑定的线程都不会再改变

EventLoop 代表了 Reactor 模型中的 Handler,主要需要处理IO事件和其他两种任务,分别为定时任务和一般任务

一个EventLoop,可以注册很多不同的Netty Channel。相当于是一对多的关系

2. EventLoopGroup

EventLoopGroup是一组EventLoop的抽象,主要目的是为了减少上下文切换,和资源重复利用

它的作用是把Channel绑定到一个的EventLoop上去,因此我们需要获取到里面的某个EventLoop

EventExecutor也提供了inEventLoop方法用户判断当前代码执行是不是在绑定的线程,如果不是,我们就需要通过提交任务的方式提交,如果是,我们就可以直接执行,因此我们可以看到很多类似代码

类的继承关系:
Netty(2)Netty 组件_第5张图片

源码:

/**
     * Return the next {@link EventLoop} to use
     */
    @Override
    EventLoop next();

    /**
     * Register a {@link Channel} with this {@link EventLoop}. The returned {@link ChannelFuture}
     * will get notified once the registration was complete.
     */
    ChannelFuture register(Channel channel);

    /**
     * Register a {@link Channel} with this {@link EventLoop} using a {@link ChannelFuture}. The passed
     * {@link ChannelFuture} will get notified once the registration was complete and also will get returned.
     */
    ChannelFuture register(ChannelPromise promise);

    /**
     * Register a {@link Channel} with this {@link EventLoop}. The passed {@link ChannelFuture}
     * will get notified once the registration was complete and also will get returned.
     *
     * @deprecated Use {@link #register(ChannelPromise)} instead.
     */
    @Deprecated
    ChannelFuture register(Channel channel, ChannelPromise promise);

二. Channel

Channel是Netty中用来传输数据的通道,每个Channel代表了客户端的连接

核心API一览:

  • EventLoop eventLoop()
    返回该通道注册的事件轮询器。
  • Channel parent()
    返回该通道的父通道,如果是ServerSocketChannel实例则返回null,SocketChannel实例则返回对应的ServerSocketChannel。
  • ChannelConfig config()
    返回该通道的配置参数。
  • boolean isOpen()
    端口是否处于open,通道默认一创建isOpen方法就会返回true,close方法被调用后该方法返回false。
  • boolean isRegistered()
    是否已注册到EventLoop。
  • boolean isActive()
    通道是否处于激活。NioSocketChannel的实现是java.nio.channels.SocketChannel实例的isOpen()与isConnected()都返回true。NioServerSocketChannel的实现ServerSocketChannel.socket().isBound(),如果绑定到端口中,意味着处于激活状态。
  • ChannelFuture closeFuture()
    Future 模式的应用,调用该方法的目的并不是关闭通道,而是预先创建一个凭证(Future),等通道关闭时,会通知该 Future,用户可以通过该 Future 注册事件。
  • ChannelFuture bind(SocketAddress localAddress)
    Netty 服务端绑定到本地端口,开始监听客户端的连接请求。该过程会触发事件链(ChannelPipeline)。该部分将在后续讲解服务端启动流程时再详细分析。
  • ChannelFuture connect(SocketAddress remoteAddress)
    Netty客户端连接到服务端,该过程同样会触发一系列事件(ChannelPipeline)。该部分将在后续讲解客户端启动流程时再详细分析。
  • ChannelFuture disconnect()
    断开连接,但不会释放资源,该通道还可以再通过connect重新与服务器建立连接。
  • ChannelFuture close()
    关闭通道,回收资源,该通道的生命周期完全结束。
  • ChannelFuture deregister()
    取消注册。
  • Channel read()
    通道读,该方法并不是直接从读写缓存区读取文件,而是向NIO Selecor注册读事件(目前主要基于NIO)。当通道收到对端的数后,事件选择器会处理读事件,从而触发ChannelInboundHandler#channelRead 事件,然后继续触发ChannelInboundHandler#channelReadComplete(ChannelHandlerContext)事件。
  • ChannelFuture write(Object msg)
    向通道写字节流,会触发响应的写事件链,该方法只是会将字节流写入到通道缓存区,并不会调用flush方法写入通道中。
  • Channel flush()
    刷写所有挂起的消息(刷写到流中)。
  • ChannelFuture writeAndFlush(Object msg)
    相当于调用write与flush方法。

Netty中,真正帮助Channel完成IO读写操作的是它的内部类unsafe, 源码如下, 很多重要的功能在这个接口中定义, 下面列举的常用的方法

interface Unsafe {
//  把channel注册进EventLoop
void register(EventLoop eventLoop, ChannelPromise promise);
 
 // todo 给channel绑定一个 adress,
void bind(SocketAddress localAddress, ChannelPromise promise);

// 把channel注册进Selector
void deregister(ChannelPromise promise);

// 从channel中读取IO数据
void beginRead();

// 往channe写入数据
void write(Object msg, ChannelPromise promise);
...
...

1. ChannelFuture

Netty 里面的IO操作全部是异步的。这意味着,在调用结束时,无法保证IO操作已完成。但是它会返回给你一个ChannelFuture 实例,里面保存了IO操作的结果信息或状态

打个比方:

把客户端代码改成这样

 public static void main(String[] args) {
        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            // 创建客户端启动对象
            Channel channel = new Bootstrap()
                    .group(eventExecutors) // 设置线程组
                    .channel(NioSocketChannel.class) //设置 客户端通道的实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new StringEncoder()); // 解码器
                        }
                    })
                    .connect("localhost", 8000).channel();
            channel.writeAndFlush("加入了连接");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

目的是:连接之后给服务端发送一个消息,
代码本身看着没有任何问题,但是运行之后,服务端无法收到消息

原因:

是因为connect方法是异步的,原理是在connect方法中执行连接的线程不是main线程,而是前面创建的nio线程,导致main线程执行发送消息的代码时,还没有连接成功(即channel还没有初始化成功),这就是netty框架中异步的一种体现,可以理解为netty中的所有操作时异步的

解决办法:

  1. 让主线程延迟几秒
    这样确实可以,但是治标不治本

  2. sync()方法,阻塞到直到执行结果出来
    修改后代码如下:

        public static void main(String[] args) {
            EventLoopGroup eventExecutors = new NioEventLoopGroup();
            try {
                // 创建客户端启动对象
                ChannelFuture channelFuture = new Bootstrap()
                        .group(eventExecutors) // 设置线程组
                        .channel(NioSocketChannel.class) //设置 客户端通道的实现类
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
                                ch.pipeline().addLast(new StringEncoder()); // 解码器
                            }
                        })
                        .connect("localhost", 8000);
                channelFuture.sync(); // 同步阻塞 到 执行完成
                channelFuture.channel().writeAndFlush("加入了连接");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
  3. addListener() 添加执行成功后的回调
    修改后的代码:

        public static void main(String[] args) {
            EventLoopGroup eventExecutors = new NioEventLoopGroup();
            try {
                // 创建客户端启动对象
                ChannelFuture channelFuture = new Bootstrap()
                        .group(eventExecutors) // 设置线程组
                        .channel(NioSocketChannel.class) //设置 客户端通道的实现类
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
                                ch.pipeline().addLast(new StringEncoder()); // 解码器
                            }
                        })
                        .connect("localhost", 8000);
                channelFuture.addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) {
                        future.channel().writeAndFlush("加入了连接!");
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

三. Pipeline & Handler

Netty 使用 ChannelHandler 来处理 Channel 上的各种事件,分为入站、出站两种。所有 ChannelHandler 被连成一串,就是 Pipeline

  • 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果

  • 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工

一. 入站处理器

public static void main(String[] args) {
        // 创建线程组
        // bossGroup 处理链接请求
        // workerGroup 处理客户端业务逻辑
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            // 创建服务端启动对象
            new ServerBootstrap()
                    .group(bossGroup, workerGroup) // 设置两个线程组
                    .channel(NioServerSocketChannel.class) //设置tcpSocket通道 使用nio作为服务器通道
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        protected void initChannel(NioSocketChannel ch) {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                    System.out.println("第一个入站消息处理器");
                                    ctx.fireChannelRead(msg);
                                }
                            });
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                    System.out.println("第二个入站消息处理器");
                                    ctx.channel().writeAndFlush(msg);
                                }
                            });
                            ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg,
                                                  ChannelPromise promise) {
                                    System.out.println("第一个出站消息处理器");
                                    ctx.write(msg, promise);
                                }
                            });
                            ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg,
                                                  ChannelPromise promise) {
                                    System.out.println("第二个出站消息处理器");
                                    ctx.write(msg, promise);
                                }
                            });
                        }
                    }).bind(8000).sync();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

多个 入站处理器的处理顺序是根据 addLast()的顺序来的
多个 出站处理器的处理顺序是根据 addLast()的倒序来的

你可能感兴趣的:(netty,java)