Netty学习 应用Demo之“自动回复”聊天业务

Netty实现自动回复步骤

主要分成五步

1、创建EventLoopGroup实现循环组 管理EventLoop线程

2、创建Bootstrap ,Bootstrap对于服务端而言,先后设置其中的线程组group、通道channel、处理器handler、客户端通道对应的处理器childHandler

3、自定义服务器接收和响应客户端处理器和初始化器

4、返回Future对象,通过Future来获取操作的结果,比如:将关闭的通道也设置成异步的

5、优雅的关闭事件循环组

这里我说明一下我觉得很重要 ChannelHandlerContext通道处理器上下文 、ChannelPipeline 管道、Hander处理器的关系,下面的一行代码可以说明

pipeline.addLast(new XXXHandler());其实pipeline添加的是ChannelHandlerContext 包裹的Hander 就像这样 new ChannelHandlerContext (new XXXHandler()) ,这个对象添加到了pipeline

1、服务端代码

创建MyChatServer 类作为服务端

java 复制代码
public class MyChatServer {
    public static void main(String[] args) {
        // 创建Reactor
        // 用来管理channel 监听事件 ,是无限循环的事件组(线程池) 可以设置线程数,cup核 * num
        EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerLoopGroup = new NioEventLoopGroup();
        // 服务端的启动对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 设置相关参数  这是一个链式编程
        serverBootstrap.group(bossLoopGroup,workerLoopGroup)
                // 声明通道类型
                .channel(NioServerSocketChannel.class)
                // 设置处理器  我这里设置了netty提供的Handler 处理器
                .handler(new LoggingHandler(LogLevel.INFO))
                // 当连接被阻塞 ,SO_BACKLOG 阻塞队列的长度
                .option(ChannelOption.SO_BACKLOG,128)
                // 设置连接保持活跃的状态
                .childOption(ChannelOption.SO_KEEPALIVE,true)
                .childHandler(new MyChatServerInitializer());

        System.out.println("服务端初始化完成");

        // 启动需要设置端口  还需要设置是异步启动
        try {
            // 设置异步的future
            ChannelFuture future = serverBootstrap.bind(8899).sync();


            // 将关闭的通道也设置成异步的
            // 阻塞finally 中的代码
            future.channel().closeFuture().sync();


        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 优雅关闭
            bossLoopGroup.shutdownGracefully();
            workerLoopGroup.shutdownGracefully();
        }
    }
}

1.1 自定义初始化器

下面是自定义初始化器的代码,自定义handler的设置,需要先有通道初始化器ChannelInitializer,实现其中的通道初始化方法,具体逻辑为 获取通道中的管道,然后加入handler


ChannelInitializer继承关系图

java 复制代码
public class MyChatServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 创建管道
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 先设置解码器 后设置编码器
        // 基于lineDelimiter 分隔符的一种解码器
        pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

        // 设置处理器  pipeline 管理的是由handlerContext包裹的handle   add[handlerContext(hander)]
        // pipeline 是双向链表 headContext头节点   tailContext尾节点
        pipeline.addLast(new MyChatServerHandler());
    }
}

1.2 自定义handler处理器

handler的逻辑如下

a) 继承SimpleChannelInboundHandler,还可以设置泛型对应需要处理的msg的类型是除继承适配器之外自定义处理器的另一种方法。

b) 重写其中的方法,channelActive 、channelRead、channelReadComplete,分别对应于通道创建、读事件发生、读事件完成三个时间点。

c) 方法的参数有一个 ChannelHandlerContext ,是处理器的上下文,除了获取通道和管道外,可以调用writeAndFlush() 直接写入数据。

java 复制代码
/**
 * 自定义处理器的另一种方法
 * SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter的子类
 */
public class MyChatServerHandler extends SimpleChannelInboundHandler {

    // 当多个通道传入handler , 使用通道组的管理方法
    // GlobalEventExecutor 全局事件执行器
    //INSTANCE 代表的是单例
    private  static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);


    /**
     * channelRead0 处理读取数据逻辑的方法
     *
     * @param ctx  通道处理器上下文  它是连接pipeline 和 handler 中间角色
     * @param msg
     * @throws Exception
     *
     * 读取数据, 并广播给其它客户端
     * 步骤:
     * 接收自身的Channel
     * 这个方法可以帮我们自动释放ByteBuf的内存空间
     */
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("[客户端]"+msg);
        Channel selfChannel = ctx.channel();
        //提前处理一些消息
//        ByteBuf buf = (ByteBuf)msg;
        // 迭代channel通道组
        Iterator<Channel> it = channelGroup.iterator();
        while (it.hasNext()){
            Channel ch = it.next();
            if(selfChannel != ch){ // 如果这个通道不是自身通道 则
                ch.writeAndFlush("[服务器] - "+selfChannel.remoteAddress()+"发送的消息:"+msg);
                continue;
            }

            // 如果是自身的通道 【客户端发给服务端,服务端回应客户端的消息】
            String answer ;
            // 如果是发送消息的通道
            if (((String)msg).length() == 0 ){
                answer = "期待你说点什么\r\n";
            }else {
                answer = "你说的是这句话嘛:" + msg + "?\r\n";
            }
            ch.writeAndFlush(answer);
        }



    }

    /**
     *  连接成功, 此时通道是活跃的时候触发
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LocalDate today = LocalDate.now();
        String dateStr = today.toString(); // 默认格式为 "yyyy-MM-dd"
        ctx.writeAndFlush("Welcome to server-- now :"+dateStr+"\r\n");
    }

    /**
     *  通道不活跃 ,用于处理用户下线的逻辑
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()+"下线了\r\n");
    }

    /**
     *
     * @param ctx 通道处理器上下文
     * @throws Exception
     * 连接刚刚建立时 ,第一个被执行的方法,
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

        System.out.println("[服务端地址]:"+ctx.channel().remoteAddress()+"连接成功\r\n");
        // 添加到通道组中管理
        channelGroup.add(ctx.channel());
    }

    /**
     *
     * @param ctx  通道处理器上下文
     * @throws Exception
     * 当连接断开 最后执行的方法
     * 连接断开时 , channel 会自动从 通道组中移除
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[服务端地址]:"+ctx.channel().remoteAddress()+"断开连接\r\n");
    }

    /**
     *  通用异常处理类
     * @param ctx 通道处理器上下文
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 关闭
        ctx.close();
    }
}

2、客户端代码

代码和服务端代码变化不大。直接上代码看看

不同的是,客户端事件循环组需要一个,同样也需要自定义初始化器和自定义处理器

java 复制代码
public class MyChatClient {
    public static void main(String[] args) {
        // 客户端事件循环组 只需要一个
        EventLoopGroup group = new NioEventLoopGroup();
        // 客户端启动器
        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                // 自定义通道初始化器
                .handler(new MyChatClientInitializer());

        System.out.println("客户端初始化完成了");
        try {
            // 设置异步的future  需要连接的服务端参数
            ChannelFuture future = bootstrap.connect("127.0.0.1", 8899).sync();

            // 将关闭的通道也设置成异步的
            // 阻塞finally 中的代码
//            future.channel().closeFuture().sync();
            // 因为 本次业务是可以多次发送消息的业务, 所以上面的阻塞关闭 需要注释掉
            // 通过键盘输入发送的数据  BufferedReader 来实现
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {
                String msg = br.readLine();
                // 发送到通道之中
                future.channel().writeAndFlush(msg+"\r\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }
    }
}

2.1 自定义初始化器

这个部分引入了Netty 编解码器

DelimiterBasedFrameDecoder 分隔符的一种解码器

StringEncoder / StringDecoder 对字符串处理

ObjectEncoder / ObjectDecoder 对java对象处理

最后还需要一个自定义的处理器MyChatClientHandler 后面有代码

java 复制代码
public class MyChatClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 先设置解码器 后设置编码器
        // 基于lineDelimiter 分隔符的一种解码器
        pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

        // 添加处理器
        pipeline.addLast(new MyChatClientHandler());
    }
}

2.2 自定义处理器

java 复制代码
/**
 * SimpleChannelInboundHandler
 *  客户端需要添加 msg 泛型
 */
public class MyChatClientHandler extends SimpleChannelInboundHandler<String> {
    /**
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 接收服务的消息
        System.out.println("[服务端]:"+msg);
    }
}

最后展示

业务:自动回应客户端是否说的是msg ,如果直接回车回应"期待你说点什么"

程序运行结果

结语:希望可以给@你带来一点点帮助,资料来源于网上学习视频总结。

相关推荐
郑州吴彦祖77213 分钟前
【java】数据类型与变量以及操作符
java·intellij-idea
程序员大金14 分钟前
基于SpringBoot+Vue+MySQL的在线学习交流平台
java·vue.js·spring boot·后端·学习·mysql·intellij-idea
吹老师个人app编程教学19 分钟前
阿里巴巴_java开发规范手册详解
java·开发语言
天上掉下来个程小白19 分钟前
Stream流的终结方法(一)
java·windows
天上掉下来个程小白41 分钟前
请求响应-08.响应-案例
java·服务器·前端·springboot
大白_dev42 分钟前
数据校验的总结
java·开发语言
失落的香蕉1 小时前
Java第二阶段---10方法带参---第三节 面向对象和面向过程的区别
java·开发语言
哎呀呀嗯呀呀1 小时前
class 031 位运算的骚操作
java·算法·位运算
2401_858120531 小时前
古典舞在线交流平台:SpringBoot设计与实现详解
java·spring boot·后端
大白飞飞1 小时前
IDEA创建、导入、删除maven项目
java·maven·intellij-idea