vlambda博客
学习文章列表

「Netty实战 03」大白话 Netty 核心组件分析

大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年。

本文目录:

  • Bytebuf(字节容器)

  • Bootstrap 和 ServerBootstrap(启动引导类)

  • Channel(网络操作抽象类)

  • EventLoop(事件循环)

    • EventLoop 介绍

    • Channel 和 EventLoop 的关系

    • EventloopGroup 和 EventLoop 的关系

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

  • ChannelFuture(操作执行结果)


觉得不错的话,欢迎 star!ღ( ´・ᴗ・` )比心

  • Netty 从入门到实战系列文章地址:https://github.com/Snailclimb/netty-practical-tutorial 。
  • RPC 框架源码地址:https://github.com/Snailclimb/guide-rpc-framework

简单介绍 Netty 最核心的一些组件(对于每一个组件这里不详细介绍)。

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

Bytebuf(字节容器)

网络通信最终都是通过字节流进行传输的。ByteBuf 就是 Netty 提供的一个字节容器,其内部是一个字节数组。 当我们通过 Netty 传输数据的时候,就是通过 ByteBuf 进行的。

我们可以将 ByteBuf 看作是 Netty 对 Java NIO 提供了 ByteBuffer 字节容器的封装和抽象。

有很多小伙伴可能就要问了 :为什么不直接使用 Java NIO 提供的 ByteBuffer 呢?

因为 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();
        }
    }

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

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

Channel(网络操作抽象类)

Channel 接口是 Netty 对网络操作抽象类。通过 Channel 我们可以进行 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 中最核心的概念了!

《Netty 实战》这本书是这样介绍它的:

EventLoop 定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。

是不是很难理解?说实话,我学习 Netty 的时候看到这句话是没太能理解的。

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

Channel 和 EventLoop 的关系

ChannelEventLoop 直接有啥联系呢?

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

EventloopGroup 和 EventLoop 的关系

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

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

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

「Netty实战 03」大白话 Netty 核心组件分析

https://www.jianshu.com/p/128ddc36e713

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

下面这段代码使用过 Netty 的小伙伴应该不会陌生,我们指定了序列化编解码器以及自定义的 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());
                    }
                });

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

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

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

ChannelHandler 被添加到的 ChannelPipeline 它得到一个 ChannelHandlerContext,它代表一个 ChannelHandlerChannelPipeline 之间的“绑定”。ChannelPipeline 通过 ChannelHandlerContext来间接管理 ChannelHandler

https://www.javadoop.com/post/netty-part-4

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();

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

Channel channel = f.channel();

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

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

后记


往期推荐