Netty常见组件二

一、ChannelHandler

在这里插入图片描述

1、ChannelHandler接口

从开发人员的角度来看,Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 的方法是由网络事件触发的。

Netty 定义了两个重要的 ChannelHandler 子接口:

  • ChannelInboundHandler:处理入站数据以及各种状态变化;
  • ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作。

1.1 ChannelInboundHandler 接口方法

  • channelRegistered:当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
  • channelUnregistered:当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用
  • channelActive:当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
  • channelInactive:当 Channel 离开活动状态并且不再连接它的远程节点时被调用
  • channelReadComplete:当 Channel 上的一个读操作完成时被调用
  • channelRead:当从 Channel 读取数据时被调用ChannelWritabilityChanged:
    当 Channel 的可写状态发生改变时被调用。可以通过调用 Channel 的 isWritable()方法来检测 Channel 的可写性。与可写性相关的阈值可以通过 Channel.config().setWriteHighWaterMark()和 Channel.config().setWriteLowWaterMark()方法来设置
  • userEventTriggered:当 ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用。

这些方法将会在数据被接收时或者与其对应的 Channel 状态发生改变时被调用。这些方法也与 Channel 的生命周期密切相关。

1.2 ChannelOutboundHandler 接口方法

  • bind(ChannelHandlerContext,SocketAddress,ChannelPromise):当请求将 Channel 绑定到本地地址时被调用
  • connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise):当请求将 Channel 连接到远程节点时被调用
  • disconnect(ChannelHandlerContext,ChannelPromise):当请求将 Channel 从远程节点断开时被调用
  • close(ChannelHandlerContext,ChannelPromise):当请求关闭 Channel 时被调用
  • deregister(ChannelHandlerContext,ChannelPromise):当请求将 Channel 从它的 EventLoop 注销时被调用
  • read(ChannelHandlerContext):当请求从 Channel 读取更多的数据时被调用
  • flush(ChannelHandlerContext):当请求通过 Channel 将入队数据冲刷到远程节点时被调用
  • write(ChannelHandlerContext,Object,ChannelPromise):当请求通过 Channel 将数据写到远程节点时被调用

2、ChannelHandler适配器

我们可以使用 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 类作为自己的 ChannelHandler 的父类。因为它们提供了定义在对应接口中的所有方法的默认实现。所以,我们在开发过程中,只需要关注实现我们感兴趣的事件即可。

不管是读和写,Buffer 用完后都必须进行释放,否则可能会造成内存泄露。
Netty为我们提供的各种预定义 Handler 实现,都实现了数据的正确处理,所以我们自行在编写业务 Handler 时,也需要注意这一点:要么继续传递,要么自行释放。

SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter的子类,做了一些资源管理封装,这个实现会在数据被 channelRead0()方法消费之后自动释放数据。使用它也是不多的选择。
在这里插入图片描述

二、Bootstrap和ServerBootstrap

网络编程里,“服务器”和“客户端”实际上表示了不同的网络行为;换句话说,是监听传入的连接还是建立到一个或者多个进程的连接。因此,Netty 提供了 两种类型的启动器引导:

  • Bootstrap:用于客户端启动,
  • ServerBootstrap:用于服务器启动。

Bootstrap和ServerBootStrap是 Netty提供的一个创建客户端和服务端启动器的工厂类,使用这个工厂类非常便利地创建启动类,大大地减少了开发的难度。

看一下它们类图:
在这里插入图片描述

可以看出都是继承于 AbstractBootStrap抽象类,所以大致上的配置方法都相同。

Bootstrap和ServerBootstrap的区别:
在这里插入图片描述

  • 区别一
    ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而 Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。
  • 区别二
    一个客户端只需要一个 EventLoopGroup,但是一个 ServerBootstrap 则需要两个(也可以是同一个实例)EventLoopGroup。

一般来说,使用 Bootstrap/ServerBootstrap创建启动器的步骤可分为以下几步:
在这里插入图片描述
一般代码如下:下面就从这几个方法了解 Bootstrap。

    private void start() throws InterruptedException {
        //创建两个线程组 boosGroup、workerGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //创建服务端的启动对象,设置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    //设置服务端通道实现类型
                    .channel(NioServerSocketChannel.class)
                    //设置线程队列得到连接个数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //设置保持活动连接状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //使用本地地址,绑定端口号
                    .localAddress(new InetSocketAddress(port))
                    //使用匿名内部类的形式初始化通道对象
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //给pipeline管道设置处理器
                            socketChannel.pipeline().addLast(new MyServerHandler());
                        }
                    });
            System.out.println("MyServer 服务端已经准备就绪...");
            // 异步绑定,启动服务端, sysc()会阻塞到完成
            ChannelFuture channelFuture = bootstrap.bind().sync();
            System.out.println("服务器启动完成");
            //阻塞当前线程,对关闭通道进行监听(直到服务器 channel被关闭)
            channelFuture.channel().closeFuture().sync();
        } finally {
            //释放掉所有的资源,包括创建的线程
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

1、group()

因为服务端要使用两个线程组,一般创建线程组直接使用以下 new就完事了。

  • bossGroup:用于监听客户端连接,专门负责与客户端创建连接,并把连接注册到workerGroup的Selector中。
  • workerGroup:用于处理每一个连接发生的读写事件。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();

既然是线程组,那线程数默认是多少呢?

通过源码可以看到,如果不传,默认的线程数是 cpu核数的两倍。
在这里插入图片描述
假设想自定义线程数,可以使用有参构造器:

//设置bossGroup线程数为1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//设置workerGroup线程数为16
EventLoopGroup workerGroup = new NioEventLoopGroup(16);

2、channel()

这个方法用于设置通道类型,当建立连接后,会根据这个设置创建对应的 Channel实例。

通道类型有以下:

  • NioSocketChannel: 异步非阻塞的客户端 TCP Socket 连接。
  • NioServerSocketChannel: 异步非阻塞的服务器端 TCP Socket 连接。因为是异步非阻塞的。所以一般首选
  • OioSocketChannel: 同步阻塞的客户端 TCP Socket 连接。
  • OioServerSocketChannel: 同步阻塞的服务器端 TCP Socket 连接。
  • NioSctpChannel: 异步的客户端 Sctp(Stream Control Transmission Protocol,流控制传输协议)连接。
  • NioSctpServerChannel: 异步的 Sctp 服务器端连接。

3、option()与childOption()

option()与childOption()的区别:

  • option():设置的是服务端参数,用于接收进来的连接,也就是 boosGroup线程。
  • childOption(),是提供给父管道接收到的连接,也就是 workerGroup线程。

ChannelOption类的各种属性在套接字选项中都有对应。所以我们直接指定它的属性即可。

常用属性如下:

  • ChannelOption.SO_BACKLOG
    SO_BACKLOG (Socket参数),服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128
  • ChannelOption.SO_REUSEADDR
    ChanneOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口。
  • ChannelOption.SO_KEEPALIVE
    SO_KEEPALIVE (Socket参数),连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。
  • ChannelOption.SO_RCVBUF
    SO_RCVBUF(Socket参数),TCP数据接收缓冲区大小。
  • ChannelOption.SO_LINGER
    ChannelOption.SO_LINGER 参数对应于套接字选项中的 SO_LINGER,Linux 内核默认的处理方式是当用户调用 close()方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close()的调用时间,直到数据完全发送
  • ChannelOption.TCP_NODELAY
    TCP_NODELAY (TCP参数),立即发送数据,默认值为Ture。

4、localAddress()

在这里插入图片描述
localAddress() 方法:提供用于服务端或者客户端绑定服务器地址和端口号(SocketAddress)。

localAddress() 与 bind()方法在绑定 地址和端口号时是类似的,两者选一个绑定即可。

5、childHandler(重点)

在 Bootstrap中 childHandler()方法需要初始化通道,实例化一个 ChannelInitializer,这时候需要重写 initChannel()初始化通道的方法,装配流水线就是在这个地方进行。代码如下:

//使用匿名内部类的形式初始化通道对象
.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //给pipeline管道设置处理器
        socketChannel.pipeline().addLast(new MyServerHandler());
    }
});

ChannelPipeline是 Netty处理请求的责任链,ChannelHandler则是具体处理请求的处理器。

我们的重点就是创建一个 ChannelHandler,然后实现业务Handler的处理。

处理器Handler主要分为两种:一般我们会继承这两个。

  • ChannelInboundHandlerAdapter(入站处理器):入站指的是数据从底层java NIO Channel到Netty的Channel。
  • ChannelOutboundHandler(出站处理器):出站指的是通过Netty的Channel来操作底层的java NIO Channel。

SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter的子类,做了一些封装,一般我们也会使用。

6、bind()

bind()方法:提供用于服务端或者客户端绑定服务器地址和端口号(SocketAddress),默认是异步启动。如果加上 sync()方法则是同步。

// 异步绑定,启动服务端, sysc()会阻塞到完成
ChannelFuture channelFuture = bootstrap.bind().sync();

bind() 有五个同名的重载方法,作用都是用于绑定地址和端口号,并启动服务。
在这里插入图片描述

7、关闭EventLoopGroup

//释放掉所有的资源,包括创建的线程
bossGroup.shutdownGracefully().sync();;
workerGroup.shutdownGracefully();

优雅地关闭所有的 child Channel。关闭之后,释放掉底层的资源。如果加上 sync()方法则是同步。

参考文章:

  • 超详细Netty入门,看这篇就够了!:https://blog.csdn.net/yunqiinsight/article/details/107953180

– 求知若饥,虚心若愚。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值