netty无缝切换rabbitmq、activemq、rocketmq实现聊天室单聊、群聊功能


netty的pipeline处理链上的handler:需要IdleStateHandler心跳检测channel是否有效,以及处理登录认证的UserAuthHandler和消息处理MessageHandler
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(defLoopGroup,
//编码解码器
new HttpServerCodec(),
//将多个消息转换成单一的消息对象
new HttpObjectAggregator(65536),
//支持异步发送大的码流,一般用于发送文件流
new ChunkedWriteHandler(),
//检测链路是否读空闲,配合心跳handler检测channel是否正常
new IdleStateHandler(60, 0, 0),
//处理握手和认证
new UserAuthHandler(),
//处理消息的发送
new MessageHandler()
);
}
对于所有连进来的channel,我们需要保存起来,往后的群发消息需要依靠这些channel
public static void addChannel(Channel channel) {
String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);
System.out.println("addChannel:" + remoteAddr);
if (!channel.isActive()) {
logger.error("channel is not active, address: {}", remoteAddr);
}
UserInfo userInfo = new UserInfo();
userInfo.setAddr(remoteAddr);
userInfo.setChannel(channel);
userInfo.setTime(System.currentTimeMillis());
userInfos.put(channel, userInfo);
}
登录后,channel就变成有效的channel,无效的channel之后将会丢弃
public static boolean saveUser(Channel channel, String nick, String password) {
UserInfo userInfo = userInfos.get(channel);
if (userInfo == null) {
return false;
}
if (!channel.isActive()) {
logger.error("channel is not active, address: {}, nick: {}", userInfo.getAddr(), nick);
return false;
}
// 验证用户名和密码
if (nick == null || password == null) {
return false;
}
LambdaQueryWrapper<Account> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Account::getUsername, nick).eq(Account::getPassword, password);
Account account = accountMapperStatic.selectOne(lambdaQueryWrapper);
if (account == null) {
return false;
}
// 增加一个认证用户
userCount.incrementAndGet();
userInfo.setNick(nick);
userInfo.setAuth(true);
userInfo.setId(account.getId());
userInfo.setUsername(account.getUsername());
userInfo.setGroupNumber(account.getGroupNumber());
userInfo.setTime(System.currentTimeMillis());
// 注册该用户推送消息的通道
offlineInfoTransmitStatic.registerPull(channel);
return true;
}
当channel关闭时,就不再接收消息。unregisterPull就是注销信息消费者,客户端不再接取聊天消息。此外,从下方有一个加写锁的操作,就是为了避免channel还在发送消息时,这边突然关闭channel,这样会导致报错。
public static void removeChannel(Channel channel) {
try {
logger.warn("channel will be remove, address is :{}", NettyUtil.parseChannelRemoteAddr(channel));
//加上读写锁保证移除channel时,避免channel关闭时,还有别的线程对其操作,造成错误
rwLock.writeLock().lock();
channel.close();
UserInfo userInfo = userInfos.get(channel);
if (userInfo != null) {
if (userInfo.isAuth()) {
offlineInfoTransmitStatic.unregisterPull(channel);
// 减去一个认证用户
userCount.decrementAndGet();
}
userInfos.remove(channel);
}
} finally {
rwLock.writeLock().unlock();
}
}
为了无缝切换使用rabbitmq、rocketmq、activemq、不使用中间件存储和转发聊天消息这4种状态,定义如下4个接口。依次是发送单聊消息、群聊消息、客户端启动接收消息、客户端下线不接收消息。
public interface OfflineInfoTransmit {
void pushP2P(Integer userId, String message);
void pushGroup(String groupNumber, String message);
void registerPull(Channel channel);
void unregisterPull(Channel channel);
}
其中,如何使用rabbitmq、rocketmq、activemq三种中间件中的一种来存储和转发聊天消息,它的处理流程如下:
- 单聊的模型参考线程池的模型,如果用户在线,直接通过channel发送给用户。如果用户离线,则发往中间件存储,下次用户上线时直接从中间件拉取消息。这样做对比所有消息的发送都通过中间件来转的好处是提升了性能
- 群聊则是完全通过中间件来转发消息,消息发送中间件,客户端从中间件接取消息。如果仍像单聊那样操作,在线用户直接通过channel发送,操作过于繁琐,要判断这个群组的哪些用户是否在线
- 如果用户在线就注册消费者,从中间件接取消息。否则,就断开消费者,消息保留在中间件中,以便客户端下次上线时拉取。这样就实现了离线消息的接收。
- 不管使用哪种中间件或使用不使用中间件,它的处理流程都遵循上面的3个要求,就能无缝切换上方的4种方法来存储和转发消息。需要哪种方法开启相应注解即可。

项目地址:https://github.com/shuangyueliao/netty-chat
netty无缝切换rabbitmq、activemq、rocketmq实现聊天室单聊、群聊功能的更多相关文章
- Netty入门(二)之PC聊天室
参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...
- Netty入门(一)之webSocket聊天室
一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...
- 一键QQ聊天与一键加群QQ功能
最新有项目要求,点击页面上的一个按钮,实现直接启动QQ聊天,添加QQ群的功能. 开始以为会很复杂,百度后发现QQ已经有考虑到这方面的需求,只需进入:QQ推广 -> 推广工具 就能看到如下界面
- Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)
一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...
- 教你如何把openfire的muc聊天室改造为群
openfire群聊与QQ群对比 应该是去年的时候开始接触openfire,当时在分析后发现基于xmpp协议的openfire已经具备了群聊的功能.也就没太当回事,觉得加点功能就可以做成类似于QQ群的 ...
- 网络编程(学习整理)---2--(Udp)实现简单的控制台聊天室
1.UDP协议: 总结一下,今天学习的一点知识点! UDP也是一种通信协议,常被用来与TCP协议作比较!我们知道,在发送数据包的时候使用TCP协议比UDP协议安全,那么到底安全在哪里呢?怎么理解呢! ...
- golang简易版聊天室
功能需求: 创建一个聊天室,实现群聊和单聊的功能,直接输入为群聊,@某人后输入为单聊 效果图: 群聊: 单聊: 服务端: package main import ( "fmt" ...
- IM即时通讯:如何跳出传统思维来设计聊天室架构?
因为视频直播业务的大规模扩张,聊天室这种功能在最近几年又火了起来.本篇文章将会重点挑选聊天室这个典型场景,和大家分享一下网易云信在实现这个功能时是如何做架构设计的. 相关推荐阅读几十万人同时在线的直播 ...
- IO、NIO实现简单聊天室,附带问题解析
本篇文章主要使用IO和NIO的形式来实现一个简单的聊天室,并且说明IO方法存在的问题,而NIO又是如何解决的. 大概的框架为,先提供思路和大概框架图--代码--问题及解决方式,这样会容易看一点 ...
随机推荐
- ieda使用 在jsp页面中,有时候会出现不能智能显示方法 idea pageContext.setAttribute
idea使用,出现问题记录: 就比如在 pageContext.setAttribute("user",u);这句打pageContext会智能提示, 但是后面的setAttrib ...
- c语言进阶10-算法
一. 数据结构和算法关系 为什么要学数据结构和算法? 通常,计算机解决问题的步骤如下: 在数学模型中,计算机处理的对象之间通常存在着一种最简单的线性关系,这类数学模型就是线性的数据结构.著名计算机科 ...
- python的socket模块
sk.bind(address) s.bind(address) 将套接字绑定到地址.address地址的格式取决于地址族.在AF_INET下,以元组(host,port)的形式表示地址. sk.li ...
- GCD和扩展GCD
gcd(a, b)用于求解自然数a,b的最大公约数 int gcd(int a, int b) { ) return a; return gcd(b, a%b); } extgcd(a, b, x, ...
- spring与mybatis整合(扫描Mapper接口)
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" ...
- Python基础总结之第十天开始【认识一下python的另一个数据对象-----字典】(新手可相互督促)
看了大家的评论,还是有意外的收货.感谢每个小伙伴的评论与补充. 众人拾柴火焰高~ 今天的笔记是记录python中的数据对象----字典! 前面有讲到list列表和tuple元组的笔记,他们都是一样可以 ...
- codeforces 340 A. The Wall
水水的一道题,只需要找xy的最小公倍数,然后找a b区间有多少个可以被xy的最小公倍数整除的数,就是答案. //============================================ ...
- Spring的依赖注入和管理Bean
采用Spring管理Bean和依赖注入 1.实例化spring容器 和 从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] Ap ...
- Cassandra之Docker环境实践
Cassandra简介 Cassandra是一个开源分布式NoSQL数据库系统. 它最初由Facebook开发,用于储存收件箱等简单格式数据,集GoogleBigTable的数据模型与Amazon D ...
- 图像反转(一些基本的灰度变换函数)基本原理及Python实现
1. 基本原理 获取像素值在[0, L]范围内的图像的反转图像,即为负片.适用于增强图像中白色或者灰色的区域,尤其当黑色在图片中占主地位时候 $$T(r) = L-r$$ 2. 运行结果 图源自ski ...