Netty工作模型——网络IO模型的演进,从BIO到NIO到Reactor模型与Netty工作模型

本文介绍了Java的三种IO模型:BIO、NIO和AIO,以及它们的特点和应用场景。BIO是同步阻塞,适合低并发;NIO是同步非阻塞,常用于Netty框架;AIO是异步非阻塞。文章还详细讲解了Reactor模型的单Reactor单线程、单Reactor多线程和主从Reactor多线程三种模式。最后,提到了Netty的工作模型及其优化,并展示了Netty的基本使用示例,以及Netty中的Future-Listener机制用于异步操作的处理。

一、IO模型

I/O 模型简单的理解:就是 用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。
Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO。

1、BIO(同步阻塞)

服务器实现模式为 一个连接对应一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。

在JDK1.4之前,BIO是我们实现网络编程的唯一选择,虽然服务端对并发量支撑较差,但好在程序简单易理解。

在这里插入图片描述


import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BIOServer {
    public static void main(String[] args) throws Exception {

        //线程池机制

        //思路
        //1. 创建一个线程池
        //2. 如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)

        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

        //创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);

        System.out.println("服务器启动了");

        while (true) {

            System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
            //监听,等待客户端连接
            System.out.println("等待连接....");
            final Socket socket = serverSocket.accept();
            System.out.println("连接到一个客户端");

            //就创建一个线程,与之通讯(单独写一个方法)
            newCachedThreadPool.execute(new Runnable() {
                public void run() { //我们重写
                    //可以和客户端通讯
                    handler(socket);
                }
            });

        }


    }

    //编写一个handler方法,和客户端通讯
    public static void handler(Socket socket) {

        try {
            System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
            byte[] bytes = new byte[1024];
            //通过socket 获取输入流
            InputStream inputStream = socket.getInputStream();

            //循环的读取客户端发送的数据
            while (true) {

                System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());

                System.out.println("read....");
               int read =  inputStream.read(bytes);
               if(read != -1) {
                   System.out.println(new String(bytes, 0, read
                   )); //输出客户端发送的数据
               } else {
                   break;
               }
            }


        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("关闭和client的连接");
            try {
                socket.close();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

BIO的问题也很明显:
1.每个请求都需要创建独立的线程,与对应的客户端进行数据Read、业务处理、数据Write。
2.当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
3.连接建立后,如果当前线程暂时没有数据可读,则线程会阻塞在Read操作上,造成线程资源浪费。

2、NIO(同步非阻塞)

Java NIO全称java non-blocking IO,是指JDK从1.4开始提供新的API,是同步非阻塞的,NIO相关类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写。

NIO有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。

在这里插入图片描述
在这里插入图片描述
由于NIO的编程复杂、API复杂等等原因,Netty应运而生。

3、AIO(异步非阻塞)

JDK7 引入了 Asynchronous I/0,即 AIO。在进行IO编程中,常用到两种模式:Reactor 和 Proactor。Java 的NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。

AIO 即 NIO2.0,叫做异步不阻塞的 IO。AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

在这里插入图片描述

二、Reactor模型

AIO目前并不是主流的IO模型,linux目前并不支持AIO的异步非阻塞模式,即使是epoll也是在NIO的基础上进行的优化,所以当前主流的IO模型还是NIO模型。

1、单Reactor单线程

在这里插入图片描述
在这里插入图片描述

  • Select 是Java NIO多路复用模型的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求。
  • Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发。
  • 如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接3完成后的后续业务处理。
  • 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应。
  • Handler 会完成 Read一业务处理一Send 的完整业务流程。

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws Exception{

        //创建ServerSocketChannel -> ServerSocket

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //得到一个Selecor对象
        Selector selector = Selector.open();

        //绑定一个端口6666, 在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        //把 serverSocketChannel 注册到  selector 关心 事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("注册后的selectionkey 数量=" + selector.keys().size()); // 1

        //循环等待客户端连接
        while (true) {

            //这里我们等待1秒,如果没有事件发生, 返回
            if(selector.select(1000) == 0) { //没有事件发生
                System.out.println("服务器等待了1秒,无连接");
                continue;
            }

            //如果返回的>0, 就获取到相关的 selectionKey集合
            //1.如果返回的>0, 表示已经获取到关注的事件
            //2. selector.selectedKeys() 返回关注事件的集合
            //   通过 selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectionKeys 数量 = " + selectionKeys.size());

            //遍历 Set<SelectionKey>, 使用迭代器遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()) {
                //获取到SelectionKey
                SelectionKey key = keyIterator.next();
                //根据key 对应的通道发生的事件做相应处理
                if(key.isAcceptable()) { //如果是 OP_ACCEPT, 有新的客户端连接
                    //该该客户端生成一个 SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());
                    //将  SocketChannel 设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将socketChannel 注册到selector, 关注事件为 OP_READ, 同时给socketChannel
                    //关联一个Buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                    System.out.println("客户端连接后 ,注册的selectionkey 数量=" + selector.keys().size()); //2,3,4..


                }
                if(key.isReadable()) {  //发生 OP_READ

                    //通过key 反向获取到对应channel
                    SocketChannel channel = (SocketChannel)key.channel();

                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("form 客户端 " + new String(buffer.array()));

                }

                //手动从集合中移动当前的selectionKey, 防止重复操作
                keyIterator.remove();

            }

        }

    }
}

这种模型比较简单,没有多线程问题,所有的连接、业务处理都在一个线程中完成。

这种模型显而易见有个致命缺点,就是当业务处理比较耗时时,会造成消息积压。

2、单Reactor多线程

在这里插入图片描述
在这里插入图片描述

Reactor 对象通过 select 监控客户端请求事件,收到事件后,通过 dispatch 进行分发。

  • 如果建立连接请求,则右 Acceptor 通过accept 处理连接请求,然后创建一个 Handler 对象处理完成连接后的各种事件
  • 如果不是连接请求,则由 reactor 分发调用连接对应的 handler 来处理。
  • handler 只负责响应事件,不做具体的业务处理,通过 read 读取数据后,会分发给后面的 worker 线程池的某个线程处理业务。
  • worker 线程池会分配独立线程完成真正的业务,并将结果返回给 handler。
  • handler 收到响应后,通过 send 将结果返回给 client。

这种模式充分利用了多核CPU的处理能力,使用多线程进行业务处理。但是单reactor处理所有的事件的监听和响应,在单reactor下,高并发场景很容易出现性能瓶颈。

3、主从Reactor多线程

在这里插入图片描述
在这里插入图片描述

在这种模式下,对Reactor进一步拆分。

  • Reactor 主线程 MainReactor 对象通过 select 监听连接事件,收到事件后,通过Acceptor 处理连接事件。
  • 当 Acceptor 处理连接事件后,MainReactor 将连接分配给 SubReactor。
  • subreactor 将连接加入到连接队列进行监听,并创建 handler 进行各种事件处理。
  • 当有新事件发生时,subreactor 就会调用对应的 handler 处理。
  • handler 通过 read 读取数据,分发给后面的 worker 线程处理。
  • worker 线程池分配独立的 worker 线程进行业务处理,并返回结果。
  • handler 收到响应的结果后,再通过 send 将结果返回给 client。
  • Reactor 主线程可以对应多个 Reactor 子线程、即 MainRecator 可以关联多个 SubReactor。

三、Netty工作模型

1、Netty工作模型

Netty就是在主从Reactor多线程的模型下,进行了优化产生的。

在这里插入图片描述

  • Netty 抽象出两组线程池 BossGroup 专门负责接收客户端的连接,WorkerGroup 专门负责网络的读写。
  • BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup(在Epoll支持下也可以是EpollEventLoopGroup)
  • NioEventLoopGroup 相当于一个事件循环组,这个组中含有多个事件循环 ,每一个事件循环是 NioEventLoop。
  • NioEventLoop 表示一个不断循环的执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 的网络通讯。
  • NioEventLoopGroup 可以有多个线程、即可以含有多个 NioEventLoop。
  • 每个 Boss NioEventLoop 循环执行的步骤有 3 步:
    (1)轮询 accept 事件(2)处理 accept 事件,与 client 建立连接,生成 NioScocketChannel,并将其注册到某个 worker NIOEventLoop 上的 selector(3)处理任务队列的任务 ,即 runAllTasks
  • 每个 Worker NIOEventLoop 循环执行的步骤:
    (1)轮询 read,write 事件(2)处理 i/o 事件,即 read ,write 事件,在对应 NioScocketChannel 处理(3)处理任务队列的任务,即 runAlITasks
  • 每个 WorkerNIOEventLoop 处理业务时,会使用pipeline(管道),pipeline 中包含了 channel,即通过pipeline可以获取到对应通道。管道中维护了很多的 处理器

2、Netty入门案例

(1)server


public class NettyServer {
    public static void main(String[] args) throws Exception {


        //创建BossGroup 和 WorkerGroup
        //说明
        //1. 创建两个线程组 bossGroup 和 workerGroup
        //2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成
        //3. 两个都是无限循环
        //4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        //   默认实际 cpu核数 * 2 (NettyRuntime.availableProcessors() * 2)
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //8



        try {
            //创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();

            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("客户socketchannel hashcode=" + ch.hashCode()); //可以使用一个集合管理 SocketChannel, 再推送消息时,可以将业务加入到各个channel 对应的 NIOEventLoop 的 taskQueue 或者 scheduleTaskQueue
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器

            System.out.println(".....服务器 is ready...");

            //绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //给cf 注册监听器,监控我们关心的事件

            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口 6668 成功");
                    } else {
                        System.out.println("监听端口 6668 失败");
                    }
                }
            });


            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        }finally {
            // 优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}


/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //读取数据事件(这里我们可以读取客户端发送的消息)
    /*
    1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
    2. Object msg: 就是客户端发送的数据 默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        System.out.println("服务器读取线程 " + Thread.currentThread().getName() + " channle =" + ctx.channel());
        System.out.println("server ctx =" + ctx);
        System.out.println("看看channel 和 pipeline的关系");
        Channel channel = ctx.channel(); // channel和pipeline是互相包含的关系
        ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链表, 涉及到出栈入栈问题


        //将 msg 转成一个 ByteBuf
        //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:" + channel.remoteAddress());
    }

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        //writeAndFlush 是 write + flush
        //将数据写入到缓存,并刷新
        //一般讲,我们对这个发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));
    }

    //处理异常, 一般是需要关闭通道

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

(2)client


public class NettyClient {
    public static void main(String[] args) throws Exception {

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();


        try {
            //创建客户端启动对象
            //注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
            Bootstrap bootstrap = new Bootstrap();

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });

            System.out.println("客户端 ok..");

            //启动客户端去连接服务器端
            //关于 ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {
            // 优雅关闭
            group.shutdownGracefully();
        }
    }
}


public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client " + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, server: (>^ω^<)喵", CharsetUtil.UTF_8));
    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3、Netty任务队列

(1)用户程序自定义的普通任务
将任务推送至普通任务队列异步执行,不阻塞主线程。

(2)定时任务
将任务推送至定时任务队列中异步执行,不阻塞主线程。

//读取数据事件(这里我们可以读取客户端发送的消息)
/*
1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
2. Object msg: 就是客户端发送的数据 默认Object
 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {


    //比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交在该channel 对应的NIOEventLoop 的 taskQueue中

    //解决方案1 用户程序自定义的普通任务
    ctx.channel().eventLoop().execute(new Runnable() {
        @Override
        public void run() {

            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        }
    });
    // 上面的任务执行完毕之后,才会执行该任务!taskQueue中的任务是同一个线程
    ctx.channel().eventLoop().execute(new Runnable() {
        @Override
        public void run() {

            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        }
    });

    //解决方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中

    ctx.channel().eventLoop().schedule(new Runnable() {
        @Override
        public void run() {

            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵4", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        }
    }, 5, TimeUnit.SECONDS);



    System.out.println("go on ...");


    System.out.println("服务器读取线程 " + Thread.currentThread().getName() + " channle =" + ctx.channel());
    System.out.println("server ctx =" + ctx);
    System.out.println("看看channel 和 pipeline的关系");
    Channel channel = ctx.channel(); // channel和pipeline是互相包含的关系
    ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链表, 涉及到出栈入栈问题


    //将 msg 转成一个 ByteBuf
    //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
    ByteBuf buf = (ByteBuf) msg;
    System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
    System.out.println("客户端地址:" + channel.remoteAddress());
}

(3)非当前Reactor线程调用Channel的各种方法
将channel列表维护为一个List,可以取得该List中的数据在非当前用户下推送给其他用户。

//使用链式编程来进行设置
bootstrap.group(bossGroup, workerGroup) //设置两个线程组
        .channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现
        .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
        .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup
        .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)
            //给pipeline 设置处理器
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                System.out.println("客户socketchannel hashcode=" + ch.hashCode()); //可以使用一个集合管理 SocketChannel, 再推送消息时,可以将业务加入到各个channel 对应的 NIOEventLoop 的 taskQueue 或者 scheduleTaskQueue
                ch.pipeline().addLast(new NettyServerHandler());
            }
        }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器

Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 网络通道。NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线程 NioEventLoop负责。

NioEventLoopGroup 下包含多个 NioEventLoop

  • 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
  • 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
  • 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
  • 每个 NioChannel 都绑定有一个自己的 ChannelPipeline

四、异步模型

当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态、通知和回调来通知调用者。

Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得IO 操作结果。

Netty 的异步模型是建立在 fuure 和 callback 的之上的。callback 就是回调。 Future是重点,它的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun 返回显然不合适。那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future 去监控方法 fun 的处理过程(即 : Future-Listener 机制)

1、Future-Listener机制

当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态注册监听函数来执行完成后的操作。

常见有如下操作:

  • 通过 isDone 方法来判断当前操作是否完成;
  • 通过 isSuccess 方法来判断已完成的当前操作是否成功;
  • 通过 getCause 方法来获取已完成的当前操作失败的原因;
  • 通过 isCancelled 方法来判断已完成的当前操作是否被取消;通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果Future 对象已完成,则通知指定的监听器
//绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
//启动服务器(并绑定端口)
ChannelFuture cf = bootstrap.bind(6668).sync();

//给cf 注册监听器,监控我们关心的事件

cf.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (cf.isSuccess()) {
            System.out.println("监听端口 6668 成功");
        } else {
            System.out.println("监听端口 6668 失败");
        }
    }
});

参考资料

《Scalable IO in Java》
https://blog.csdn.net/qq_36389060/article/details/124232377
https://www.bilibili.com/video/BV1DJ411m7NR

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值