简介
通过之前的系列文章,咱们曾经晓得了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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!