Netty核心组件

本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别


概述

  Netty核心组件:

  • Bootstrap:Netty的启动入口类,分为客户端(ClientBootstrap)和服务端(ServerBootstrap)两种类型
  • Channel:网络通信的核心抽象层,封装了底层Socket操作,提供统一的网络I/O接口
  • EventLoop:采用Reactor线程模型,一个EventLoop可绑定多个Channel,实现多路复用
  • 事件模型
    • 入站事件(Inbound):从网络层到业务层的处理流程(如数据读取)
    • 出站事件(Outbound):从业务层到网络层的处理流程(如数据写入)
  • ChannelHandler与Pipeline
    • ChannelHandler:处理网络与业务间的各种操作(如编解码、压缩等)
    • Pipeline:采用责任链模式组织Handler,负责事件的流转处理
  • ChannelFuture:为异步操作提供结果通知机制,是JUC Future的增强实现

一、EventLoopGroup&EventLoop

  EventLoopGroup用于分配当前的channel由哪一个EventLoop进行管理当某个Channel分配给某一个EventLoop以后,在Channel的整个生命周期内,都是由该EventLoop处理。一个EventLoop可能对应多个Channel,这是多路复用思想在Netty中的体现。
  EventLoopGroup负责去管理EventLoop,当有一个Channel诞生时,由EventLoopGroup负责将EventLoop和Channel绑定。在Channel的整个生命周期内,都由该EventLoop进行管理。
在这里插入图片描述
图片来源:图灵学院

  而其他线程想要执行Channel的某些动作时,都要将Channel交给与之绑定的EventLoop本身进行相关的执行。以服务端的绑定操作为例,这里的bind方法是由主线程去执行的,但是底层绑定操作应该由NioServerSocketChannel执行。
在这里插入图片描述
  在真正执行之前会进行判断:

  • 如果当前线程是EventLoop中记录的线程,就由当前线程去执行

在这里插入图片描述

  • 否则将其包装成Runnable任务,交给EventLoop 并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。

在这里插入图片描述
  在上述的案例中,bind操作应该交给NioServerSocketChannel对应的EventLoop去执行。

二、Channel&ChanneHandler

  Channel可以理解成是对于Socket的一种封装,每次通信都会产生一个Channel。而Channel中还维护了一个pipeline。pipeline可以看做是一个双向链表,记录了该Channel的所有入站和出站事件。即:每一个channel都有自己的pipeline,负责将handler进行组装成责任链。(双向链表)
  责任链中的每个元素称为ChanneHandler,需要实现ChannelInboundHandlerChannelInboundHandler。前者是入站处理器,处理从网络到业务部分的事件。后者是出站处理器,处理从业务部分到网络的事件。
  如果不想实现接口中的所有方法,可以去继承对应的ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter,这两个抽象类运用了接口适配器模式,对接口中的方法进行了默认的实现,用户在使用时只需要自己选择需要重写的方法即可。
在这里插入图片描述
  所有的入站和出站事件,在ChannelHandlerMask中都有常量定义:
在这里插入图片描述

三、ChannelPipeline&ChannelHandlerContext

  ChannelPipeline的底层是一种类似于双向链表的实现,入站和出站事件,在责任链中的顺序是没有要求的,但是入站和入站事件之间,出站和出站事件之间,对于顺序有严格的要求,关系到事件的流转。
  ChannelHandlerContextChannelPipeline中的上下文。每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext,而这个上下文,起到的作用和JDK的LinkedList的Node节点是相似的,用于组织责任链:
在这里插入图片描述
  在写数据时,我们既可以通过ctx去写,也可以通过channel().write();去写,这两者有什么区别?
在这里插入图片描述
  前者的写,只会写到与之相邻的下一个ChannelHandler上,而后者的写,会经过该ChannelPipeline后续所有的ChannelHandler

四、共享模式

  在一般的情况下,不同Channel之间的Handler,是线程隔离的(在Handler没有static成员变量的场景下),每个Channel都有自己的pipeline。即使是某个Handler出现在了两个Channel中,实际上也是不同的实例。
在这里插入图片描述
图片来源:图灵学院

  如果要求某个Handler中的属性共享,例如统计全局的信息,可以用到@ChannelHandler.Sharable注解:
在这里插入图片描述
  这样该实例在不同的Channel之间就可以共享。
在这里插入图片描述

五、Buffer的释放

  在NIO编程中,需要将数据读取到缓冲区,或者写入到缓冲区,而在Netty中依旧存在Buffer的概念:
在这里插入图片描述
  如果Buffer得不到及时的释放,可能会造成OOM的问题,在NIO中是进行的手动释放,Netty会自动进行Buffer的释放,因为Netty会在pipeline中安装两个Handle:
在这里插入图片描述

  这两个Handle位于责任链的头部和尾部,其中会进行buffer的释放。但是如果在入站或出站的操作中,没有将数据向前(出站)/向后(入栈)传递,而是在某个handler中进行了拦截,导致数据没有发送到责任链的头部和尾部,则需要调用ReferenceCountUtil.release方法手动地去进行资源的释放。
  writeAndFlush的底层就是这样实现的。
在这里插入图片描述
  同时在针对入栈的场景,Netty提供了SimpleChannelInboundHandler,在读取完成数据后,自动进行清理工作:
在这里插入图片描述

附录:Netty编程模型案例

服务端实现流程:

  1. 创建EventLoopGroup实例用于处理事件线程。
  2. 配置ServerBootstrap:
    • 绑定EventLoopGroup
    • 设置channel类型(NioServerSocketChannel)
    • 配置监听IP和端口
    • 添加handler处理链
  3. 绑定服务器并获取ChannelFuture
  4. 执行关闭操作
public class NettyServerDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Netty服务器启动");
        //产生EventLoopGroup的实例,需要线程去处理事件。
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //服务端ServerBootstrap
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //将EventLoopGroup绑定到ServerBootstrap上
            serverBootstrap.group(group)
                    //        指定使用何种channel(NioServerSocketChannel)
                    .channel(NioServerSocketChannel.class)
                    //指定端口
                    .localAddress(new InetSocketAddress(8080))
                    //指定要经历哪些handler
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });

            //服务端ServerBootstrap绑定到服务器, 通过sync确保服务器在绑定端口后才继续执行后续代码,避免在未准备好时就处理连接。
            // 拿到future。
            ChannelFuture f = serverBootstrap.bind().sync();
            //阻塞当前线程,直到服务端的ServerChannel被关闭
            f.channel().closeFuture().sync();
        } finally {
            //关闭线程组
            group.shutdownGracefully().sync();
        }
    }
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 读取客户端的信息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //将消息转换为buffer
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("接收到的客户端的消息" + buf.toString(StandardCharsets.UTF_8));
        //回写到客户端
        ctx.writeAndFlush(buf);
        ctx.close();
    }

    /**
     * 处理异常并关闭连接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

客户端实现流程:

  1. 创建EventLoopGroup实例用于处理事件线程。
  2. 配置Bootstrap:
    • 绑定EventLoopGroup1
    • 设置channel类型(NioSocketChannel)
    • 配置目标服务器IP和端口
    • 添加handler处理链
  3. 连接服务器并获取ChannelFuture
  4. 执行关闭操作
public class NettyClientDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Netty客户端启动");
        //产生EventLoopGroup的实例,需要线程去处理事件。
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //客户端Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //将EventLoopGroup的实例绑定到客户端
            bootstrap.group(group)
                    //        指定使用何种channel(NioSocketChannel)
                    .channel(NioSocketChannel.class)
                    //与服务端建立连接
                    .remoteAddress(new InetSocketAddress("127.0.0.1",8080))
                    //指定要经历哪些handler
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            //连接服务器
            ChannelFuture future = bootstrap.connect().sync();
            //阻塞当前线程,直到客户端的Channel被关闭
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }

    }
}
public class NettyClientHandler extends SimpleChannelInboundHandler {

    /**
     * 读取服务端发送的数据
     * @param ctx           the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
     *                      belongs to
     * @param msg           the message to handle
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("客户端接收到服务端的" + msg.toString());
        ctx.close();
    }

    /**
     * 建立连接后,客户端向服务端发送数据
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(
                "向服务端发送数据", CharsetUtil.UTF_8));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值