netty的pipeline处理链上的handler:需要IdleStateHandler心跳检测channel是否有效,以及处理登录认证的UserAuthHandler和消息处理MessageHandler

  1. protected void initChannel(SocketChannel ch) throws Exception {
  2. ch.pipeline().addLast(defLoopGroup,
  3. //编码解码器
  4. new HttpServerCodec(),
  5. //将多个消息转换成单一的消息对象
  6. new HttpObjectAggregator(65536),
  7. //支持异步发送大的码流,一般用于发送文件流
  8. new ChunkedWriteHandler(),
  9. //检测链路是否读空闲,配合心跳handler检测channel是否正常
  10. new IdleStateHandler(60, 0, 0),
  11. //处理握手和认证
  12. new UserAuthHandler(),
  13. //处理消息的发送
  14. new MessageHandler()
  15. );
  16. }

对于所有连进来的channel,我们需要保存起来,往后的群发消息需要依靠这些channel

  1. public static void addChannel(Channel channel) {
  2. String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);
  3. System.out.println("addChannel:" + remoteAddr);
  4. if (!channel.isActive()) {
  5. logger.error("channel is not active, address: {}", remoteAddr);
  6. }
  7. UserInfo userInfo = new UserInfo();
  8. userInfo.setAddr(remoteAddr);
  9. userInfo.setChannel(channel);
  10. userInfo.setTime(System.currentTimeMillis());
  11. userInfos.put(channel, userInfo);
  12. }

登录后,channel就变成有效的channel,无效的channel之后将会丢弃

  1. public static boolean saveUser(Channel channel, String nick, String password) {
  2. UserInfo userInfo = userInfos.get(channel);
  3. if (userInfo == null) {
  4. return false;
  5. }
  6. if (!channel.isActive()) {
  7. logger.error("channel is not active, address: {}, nick: {}", userInfo.getAddr(), nick);
  8. return false;
  9. }
  10. // 验证用户名和密码
  11. if (nick == null || password == null) {
  12. return false;
  13. }
  14. LambdaQueryWrapper<Account> lambdaQueryWrapper = new LambdaQueryWrapper<>();
  15. lambdaQueryWrapper.eq(Account::getUsername, nick).eq(Account::getPassword, password);
  16. Account account = accountMapperStatic.selectOne(lambdaQueryWrapper);
  17. if (account == null) {
  18. return false;
  19. }
  20. // 增加一个认证用户
  21. userCount.incrementAndGet();
  22. userInfo.setNick(nick);
  23. userInfo.setAuth(true);
  24. userInfo.setId(account.getId());
  25. userInfo.setUsername(account.getUsername());
  26. userInfo.setGroupNumber(account.getGroupNumber());
  27. userInfo.setTime(System.currentTimeMillis());
  28. // 注册该用户推送消息的通道
  29. offlineInfoTransmitStatic.registerPull(channel);
  30. return true;
  31. }

当channel关闭时,就不再接收消息。unregisterPull就是注销信息消费者,客户端不再接取聊天消息。此外,从下方有一个加写锁的操作,就是为了避免channel还在发送消息时,这边突然关闭channel,这样会导致报错。

  1. public static void removeChannel(Channel channel) {
  2. try {
  3. logger.warn("channel will be remove, address is :{}", NettyUtil.parseChannelRemoteAddr(channel));
  4. //加上读写锁保证移除channel时,避免channel关闭时,还有别的线程对其操作,造成错误
  5. rwLock.writeLock().lock();
  6. channel.close();
  7. UserInfo userInfo = userInfos.get(channel);
  8. if (userInfo != null) {
  9. if (userInfo.isAuth()) {
  10. offlineInfoTransmitStatic.unregisterPull(channel);
  11. // 减去一个认证用户
  12. userCount.decrementAndGet();
  13. }
  14. userInfos.remove(channel);
  15. }
  16. } finally {
  17. rwLock.writeLock().unlock();
  18. }
  19. }

为了无缝切换使用rabbitmq、rocketmq、activemq、不使用中间件存储和转发聊天消息这4种状态,定义如下4个接口。依次是发送单聊消息、群聊消息、客户端启动接收消息、客户端下线不接收消息。

  1. public interface OfflineInfoTransmit {
  2. void pushP2P(Integer userId, String message);
  3. void pushGroup(String groupNumber, String message);
  4. void registerPull(Channel channel);
  5. void unregisterPull(Channel channel);
  6. }

其中,如何使用rabbitmq、rocketmq、activemq三种中间件中的一种来存储和转发聊天消息,它的处理流程如下:

  1. 单聊的模型参考线程池的模型,如果用户在线,直接通过channel发送给用户。如果用户离线,则发往中间件存储,下次用户上线时直接从中间件拉取消息。这样做对比所有消息的发送都通过中间件来转的好处是提升了性能
  2. 群聊则是完全通过中间件来转发消息,消息发送中间件,客户端从中间件接取消息。如果仍像单聊那样操作,在线用户直接通过channel发送,操作过于繁琐,要判断这个群组的哪些用户是否在线
  3. 如果用户在线就注册消费者,从中间件接取消息。否则,就断开消费者,消息保留在中间件中,以便客户端下次上线时拉取。这样就实现了离线消息的接收。
  4. 不管使用哪种中间件或使用不使用中间件,它的处理流程都遵循上面的3个要求,就能无缝切换上方的4种方法来存储和转发消息。需要哪种方法开启相应注解即可。

项目地址:https://github.com/shuangyueliao/netty-chat

netty无缝切换rabbitmq、activemq、rocketmq实现聊天室单聊、群聊功能的更多相关文章

  1. Netty入门(二)之PC聊天室

    参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...

  2. Netty入门(一)之webSocket聊天室

    一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...

  3. 一键QQ聊天与一键加群QQ功能

    最新有项目要求,点击页面上的一个按钮,实现直接启动QQ聊天,添加QQ群的功能. 开始以为会很复杂,百度后发现QQ已经有考虑到这方面的需求,只需进入:QQ推广 -> 推广工具 就能看到如下界面

  4. Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)

    一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...

  5. 教你如何把openfire的muc聊天室改造为群

    openfire群聊与QQ群对比 应该是去年的时候开始接触openfire,当时在分析后发现基于xmpp协议的openfire已经具备了群聊的功能.也就没太当回事,觉得加点功能就可以做成类似于QQ群的 ...

  6. 网络编程(学习整理)---2--(Udp)实现简单的控制台聊天室

    1.UDP协议: 总结一下,今天学习的一点知识点! UDP也是一种通信协议,常被用来与TCP协议作比较!我们知道,在发送数据包的时候使用TCP协议比UDP协议安全,那么到底安全在哪里呢?怎么理解呢! ...

  7. golang简易版聊天室

    功能需求: 创建一个聊天室,实现群聊和单聊的功能,直接输入为群聊,@某人后输入为单聊 效果图: 群聊:   单聊: 服务端: package main import ( "fmt" ...

  8. IM即时通讯:如何跳出传统思维来设计聊天室架构?

    因为视频直播业务的大规模扩张,聊天室这种功能在最近几年又火了起来.本篇文章将会重点挑选聊天室这个典型场景,和大家分享一下网易云信在实现这个功能时是如何做架构设计的. 相关推荐阅读几十万人同时在线的直播 ...

  9. IO、NIO实现简单聊天室,附带问题解析

      本篇文章主要使用IO和NIO的形式来实现一个简单的聊天室,并且说明IO方法存在的问题,而NIO又是如何解决的.   大概的框架为,先提供思路和大概框架图--代码--问题及解决方式,这样会容易看一点 ...

随机推荐

  1. TreeSet类的排序

    TreeSet支持两种排序方法:自然排序和定制排序.TreeSet默认采用自然排序. 1.自然排序 TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间大小关系, ...

  2. rabbitMQ_workQueue(二)

    生产者发送多个消息到队列,由多个消费者消费.   如果一个消费者需要处理一个耗时的任务,那么队列中其他的任务将被迫等待这个消费者处理完成,所以为了避免这样的情况,可以建立对个消费者进行工作. 本例中使 ...

  3. ajax性能优化

    ajax性能优化 例: 模块: A B C D 开销: 50%  3% 25%   22% 如果我们优化B就如同那些那些只执行一次的代码,性能·提高不到哪里去:反之,我们去优化A,比如去优化它的循环, ...

  4. rocksdb编译步骤——Java、Golang、mac

    如果不是必要不建议自己编译rocksdb,编译的过程比较耗时费力.现在已经有很多编译好的文件可供使用. Java <!-- https://mvnrepository.com/artifact/ ...

  5. Java匹马行天下之J2EE框架开发——Spring—>Spring框架知多少

    ————也许我注定成不了一个伟大的人,但是至少我可以做一个很棒的自己.我想我现在应该做的不是瞻前顾后,而是活在当下,正确认知自己,做好自己现在的工作,努力提升自己的能力,踏踏实实地做一个程序员 一.思 ...

  6. Serilog 自定义Enricher 来增加记录的信息

    Serilog 自定义Enricher 来增加记录的信息 Intro Serilog 是 .net 里面非常不错的记录日志的库,结构化日志记录,而且配置起来很方便,自定义扩展也很方便 Serilog ...

  7. JAVA jobs

    Java岗位1, SpringMVC, spring, mybaits2, 高并发编程3, mysql或者oracle SQL调优及函数,存储过程,JOB调度

  8. Assign the task HDU - 3974 (dfs序 + 线段树)

    有一家公司有N个员工(从1到N),公司里每个员工都有一个直接的老板(除了整个公司的领导).如果你是某人的直接老板,那个人就是你的下属,他的所有下属也都是你的下属.如果你是没有人的老板,那么你就没有下属 ...

  9. Java Web基础面试题整理

    Tomcat的缺省端口是多少,怎么修改 tomcat默认缺省端口是8080 修改方法: 找到Tomcat目录下的conf文件夹 进入conf文件夹里面找到server.xml文件 打开server.x ...

  10. Opengl_入门学习分享和记录_02_渲染管线(一)顶点着色器&片段着色器

    写在前面的废话:今天俺又来了哈哈,真的好棒棒! 今天的内容:之前我们大概描述了,我们自己定义的顶点坐标是如何被加载到GPU之中,并且介绍了顶点缓冲对象VBO用于管理这一块内存.今天开始详细分析它的具体 ...