第3章 Netty的组件和设计

第3章 Netty的组件和设计

Netty源码设计非常优秀。主要体现在技术方面和体系结构方面。

    1. Netty基于Java NIO的异步和事件驱动的实现,保证了高负载下应用程序性能的最大化和可伸缩性。
    1. Netty使用众多设计模式,将应用程序从网络层解耦。

3.1 Channel、EventLoop和ChannelFuture

Channel、EventLoop和ChannelFuture是Netty用于对网络进行的抽象:

    1. Channel ------ Socket
    1. EventLoop ------ 控制流、多线程和并发
    1. ChannelFuture ------ 异步通知

3.1.1 Channel 接口

    1. EmbeddedChannel ----- Embedded传输
    1. LocalServerChannel ----- Local传输
    1. NioDatagramChannel ----- UDP协议NIO传输
    1. NioSctpChannel ----- SCTP协议NIO传输(基于Session) (注:SCTP (Stream Control Transmission Protocol)是一种传输协议,在TCP/IP协议栈中所处的位置和TCP、UDP类似,兼有TCP/UDP两者特征。)
    1. NioSocketChannel ----- TCP协议NIO传输

3.1.2 EventLoop 接口

    1. EventLoop是Netty中非常重要的组件,EventLoop用于处理生命周期中发生的所有事件。
    1. 与EventLoop绑定的Thread称为I/O线程,用于处理整个Channel生命周期中的I/O事件。
    1. 下图说明Channel、EventLoop、Thread以及EventLoopGroup之间的关系

约定俗成的关系(非常重要):
    1. 一个EventLoopGroup包含一个或多个EventLoop
    1. 一个EventLoop在其生命周期内只能和一个Thread绑定
    1. 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理
    1. 一个Channel在其生命周期内,只能注册于一个EventLoop
    1. 一个EventLoop可能被分配处理多个Channel。也就是EventLoop与Channel是1:n的关系
    1. 一个Channel上的所有ChannelHandler的事件由绑定的EventLoop中的I/O线程处理
    1. 不要阻塞Channel的I/O线程,可能会影响该EventLoop中其他Channel事件处理

3.1.3 ChannelFuture 接口

Netty中所有的I/O操作都是异步的,该异步操作可能无法立即得到返回。Netty提供addListener()方法注册回调函数。

    1. 可以将ChannelFuture看作是将来要执行的操作的结果占位符,什么时候被执行,不知道。但肯定会被执行
    1. 属于同一个Channel的操作(回调函数)都被保证将按照注册的顺序执行。

3.2 ChannelHandler 和 ChannelPipeline

3.2.1 ChannelHandler 接口(Netty 的主要组件)

    1. Netty提供了很多扩展的ChannelHandler。如ChannelInboundHandler处理入站事件。
    1. ChannelHandler的方法,就是常说的事件。如:channelActive(链路激活事件)等。所以,ChannelHandler可以说是处理事件的具体业务代码逻辑。

3.2.2 ChannelPipeline 接口

    1. ChannelPipeline本质上是ChannelHandler链的容器
    1. ChannelHandler是处理Channel上的入站和出站事件的代码。
    1. ChannelHandler对象接收事件触发并执行实现的业务逻辑,接着传递给链中的下一个ChannelHandler处理
    1. 请注意下图中头部-尾端,Netty的头部-尾端是规定的,需要记住。

ChannelHandler 安装到 ChannelPipeline 中的过程如下所示: 

  1. 一个ChannelInitializer的实现被注册到了ServerBootstrap中 ; 
  2. 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler; 
  3. ChannelInitializer 将它自己从 ChannelPipeline 中移除。

上图解释:
    1. 一个入站事件被读取,从ChannelPipeline头部开始流动,传递给第一个ChannelInBoundHandler,这个 ChannelHandler 不一定会实际地修改数据,具体取决于它的具体功能。在这之后,数据将会被传递给链中的下一个ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端
    1. 一个出站事件触发,从链路尾端的ChannelOutboundHandler开始流动,直到它到达链的头部为止。

注:一个 Netty 应用程序中入站和出站数据流之间的区别。从一个客户端应用程序
的角度来看,如果事件的运动方向是从客户端到服务器端,那么我们称这些事件为出站的,反之
则称为入站的。

3.2.3 channel.write(...)和channelHandlerContext.write(...)区别

    1. channel.write(...) ----- 将会导致消息从ChannelPipeline 的尾端开始流动
    1. channelHandlerContext.write(...) ----- 将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动
    1. ctx.write(...)的性能优于channel.write(...)
    1. channelHandlerContext.write(...)是将消息从当前的出站队列Hanlder往头部传。 channel.write(...)是从ChannelPipeline的尾部开始往头部传。如果没有理解这点,请看下文。

3.2.4 编码器和解码器

    1. Netty提供多种编码器和解码器,比如:ProtobufDecoder或ProtobufEncoder。所有由 Netty 提供的编码器/解码器适配器类都实现
      了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口
    1. 编码器/解码器中覆写了channelRead()方法,在方法里调用encode()/decode()方法。再传递给下一个ChannelHandler处理.
    1. 解码器添加在入站事件的头部,编码器添加在出站事件的头部。天然的解决了网络数据的编解码,非常优秀的设计。

3.2.5 抽象类 SimpleChannelInboundHandler

最常见的情况是,你的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。
要创建一个这样的 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler,其中 T 是你要处理的消息的 Java 类型 。
在这个 ChannelHandler 中, 你将需要重写基类的一个或者多个方法,并且获取一个到 ChannelHandlerContext 的引用,这个引用将作为输入参数传递给 ChannelHandler 的所有方法。
在这种类型的 ChannelHandler 中,最重要的方法是 channelRead0(ChannelHandlerContext,T),只要不要阻塞当前的 I/O 线程即可。

3.3 引导

Netty有两种类型的引导: 客户端(Bootstrap)和服务端(ServerBootstrap)

    1. Bootstrap(客户端) - 连接远程的主机和端口
    1. ServerBootstrap(服务端) - 两个端口。第一个是本地监听端口,第二个是与tcp连接端口。
    1. 客户端需要一个EventLoopGroup;服务端需要两个EventLoopGroup

3.3.1 服务端需要两个EventLoopGroup

Netty的服务端负责两项任务:

    1. 监听本地端口,等待客户端连接。
    1. 建立客户端通信的临时分配的端口。所以服务端有两个EventLoopGroup,通常称为: bossEventLoopGroup + workerEventLoopGroup.

上图解释:

因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务
器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传
入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。

与 ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建
Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel
分配一个 EventLoop。

    1. 上图左边的是ServerChannel,用于监听本地端口的通道。对应于bossEventLoopGroup
    1. 右边的是与具体客户端连接的channel,用于数据通信。对应于workerEventLoopGroup
代码:
private EventLoopGroup bossGroup = new NioEventLoopGroup();
private EventLoopGroup workerGroup = new NioEventLoopGroup(2,...);

public void bind(int port) throws InterruptedException {
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 1024)
            .childHandler(new ChildChannelHandler());

    ChannelFuture cf = bootstrap.bind(port);
    cf.addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            if(future.isSuccess()) {
                LOGGER.info("netty server bind success.");
            } else {
                LOGGER.error("netty server bind fail.", future.cause());
            }
        }
    });
}

其实传一个EventLoopGroup也是可行的,源码如下



共用了一个EventLoopGroup

posted @   economies  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示