Netty核心组件总览

本文来说下Netty 核心组件


核心组件概述

下面枚举所有的 Netty 应用程序的基本构建模块(核心组件),包括客户端和服务器:

  • Bytebuf(字节容器)
  • Bootstrap 和 ServerBootstrap (启动引导类)
  • Channel(网络操作抽象类)
  • EventLoop (事件循环)
  • ChannelHandler (消息处理器)和 ChannelPipeline (ChannelHandler 对象链表)
  • ChannelFuture(操作执行结果)

通过下面这张图你可以将我提到的这些 Netty 核心组件串联起来:

在这里插入图片描述


Bytebuf字节容器

网络通信最终都是通过字节流进行传输的。 Netty 使用自建的 buffer API,而不是使用 NIO 的 ByteBuffer 来存储连续的字节序列。与 ByteBuffer 相比这种方式拥有明显的优势。Netty 使用新的 buffer 类型 ByteBuf,被设计为一个可从底层解决 ByteBuffer 问题,并可满足日常网络应用开发需要的缓冲类型。这些很酷的特性包括:

  • 如果需要,允许使用自定义的缓冲类型。
  • 复合缓冲类型中内置的透明的零拷贝实现。
  • 开箱即用的动态缓冲类型,具有像 StringBuffer 一样的动态缓冲能力。
  • 不再需要调用的 flip() 方法。
  • 正常情况下具有比 ByteBuffer 更快的响应速度。

Bootstrap和ServerBootstrap(启动引导类)

Bootstrap 是客户端的启动引导类/辅助类,不管程序使用哪种协议,无论是创建一个客户端还是服务器都需要使用“引导”。具体使用方法如下:

 EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动引导/辅助类:Bootstrap
            Bootstrap b = new Bootstrap();
            //指定线程模型
            b.group(group).
                    ......
            // 尝试建立连接
            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } finally {
            // 优雅关闭相关线程组资源
            group.shutdownGracefully();
        }

ServerBootstrap 客户端的启动引导类/辅助类,具体使用方法如下:

    // 1.bossGroup 用于接收连接,workerGroup 用于具体的处理
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // 2.创建服务端启动引导/辅助类:ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            // 3.给引导类配置两大线程组,确定了线程模型
            b.group(bossGroup, workerGroup).
                   ......
            // 6.绑定端口
            ChannelFuture f = b.bind(port).sync();
            // 等待连接关闭
            f.channel().closeFuture().sync();
        } finally {
            // 7.优雅关闭相关线程组资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

从上面的示例中,我们可以看出:

在这里插入图片描述

  • Bootstrap 通常使用 connet() 方法连接到远程的主机和端口,作为一个 Netty TCP 协议通信中的客户端。另外,Bootstrap 也可以通过 bind() 方法绑定本地的一个端口,作为 UDP 协议通信中的一端。
  • ServerBootstrap通常使用 bind() 方法绑定本地的端口上,然后等待客户端的连接。
  • Bootstrap 只需要配置一个线程组 EventLoopGroup , 而 ServerBootstrap 需要配置两个线程组— EventLoopGroup ,一个用于接收连接,一个用于具体的 IO 处理。

Channel(网络操作抽象类)

在我们使用某种语言,如 c/c++,java,go 等,进行网络编程的时候,我们通常会使用到 Socket, Socket 是对底层操作系统网络 IO 操作(如 read,write,bind,connect等)的封装, 因此我们必须去学习 Socket 才能完成网络编程,而 Socket 的操作其实是比较复杂的,想要使用好它有一定难度, 所以 Netty 提供了Channel(注意是 io.netty.Channel,而非 Java NIO 的 Channel),更加方便我们处理 IO 事件。

Channel 接口是 Netty 对网络操作抽象类。通过 Channel 我们可以进行 I/O 操作。Channel 为用户提供:

  • 当前网络连接的通道的状态(例如是否打开?是否已连接?)
  • 网络连接的配置参数 (例如接收缓冲区大小)
  • 提供异步的网络 I/O 操作 (如建立连接,读写,绑定端口),异步调用意味着任何 I/O调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。调用后立即返回一个 ChannelFuture 实例,通过注册监听器到ChannelFuture 上,可以在 I/O操作成功、失败或取消时回调通知调用方。
  • 支持关联 I/O 操作与对应的处理程序

一旦客户端成功连接服务端,就会新建一个 Channel 同该用户端进行绑定,示例代码如下:

// 通过 Bootstrap 的 connect 方法连接到服务端
   public Channel doConnect(InetSocketAddress inetSocketAddress) {
        CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
        bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                completableFuture.complete(future.channel());
            } else {
                throw new IllegalStateException();
            }
        });
        return completableFuture.get();
    }

比较常用的Channel接口实现类是 :

  • NioServerSocketChannel(服务端)
  • NioSocketChannel(客户端)

这两个 Channel 可以和 BIO 编程模型中的 ServerSocket以及Socket两个概念对应上。


EventLoop(事件循环)

① EventLoop 概述

EventLoop(事件循环)接口可以说是 Netty 中最核心的概念了!EventLoop 定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。

是不是很难理解?说白了,EventLoop 的主要作用实际就是责监听网络事件并调用事件处理器进行相关 I/O 操作(读写)的处理。

② Channel 和 EventLoop 的关系

那 Channel 和 EventLoop 直接有啥联系呢?

Channel 为 Netty 网络操作(读写等操作)抽象类,EventLoop 负责处理注册到其上的Channel 的 I/O 操作,两者配合进行 I/O 操作。

③ EventloopGroup 和 EventLoop 的关系

EventLoopGroup 包含多个 EventLoop(每一个 EventLoop 通常内部包含一个线程),它管理着所有的 EventLoop 的生命周期。

并且,EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理,即 Thread 和 EventLoop 属于 1 : 1 的关系,从而保证线程安全。

下图是 Netty NIO 模型对应的 EventLoop 模型。通过这个图应该可以将 EventloopGroup、EventLoop、 Channel 三者联系起来:

在这里插入图片描述


ChannelHandler(消息处理器)和 ChannelPipeline(ChannelHandler 对象链表)

ChannelHandler

我们知道 Netty 是一个款基于事件驱动的网络框架,当特定事件触发时,我们能够按照自定义的逻辑去处理数据。 ChannelHandler 则正是用于处理入站(接收)和出站(发送)数据钩子,它可以处理几乎所有类型的动作,所以 ChannelHandler 会是 我们开发者更为关注的一个接口。

通俗来说,ChannelHandler 是消息的具体处理器,主要负责处理客户端/服务端接收和发送的数据

ChannelHandler 主要分为处理入站数据的 ChannelInboundHandler 和出站数据的 ChannelOutboundHandler 接口。

在这里插入图片描述

Netty 以适配器的形式提供了大量默认的 ChannelHandler 实现,主要目的是为了简化程序开发的过程,我们只需要 重写我们关注的事件和方法就可以了。 通常我们会以继承的方式使用以下适配器和抽象:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelDuplexHandler
  • ChannelOutboundHandlerAdapter

ChannelPipeline

当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。 一个 Channel 包含一个 ChannelPipeline。 ChannelPipeline 为 ChannelHandler 的链,一个 pipeline 上可以有多个 ChannelHandler。

我们可以在 ChannelPipeline 上通过 addLast() 方法添加一个或者多个ChannelHandler (一个数据或者事件可能会被多个 Handler 处理) 。当一个 ChannelHandler 处理完之后就将数据交给下一个 ChannelHandler :

 b.group(eventLoopGroup).handler(new ChannelInitializer<SocketChannel>() {
 
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));
                        ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));
                        ch.pipeline().addLast(new KryoClientHandler());
                    }
                });

入站事件和出站事件的流向

从服务端角度来看,如果一个事件的运动方向是从客户端到服务端,那么这个事件是入站的,如果事件运动的方向 是从服务端到客户端,那么这个事件是出站的。

在这里插入图片描述
上图是 Netty 事件入站和出站的大致流向,入站和出站的 ChannelHandler 可以被安装到一个ChannelPipeline中, 如果一个消息或其他的入站事件被[读取],那么它会从ChannelPipeline的头部开始流动,并传递给第一个ChannelInboundHandler ,这个ChannelHandler的行为取决于它的具体功能,不一定会修改消息。 在经历过第一个ChannelInboundHandler之后, 消息会被传递给这条ChannelHandler链的下一个ChannelHandler,最终消息会到达ChannelPipeline尾端,消息的读取也就结束了。

数据的出站 (发送) 流程与入站是相似的,在出站过程中,消息从ChannelOutboundHandler链的尾端开始流动, 直到到达它的头部为止,在这之后,消息会到达网络传输层进行后续传输。

鉴于入站操作和出站操作是不同的,可能有同学会疑惑:❓ 为什么入站 ChannelHandler和出站 ChannelHandler的数据 不会窜流呢(为什么 入站 的数据不会到出站 ChannelHandler 链中)?

因为Netty可以区分ChannelInboundHandler和 ChannelOutboundHandler的实现,并确保数据只在两个相同类型的ChannelHandler直接传递,即数据要么在 ChannelInboundHandler链之间流动,要么在ChannelOutboundHandler链之间流动。

当ChannelHandler被添加到ChannelPipeline中后,它会被分配一个ChannelHandlerContext, 它代表了ChannelHandler和ChannelPipeline之间的绑定。 ChannelPipeline 通过 ChannelHandlerContext来间接管理 ChannelHandler 。

在这里插入图片描述


在这里插入图片描述


ChannelFuture(操作执行结果)

public interface ChannelFuture extends Future<Void> {
    Channel channel();

    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);
     ......

    ChannelFuture sync() throws InterruptedException;
}

Netty 是异步非阻塞的,所有的 I/O 操作都为异步的。因此,我们不能立刻得到操作是否执行成功,但是,你可以通过 ChannelFuture 接口的 addListener() 方法注册一个监听 ChannelFutureListener,当操作执行成功或者失败时,监听就会自动触发返回结果。

ChannelFuture f = b.connect(host, port).addListener(future -> {

  if (future.isSuccess()) {
    System.out.println("连接成功!");
  } else {
    System.err.println("连接失败!");
  }
}).sync();

并且,你还可以通过ChannelFuture 的 channel() 方法获取连接相关联的Channel 。

Channel channel = f.channel();

另外,我们还可以通过 ChannelFuture 接口的 sync()方法让异步的操作编程同步的。

// bind()是异步的,但是,你可以通过 `sync()`方法将其变为同步。
ChannelFuture f = b.bind(port).sync();

本文小结

本文详细介绍了netty的核心组件以及知识,对netty的理解与掌握有极大的帮助。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值