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/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
netty系列之:文本聊天室的更多相关文章
- Netty之多用户的聊天室(三)
Netty之多用户的聊天室(三) 一.简单说明 笔者有意将Netty做成一个系列的文章,因为笔者并不是一个善于写文章的人,而且笔者学习很多技术一贯的习惯就是敲代码,很多东西敲着敲着就就熟了,然后再进行 ...
- 用SignalR 2.0开发客服系统[系列2:实现聊天室]
前言 交流群:195866844 上周发表了 用SignalR 2.0开发客服系统[系列1:实现群发通讯] 这篇文章,得到了很多帮助和鼓励,小弟在此真心的感谢大家的支持.. 这周继续系列2,实现聊天室 ...
- Netty高级应用及聊天室实战
Netty 高级应用 1. 编解码器 概念:在网络应用中,需要实现某种编解码器.将原始字节数据与自定义消息数据进行相互转换.网络中都是以字节码的形式传输的. 对Netty而言,编解码器由两部分组成:编 ...
- netty系列之:对聊天进行加密
目录 简介 PKI标准 各类证书的后缀和转换 netty中启动SSL server netty中启动SSL client 总结 简介 在之前的文章中,我们讲到了怎么使用netty建立聊天室,但是这样的 ...
- Netty学习笔记(四) 简单的聊天室功能之服务端开发
前面三个章节,我们使用了Netty实现了DISCARD丢弃服务和回复以及自定义编码解码,这篇博客,我们要用Netty实现简单的聊天室功能. Ps: 突然想起来大学里面有个课程实训,给予UDP还是TCP ...
- netty实现消息中心(二)基于netty搭建一个聊天室
前言 上篇博文(netty实现消息中心(一)思路整理 )大概说了下netty websocket消息中心的设计思路,这篇文章主要说说简化版的netty聊天室代码实现,支持群聊和点对点聊天. 此demo ...
- Netty 系列八(基于 WebSocket 的简单聊天室).
一.前言 之前写过一篇 Spring 集成 WebSocket 协议的文章 —— Spring消息之WebSocket ,所以对于 WebSocket 协议的介绍就不多说了,可以参考这篇文章.这里只做 ...
- 在线聊天室的实现(2)--基于Netty 4.x的Echo服务器实现
前言: 就如前文所讲述的, 聊天室往往是最基本的网络编程的学习案例. 本文以WebSocket为底层协议, 实现一个简单的基于web客户端的Echo服务. 服务器采用Netty 4.x来实现, 源于其 ...
- Netty入门(二)之PC聊天室
参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...
随机推荐
- nginx服务跳转
1.什么是页面跳转 将URL信息做改变 将URI信息做改变 完成伪静态配置 2.实现页面跳转的方法 http://nginx.org/en/docs/http/ngx_http_rewrite_mod ...
- 阿里P7大佬带你解密Sentinel
概述 在接连写了两篇关于限流的文章(<面试补习>- 你来说说什么是限流?, 限流神器Sentinel,不了解一下吗?)后,总感觉还差最后一点内容来闭环整个限流相关的内容,这两天在翻查相关文 ...
- docker安装mysql镜像和容器
下拉镜像 docker pull mysql/mysql-server:5.5 后面的mysql标签是版本号,是可选择的,有: 5.5 5.6 5.7 8.0 创建mysql5.5的容器 docker ...
- Flink进入大厂面试准备,收藏这一篇就够了
1. Flink 的容错机制(checkpoint) Checkpoint机制是Flink可靠性的基石,可以保证Flink集群在某个算子因为某些原因(如 异常退出)出现故障时,能够将整个应用流图的状态 ...
- esp32 Guru Meditation 错误解决方案(转)
Guru Meditation本节将对打印在 Guru Meditation Error: Core panic'ed后面括号中的致错原因进行逐一解释.IllegalInstruction此 CPU ...
- Java | Map集合
Map集合 在现实生活中,有非常多的东西,是和另外一种东西对应的,并且还是唯一的,比如:身份证号与个人,个人与手机,一夫一妻...等,这种关系就是对应关系,又叫做映射.Java为这种数据类型提供了专门 ...
- 高校表白App-团队冲刺第八天
今天要做什么 尝试连接数据库(MySQL) 做了什么 连接成功 遇到的问题 Android连接数据库可以采用JDBC连接,因为在Android开发中,大多数连接到远程MySQL数据库的方法是加入特定的 ...
- [刘阳Java]_CSS鼠标悬停
小白在学习前端技术时候,一定会在刚开始的时候学习CSS2和CSS3的相关知识.这篇内容给大家介绍一个CSS鼠标悬停的效果.大家可以先看下面的效果图,然后我们在说一下实现的效果要求吧 上图效果非常简单和 ...
- 微信小程序云开发-云存储-上传文件(图片/视频)到云存储 精简代码
说明 图片/视频这类文件是从客户端会话选择文件. 一.wxml文件添加if切换显示 <!--上传文件到云存储--> <button bindtap="chooseImg&q ...
- mysql实现主从复制、读写分离的配置方法(一)
1. 测试环境 两个CentOS7虚拟机 mysql 5.5-MariaDB master_ip:192.168.1.109 slave_ip:192.168.1.118 2. 配置主服务器 2.1 ...