乐趣区

关于netty:netty系列之文本聊天室

简介

通过之前的系列文章,咱们曾经晓得了 netty 的运行原理,还介绍了根本的 netty 服务搭建流程和音讯处理器的写法。明天本文会给大家介绍一个更加简单的例子,文本聊天室。

聊天室的工作流程

明天要介绍的是文本聊天室,对于文本聊天室来说,首先须要建设一个服务器,用于解决各个客户端的连贯,对于客户端来说,须要建设和服务器的连贯,而后向服务器输出聊天信息。服务器收到聊天信息之后,会对音讯进行响应,并将音讯返回至客户端,这样一个聊天室的流程就实现了。

文本处理器

之前的文章中,咱们有提到过,netty 的传输只反对 ByteBuf 类型,对于聊天室间接输出的字符串是不反对的,须要对字符串进行 encode 和 decode 转换。

之前咱们介绍的 encode 和 decode 的类叫做 ObjectDecoder 和 ObjectEncoder。明天咱们再介绍两个专门解决字符串的 StringDecoder 和 StringEncoder。

StringEncoder 要比 ObjectEncoder 简略很多,因为对于对象来说,咱们还须要在 Byte 数组的头部设置 Byte 数组的大小,从而保障对象所有数据读取正确。对于 String 来说,就比较简单了,只须要保障一次读入的数据都是字符串即可。

StringEncoder 继承自 MessageToMessageEncoder,其外围的 encode 代码如下:

    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {if (msg.length() == 0) {return;}

        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));
    }

从下面的代码能够看出,外围实际上是调用了 ByteBufUtil.encodeString 办法,将 String 转换成了 ByteBuf。

对于字符串编码来说,还须要界定一个编码的范畴,比方咱们须要晓得须要一次编码多少字符串,一般来说咱们通过回车符来界定一次字符串输出的完结。

netty 也提供了这样的十分便当的类叫做 DelimiterBasedFrameDecoder,通过传入不同的 Delimiter, 咱们能够将输出拆分成不同的 Frame, 从而对一行字符串进行解决。

new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))

我再看一下 StringDecoder 的外围代码,StringDecoder 继承自 MessageToMessageDecoder:

    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {out.add(msg.toString(charset));
    }

通过调用 ByteBuf 的 toString 办法,将 BuyteBuf 转换成为字符串,并且输入到 channel 中。

初始化 ChannelHandler

在 initChannel 的时候,咱们须要向 ChannelPipeline 中增加无效的 Handler。对于本例来说,须要增加 StringDecoder、StringEncoder、DelimiterBasedFrameDecoder 和真正解决音讯的自定义 handler。

咱们将初始化 Pipeline 的操作都放在一个新的 ChatServerInitializer 类中,这个类继承自 ChannelInitializer,其外围的 initChannel 办法如下:

    public void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();
        // 增加行分割器
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        // 增加 String Decoder 和 String Encoder, 用来进行字符串的转换
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);
        // 最初增加真正的处理器
        pipeline.addLast(SERVER_HANDLER);
    }

ChatServerInitializer 在 Bootstrap 中的 childHandler 中进行增加:

childHandler(new ChatServerInitializer())

真正的音讯解决逻辑

有了下面的逻辑之后,咱们最初只须要专一于真正的音讯解决逻辑即可。

这里咱们的逻辑是当客户端输出“再见”的时候,就敞开 channel,否则就将音讯回写给客户端。

其外围逻辑如下:

 public void channelRead0(ChannelHandlerContext ctx, String request) throws Exception {
        // 如果读取到 "再见" 就敞开 channel
        String response;
        // 判断是否敞开
        boolean close = false;
        if (request.isEmpty()) {response = "你说啥?\r\n";} else if ("再见".equalsIgnoreCase(request)) {
            response = "再见, 我的敌人!\r\n";
            close = true;
        } else {response = "你是不是说:'" + request + "'?\r\n";}

        // 写入音讯
        ChannelFuture future = ctx.write(response);
        // 增加 CLOSE listener,用来敞开 channel
        if (close) {future.addListener(ChannelFutureListener.CLOSE);
        }
    }

通过判断客户端的出入,来设置是否敞开按钮,这里的敞开 channel 是通过向 ChannelFuture 中增加 ChannelFutureListener.CLOSE 来实现的。

ChannelFutureListener.CLOSE 是一个 ChannelFutureListener,它会在 channel 执行结束之后敞开 channel,事实上这是一个十分优雅的敞开形式。

    ChannelFutureListener CLOSE = new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) {future.channel().close();}
    };

对于客户端来说,其外围就是从命令行读取输出,这里应用 InputStreamReader 接管命令行输出,并应用 BufferedReader 对其缓存。

而后将命令行输出通过调用 ch.writeAndFlush 写入到 channel 中,最初监听命令行输出,如果监听到“再见“,则期待 server 端敞开 channel,其外围代码如下。

// 从命令行输出
            ChannelFuture lastWriteFuture = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {String line = in.readLine();
                if (line == null) {break;}
                // 将从命令行输出的一行字符写到 channel 中
                lastWriteFuture = ch.writeAndFlush(line + "\r\n");
                // 如果输出 '再见',则期待 server 端敞开 channel
                if ("再见".equalsIgnoreCase(line)) {ch.closeFuture().sync();
                    break;
                }
            }

            // 期待所有的音讯都写入 channel 中
            if (lastWriteFuture != null) {lastWriteFuture.sync();
            }

总结

通过下面的介绍,一个简略的聊天室就建成了。后续咱们会持续摸索更加简单的利用,心愿大家可能喜爱。

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/10-netty-chat/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版