Netty自定义任务&Future-Listener机制

1. 自定义任务

常见场景:

(1)  用户程序自定义的普通任务

(2)  用户自定义定时任务

(3) 非当前Reactor线程调用Channel的各种方法(这种解决办法就是连接成功之后用一个集合将Channel维护起来,后面拿到做操作)

  例如在推送系统的业务线程里面,根据用户的标识,找到对应的channel,然后向该channel推送数据。

1.  自定义普通任务

假设我们在服务端读取到消息之后需要一个比较耗时的处理,比如:

NettyServer如下:

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.util.logging.Level;
import java.util.logging.Logger;

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        // 1. 创建bossGrou好eworkerGroup
        // bossGroup只负责连接请求,其他交给workerGroup, 两个都是无线循环
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        // 创建服务器端启动对象用于设置参数
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 使用链式编程设置参数
        bootstrap.group(bossGroup, workerGroup)// 设置两个组
                .channel(NioServerSocketChannel.class) // 设置服务器的通道
                .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() { // 设置通道测试对象(匿名对象)
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        // 给pipeline添加一个handler
                        socketChannel.pipeline().addLast(new ServerHandler());
                    }
                });

        System.out.println("服务端is ok。。。");

        // 启动服务器并绑定端口。绑定一个端口并且同步,生成一个ChannelFuture对象
        ChannelFuture channelFuture = bootstrap.bind(6666).sync();
        // 对关闭通道进行监控
        channelFuture.channel().closeFuture().sync();
    }
}

ServerHandler:

package netty;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 自定义服务器端处理handler,需要继承netty定义的ChannelInboundHandlerAdapter 类
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);

    /**
     * 读取事件
     *
     * @param ctx 上下文对象,含有pipeline管道;通道channel;地址address等信息
     * @param msg 客户端发送的数据(实际类型是ByteBuf - netty 封装的ByteBuffer)
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("当前线程: " + Thread.currentThread().getName());
        // 强转为netty的ByteBuffer(实际就是包装的ByteBuffer)
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端发送的消息是:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("客户端地址:" + ctx.channel().remoteAddress());

        // 比如这里我们将一个特别耗时的任务转为异步执行(也就是任务提交到NioEventLoop的taskQueue中)
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!0!", CharsetUtil.UTF_8));
        Thread.sleep(10 * 1000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!1!", CharsetUtil.UTF_8));
    }

    /**
     * 数据读取完毕事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 将数据写到客户端(write + flush)
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!", CharsetUtil.UTF_8));
    }

    /**
     * 发生异常事件
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

读取数据里面有个Sleep(10s),模拟处理耗时10s。

Client:

package netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NettyClient {

    public static void main(String[] args) throws InterruptedException {
        // 创建一个事件循环组
        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            // 创建一个启动Bootstrap(注意是Netty包下的)
            Bootstrap bootstrap = new Bootstrap();
            // 链式设置参数
            bootstrap.group(eventExecutors) // 设置线程组
                    .channel(NioSocketChannel.class) // 设置通道class
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new ClientHandler());
                        }
                    });
            System.out.println("客户端is ok...");

            // 启动客户端连接服务器(ChannelFuture 是netty的异步模型)
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
            // 监听关闭通道
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 关闭
            eventExecutors.shutdownGracefully();
        }
    }
}

客户端处理类如下:

package netty;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 自定义服务器端处理handler,需要继承netty定义的ChannelInboundHandlerAdapter 类
 */
public class ClientHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(ClientHandler.class);

    /**
     * 通道就绪事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("ClientHandler ctx: " + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 服务器!", CharsetUtil.UTF_8));
    }

    /**
     * 读取事件
     *
     * @param ctx 上下文对象,含有pipeline管道;通道channel;地址address等信息
     * @param msg 客户端发送的数据(实际类型是ByteBuf - netty 封装的ByteBuffer)
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 强转为netty的ByteBuffer(实际就是包装的ByteBuffer)
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("服务器会送的消息是:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("服务器地址:" + ctx.channel().remoteAddress());
    }

    /**
     * 发生异常事件
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

连接成功之后发送一条消息,然后接收服务器端回传的消息

测试结果如下:

客户端is ok...
2021-03-03 22:31:06.510 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:27 - ClientHandler ctx: ChannelHandlerContext(ClientHandler#0, [id: 0x197a62b1, L:/127.0.0.1:64388 - R:/127.0.0.1:6666])
2021-03-03 22:31:06.857 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!0!
2021-03-03 22:31:06.858 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:31:16.855 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!1!
2021-03-03 22:31:16.856 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:31:16.858 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!
2021-03-03 22:31:16.859 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666

可以看到服务器端的逻辑:

(1) channelRead 处理,中间有个10s休眠

(2) channelReadComplete处理

解决:

1. 将channelRead 耗时任务交给NioEventLoop的taskQueue

修改ServerHandler:

package netty;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 自定义服务器端处理handler,需要继承netty定义的ChannelInboundHandlerAdapter 类
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);

    /**
     * 读取事件
     *
     * @param ctx 上下文对象,含有pipeline管道;通道channel;地址address等信息
     * @param msg 客户端发送的数据(实际类型是ByteBuf - netty 封装的ByteBuffer)
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("当前线程: " + Thread.currentThread().getName());
        // 强转为netty的ByteBuffer(实际就是包装的ByteBuffer)
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端发送的消息是:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("客户端地址:" + ctx.channel().remoteAddress());

        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                // 比如这里我们将一个特别耗时的任务转为异步执行(也就是任务提交到NioEventLoop的taskQueue中)
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!0!", CharsetUtil.UTF_8));
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!1!", CharsetUtil.UTF_8));
            }
        });
    }

    /**
     * 数据读取完毕事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 将数据写到客户端(write + flush)
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!", CharsetUtil.UTF_8));
    }

    /**
     * 发生异常事件
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

测试查看客户端日志:(可以看到channelReadComplete 没有被阻塞)

客户端is ok...
2021-03-03 22:39:25.664 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:27 - ClientHandler ctx: ChannelHandlerContext(ClientHandler#0, [id: 0x6958d27f, L:/127.0.0.1:64860 - R:/127.0.0.1:6666])
2021-03-03 22:39:25.858 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!hello, 客户端!0!
2021-03-03 22:39:25.859 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:39:35.859 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!1!
2021-03-03 22:39:35.860 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666

 2. 我们将两个任务提交到taskQueue中

package netty;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 自定义服务器端处理handler,需要继承netty定义的ChannelInboundHandlerAdapter 类
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);

    /**
     * 读取事件
     *
     * @param ctx 上下文对象,含有pipeline管道;通道channel;地址address等信息
     * @param msg 客户端发送的数据(实际类型是ByteBuf - netty 封装的ByteBuffer)
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("当前线程: " + Thread.currentThread().getName());
        // 强转为netty的ByteBuffer(实际就是包装的ByteBuffer)
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端发送的消息是:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("客户端地址:" + ctx.channel().remoteAddress());

        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                // 比如这里我们将一个特别耗时的任务转为异步执行(也就是任务提交到NioEventLoop的taskQueue中)
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!0!", CharsetUtil.UTF_8));
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!1!", CharsetUtil.UTF_8));
            }
        });

        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                // 比如这里我们将一个特别耗时的任务转为异步执行(也就是任务提交到NioEventLoop的taskQueue中)
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!2!", CharsetUtil.UTF_8));
                try {
                    Thread.sleep(20 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!3!", CharsetUtil.UTF_8));
            }
        });
    }

    /**
     * 数据读取完毕事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 将数据写到客户端(write + flush)
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!", CharsetUtil.UTF_8));
    }

    /**
     * 发生异常事件
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

启动查看客户端日志: 可以看到第二个任务是在第一个任务完了的基础上才开始。 也就是后面跑任务的只有一个线程,taskQueue可以存放多个任务。

客户端is ok...
2021-03-03 22:47:04.249 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:27 - ClientHandler ctx: ChannelHandlerContext(ClientHandler#0, [id: 0xb9c4bc2d, L:/127.0.0.1:65343 - R:/127.0.0.1:6666])
2021-03-03 22:47:04.430 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!hello, 客户端!0!
2021-03-03 22:47:04.431 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:47:14.433 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!1!
2021-03-03 22:47:14.434 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:47:14.436 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!2!
2021-03-03 22:47:14.437 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:47:34.435 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!3!
2021-03-03 22:47:34.436 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666

debug 查看taskQueue有两个任务

 2. 自定义定时任务

1. 定义一次性任务

package netty;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * 自定义服务器端处理handler,需要继承netty定义的ChannelInboundHandlerAdapter 类
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);

    /**
     * 读取事件
     *
     * @param ctx 上下文对象,含有pipeline管道;通道channel;地址address等信息
     * @param msg 客户端发送的数据(实际类型是ByteBuf - netty 封装的ByteBuffer)
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("当前线程: " + Thread.currentThread().getName());
        // 强转为netty的ByteBuffer(实际就是包装的ByteBuffer)
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端发送的消息是:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("客户端地址:" + ctx.channel().remoteAddress());

        // 比如这里我们将一个特别耗时的任务转为异步执行(也就是任务提交到NioEventLoop的scheduleTaskQueue中)
        ctx.channel().eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!0!", CharsetUtil.UTF_8));
            }
        }, 5, TimeUnit.SECONDS);
    }

    /**
     * 数据读取完毕事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 将数据写到客户端(write + flush)
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!", CharsetUtil.UTF_8));
    }

    /**
     * 发生异常事件
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

客户端结果:

客户端is ok...
2021-03-03 22:54:41.197 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:27 - ClientHandler ctx: ChannelHandlerContext(ClientHandler#0, [id: 0xb887b7d2, L:/127.0.0.1:49453 - R:/127.0.0.1:6666])
2021-03-03 22:54:44.302 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!
2021-03-03 22:54:44.303 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666
2021-03-03 22:54:46.513 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:42 - 服务器会送的消息是:hello, 客户端!0!
2021-03-03 22:54:46.514 | APP_NAME_IS_UNDEFINED - INFO | nioEventLoopGroup-2-1 | netty.ClientHandler | line:43 - 服务器地址:/127.0.0.1:6666

2. 定义周期任务

package netty;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * 自定义服务器端处理handler,需要继承netty定义的ChannelInboundHandlerAdapter 类
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);

    /**
     * 读取事件
     *
     * @param ctx 上下文对象,含有pipeline管道;通道channel;地址address等信息
     * @param msg 客户端发送的数据(实际类型是ByteBuf - netty 封装的ByteBuffer)
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("当前线程: " + Thread.currentThread().getName());
        // 强转为netty的ByteBuffer(实际就是包装的ByteBuffer)
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端发送的消息是:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("客户端地址:" + ctx.channel().remoteAddress());

        // 比如这里我们将一个特别耗时的任务转为异步执行(也就是任务提交到NioEventLoop的scheduleTaskQueue中)
        ctx.channel().eventLoop().scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!0!", CharsetUtil.UTF_8));
            }
        }, 5, 5, TimeUnit.SECONDS);
    }

    /**
     * 数据读取完毕事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 将数据写到客户端(write + flush)
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端!", CharsetUtil.UTF_8));
    }

    /**
     * 发生异常事件
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

2. Futture 异步模型

  Netty中的I/O是异步的,包括Bind、Write、Connect等操作会简单的返回一个ChannelFuture。Netty的异步模型是建立在future和fallback基础上的。callback是回调;future的核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回显然不合适。那么可以在调用fun的时候,立马返回一个future,后续通过future去监控方法的处理过程(即: future-listener机制)

package io.netty.channel;

import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public interface ChannelFuture extends Future<Void> {
    Channel channel();

    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);

    ChannelFuture addListeners(GenericFutureListener... var1);

    ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> var1);

    ChannelFuture removeListeners(GenericFutureListener... var1);

    ChannelFuture sync() throws InterruptedException;

    ChannelFuture syncUninterruptibly();

    ChannelFuture await() throws InterruptedException;

    ChannelFuture awaitUninterruptibly();

    boolean isVoid();
}

Future接口如下:

package io.netty.util.concurrent;

import java.util.concurrent.TimeUnit;

public interface Future<V> extends java.util.concurrent.Future<V> {
    boolean isSuccess();

    boolean isCancellable();

    Throwable cause();

    Future<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);

    Future<V> addListeners(GenericFutureListener... var1);

    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);

    Future<V> removeListeners(GenericFutureListener... var1);

    Future<V> sync() throws InterruptedException;

    Future<V> syncUninterruptibly();

    Future<V> await() throws InterruptedException;

    Future<V> awaitUninterruptibly();

    boolean await(long var1, TimeUnit var3) throws InterruptedException;

    boolean await(long var1) throws InterruptedException;

    boolean awaitUninterruptibly(long var1, TimeUnit var3);

    boolean awaitUninterruptibly(long var1);

    V getNow();

    boolean cancel(boolean var1);
}

工作原理图如下:

  在使用netty进行编程时,拦截操作和转换出入栈数据只需要提供callback或者使用future。这使得链式操作简单、高效,有利用编写可重用的、通用的代码。

Future-Listener 机制如下:

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

2. 主要方法如下:

isDone 判断当前操作是否完成

isSuccess 判断当前操作是否成功执行

getCause 获取当前操作失败的原因

isCanclled 判断当前已完成的操作是否被取消

例如:

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        // 1. 创建bossGrou好eworkerGroup
        // bossGroup只负责连接请求,其他交给workerGroup, 两个都是无线循环
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        // 创建服务器端启动对象用于设置参数
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 使用链式编程设置参数
        bootstrap.group(bossGroup, workerGroup)// 设置两个组
                .channel(NioServerSocketChannel.class) // 设置服务器的通道
                .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() { // 设置通道测试对象(匿名对象)
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        // 给pipeline添加一个handler
                        socketChannel.pipeline().addLast(new ServerHandler());
                    }
                });

        System.out.println("服务端is ok。。。 异步绑定端口");

        // 启动服务器并绑定端口。绑定一个端口并且同步,生成一个ChannelFuture对象
        ChannelFuture channelFuture = bootstrap.bind(6666).sync();
        // 绑定端口是异步操作,添加监听器进行处理
        channelFuture.addListener(future -> {
            // isDone 方法来判断当前操作是否完成
            System.out.println(future.isDone());
            // isSuccess 方法来判断已完成的当前操作是否成功;
            System.out.println(future.isSuccess());
            // isCancelled 方法来判断已完成的当前操作是否被取消;
            System.out.println(future.isCancelled());
            if (future.isSuccess()) {
                System.out.println("绑定成功");
            } else {
                System.err.println("6666 端口绑定失败");
            }
        });

        // 对关闭通道进行监控
        channelFuture.channel().closeFuture().sync();
    }
}

结果:

服务端is ok。。。 异步绑定端口
true
true
false
绑定成功
【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
原文地址:https://www.cnblogs.com/qlqwjy/p/14477603.html