前言

t-io作者在开源其框架的同时还附带了几个demo,如:简单的hello world、im等。接下来这篇博客将会围绕tio-examples-im-simple-client、tio-examples-im-simple-server展开分析和学习。

demo项目结构

im-simple分成三个maven子项目:

  • tio-examples-im-simple-client就是客户端聊天工具项目
  • tio-examples-im-simple-common保存了一些公用的类型定义和utils,
  • tio-examples-im-simple-server包含了聊天系统的服务端和一套附带了nginx的网页聊天工具。

整片博客是围绕着网页版聊天工具而写的。

聊天系统

  1. 概述

首先介绍一下这个聊天系统demo大概交互的逻辑是怎么样子的,如下图:

  • demo中web聊天系统使用了websocket协议来和服务端进行通信。
  • demo中数据交互无论是浏览器端、还是服务端数据传输方式都使用了proto-buf(这个让我眼前一亮,之前只听说过客户端和服务器端采用proto-buf,demo里面竟然使用了js的proto-buf,因为之前接触js这一块不多,第一次看到就给跪了)。

2.代码分析

  • 服务端初始化demo:
..
// 定义handler,所有的请求数据全部都由这个handler来处理,decode/encode/handler等等
// 如果您做web开发一定知道dispatcher的概念,这个handler会将数据解码,然后将数据分发给对应的handler处理业务
aioHandler = new ImServerAioHandler();
// listenr 可以在连接上、接收到消息、发送消息后等等回调其内部方法
aioListener = new ImServerAioListener();
// 服务端上下文初始化
serverGroupContext = new ServerGroupContext<>(aioHandler, aioListener);serverGroupContext.setEncodeCareWithChannelContext(true);
aioServer = new AioServer<>(serverGroupContext);
aioServer.start(ip, port);
..

从初始化的几句代码中可以看出ImServerAioHandler 是聊天系统中的重中之重,我们来看一下里面的代码

/..
// 握手请求
handlerMap.put(Command.COMMAND_HANDSHAKE_REQ, new HandshakeReqHandler());
// 授权请求
handlerMap.put(Command.COMMAND_AUTH_REQ, new AuthReqHandler());
// 聊天请求
handlerMap.put(Command.COMMAND_CHAT_REQ, new ChatReqHandler());
// 加入群组请求
handlerMap.put(Command.COMMAND_JOIN_GROUP_REQ, new JoinReqHandler());
// 心跳请求
handlerMap.put(Command.COMMAND_HEARTBEAT_REQ, new HeartbeatReqHandler());
// 关闭连接请求
handlerMap.put(Command.COMMAND_CLOSE_REQ, new CloseReqHandler());
/../.. 在这里通过解析消息头之后获取下一步调用哪个handler来处理业务
public Object handler(ImPacket packet, ChannelContext<ImSessionContext, ImPacket, Object> channelContext) throws Exception{Command command = packet.getCommand();ImBsHandlerIntf handler = handlerMap.get(command);if (handler != null){Object obj = handler.handler(packet, channelContext);CommandStat.getCount(command).handled.incrementAndGet();return obj;} else{CommandStat.getCount(command).handled.incrementAndGet();log.warn("找不到对应的命令码[{}]处理类", command);return null;}}/..

当然,上面的其实都是业务代码,还没有很明显的看到框架代码,接下来我们看看怎样发送一条聊天的消息给一个群组。

  • ChatReqHandler.java是处理发送聊天消息的类:
        // protobuf反序列化消息体         ChatReqBody chatReqBody = ChatReqBody.parseFrom(packet.getBody());// demo这里写死了一些信息,消息发送者Integer fromId = 111;String fromNick = "test";// 把消息发送给谁Integer toId = chatReqBody.getToId();String toNick = chatReqBody.getToNick();String toGroup = chatReqBody.getGroup();// 其实demo的代码这里写的不太好,如果chatReqBody==null,前面就报错了if (chatReqBody != null){// 构建消息体响应类ChatRespBody.Builder builder = ChatRespBody.newBuilder();builder.setType(chatReqBody.getType());builder.setText(chatReqBody.getText());builder.setFromId(fromId);builder.setFromNick(fromNick);builder.setToId(toId);builder.setToNick(toNick);builder.setGroup(toGroup);builder.setTime(SystemTimer.currentTimeMillis());//同样protobuf序列化ChatRespBody chatRespBody = builder.build();byte[] bodybyte = chatRespBody.toByteArray();//组建即时聊天响应包ImPacket respPacket = new ImPacket();respPacket.setCommand(Command.COMMAND_CHAT_RESP);respPacket.setBody(bodybyte);// 如果是对群组发送,直接调用Aio.sendToGroup即可(框架代码)if (Objects.equals(ChatType.CHAT_TYPE_PUBLIC, chatReqBody.getType())){Aio.sendToGroup(channelContext.getGroupContext(), toGroup, respPacket);} else if (Objects.equals(ChatType.CHAT_TYPE_PRIVATE, chatReqBody.getType())){   // 如果是对单个人发送,也有Aio.sendToUser方法(框架代码)if (toId != null){Aio.sendToUser(channelContext.getGroupContext(), toId + "", respPacket);}}}

看看,真正使用到框架的代码实际上就一行:Aio.sendToXXX静态方法。

  • 那也许会有人问,框架是怎么知道这个group里面有哪些连接啊?框架怎么知道会有哪些连接的user呢?

注意看众多handler中,其中有一个JoinReqHandler是在客户端调用加入群组命令的时候执行的:

public Object handler(ImPacket packet, ChannelContext<ImSessionContext, ImPacket, Object> channelContext) throws Exception
{
// ..
// 连接上下文绑定群组
Aio.bindGroup(channelContext, group);
// ..}

一个简单的bindGroup即可把这个连接绑定到一个组里面,从而通过Aio.sendToGroup即可轻松的给一个组发送消息。而在AuthReqHandler中同样调用了Aio.bindUser方法来绑定有用户连接(这个不贴代码了,贴多了界面太长,让人不想看)。

保存user和group,作者使用的是一个带了读写锁的自己封装的DualHashBidiMap (apache工具包里面的,双向map,可以通过key获取value,可以通过value获取key)。

总结t-io在聊天系统中的使用方法

  • 创建了一个server端
  • 客户端在鉴权的时候Aio.bindUser
  • 客户端在加入群组的时候Aio.bindGroup
  • 客户端在发送消息时Aio.sendToUser/Aio.sendToGroup即可
  • 当然,只要你实现一下ClientAioHandler的heartbeatPacket方法,框架本身给实现了自动心跳检测,重连也只需要在初始化连接上下文时传入ReconnConf即可。
  • 后面我自己设计config-server的时候,给客户端推送一组服务IP过去也是相当容易的事情啦。
  • 棋牌类游戏交互也类似了,一桌四个人,一个的操作,发送到服务端做算法逻辑校验后,同时推送给其他三个人,好像这样实现起来也不复杂了。而需要关心的仅仅只是server本身的集群问题了。

demo中我最感兴趣的和我学到的

  • 框架提供的user和group概念很方便
  • ByteBuffer消息头的定义方法和解析,demo里面定义消息头定义得很节省空间
    ByteBuffer buffer = ByteBuffer.allocate(allLen);
    buffer.order(groupContext.getByteOrder());
    //这里比较有迷惑性,这个version并非随便乱定,而是需要根据后面是否使用压缩、序号同步等等标记出来的二进制
    buffer.put(ImPacket.VERSION);
    buffer.put((byte) packet.getCommand().getNumber());
    buffer.put(isCompress ? (byte)1 : (byte)0);
    buffer.putInt(packet.getSynSeq());
    buffer.putShort((short)bodyLen);
    

    这里注意,我差点被作者绕进去了,ImPacket.VERSION比较有迷惑性

    //这是客户端解析第一个字节的定义,不是浏览器解析规则
    //其实作者定义得version自己规定的只占用4个比特位,如:0B00001111,后四位都是用来真正标示版本号
    //实际上前面的几位是另外分出来标记是否压缩,是否同步序号等等
    byte version = ImPacket.decodeVersion(firstbyte);
    boolean isCompress = ImPacket.decodeCompress(firstbyte);
    boolean hasSynSeq = ImPacket.decodeHasSynSeq(firstbyte);
    boolean is4ByteLength = ImPacket.decode4ByteLength(firstbyte);
    
  • 消息点对点发送和消息的群组发送性能和稳定性还有待我做做测试,后续在全面了解完t-io框架后准备弄一份关于性能测试的报告出来试试。

同时感谢t-io作者对我的指导 !

转载于:https://my.oschina.net/u/2461727/blog/897548

t-io 入门篇(三)即时消息发送demo学习相关推荐

  1. 即时消息发送模块 java_五个模块,以促进模块化的即时性

    即时消息发送模块 java I designed my modular to be used with a computer. At times, it feels like I'm in the m ...

  2. 计算机与交换机基础配置入门,新手入门篇:交换机配置窍门深入学习 -电脑资料...

    新手入门篇:交换机配置技巧深入学习,关于交换机配置技巧的问题,涉及到的东西比较多, 交换机配置技巧一直以来是非常神秘的,不仅对于一般用户,对于绝大多数网管人员来说也是如此,同时也是作为网管水平高低衡量 ...

  3. RabbitMQ入门(三)消息应答与发布确认

    前言: 消息应答与发布确认都是保证消息不丢失.而重复消费问题则是消息幂等性.(之后会说幂等性) 消息应答: 应答功能属于消费者,消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理 ...

  4. ITE Super IO入门篇

    接触BIOS以来算起来已经有四年了,想着将自己所学的知识记录起来,这样也方便他人学习.这是我的第一篇博客,从SIO开始吧! 简介 SIO应用在台式机比较多,主要用来做电源管理,如上电时序控制:外围接口 ...

  5. Unity内置的三套消息发送机制的应用实例

    转自http://blog.sina.com.cn/s/blog_1491e52310102wuf6.html 代码简介 : [1] 实例中包含2个类文件, SendMessage.cs 和 Rece ...

  6. Vue学习-基础入门篇(三万字收藏篇)

    目录 前言 一.Vue介绍 1.Vue是什么 2.Vue的特点 二.Vue.js的安装 1.使用Vite创建 三.Vue声明式渲染 四.Data Property 和方法 1.Data Propert ...

  7. 无责任Windows Azure SDK .NET开发入门篇三[使用Azure AD 管理用户信息]

    三.使用Azure AD 管理用户信息 在上一章我们采用OpenID的方案和Azure AD交互进行身份验证,本章节我们继续了解如何在Azure AD中创建用户,列出用户信息,修改用户信息和删除用户信 ...

  8. 软件测试基础理论(入门篇三)---缺陷管理理论与工具(禅道)

    目录 一.软件的缺陷 二.缺陷管理 2.1 提交缺陷注意事项 2.2 缺陷的跟踪流程 三.项目管理工具之一---禅道 3.1 禅道介绍 3.1.1 基础了解 3.1.2 禅道使用流程 3.1.3测试人 ...

  9. 算法入门篇三 详解桶排序和整理排序知识 堆的相关操作 补充 不完整

    归并排序不使用递归 使用一个变量,使其按照1.2.4.8递增,控制左右两边1个元素.2个元素.4个元素等元素的合并 完全二叉树 完全二叉树 要不全是满的,要不叶子节点出现在最后一层,只要出现了叶子节点 ...

最新文章

  1. 设置Fetch快捷键Ctrl+Alt+Shift+1
  2. 【python教程】对多线程中join()的详细教程
  3. sql 取汉字首字母
  4. 团队冲刺第六天个人博客
  5. localStorage封装借口store.js的使用
  6. Windows与linux双系统安装
  7. leetcode41 缺失的第一个正数
  8. 最新!Vicor 270V-28V DCM5614以96%效率提供1300W功率
  9. Scrapy 爬虫框架初体验二 —— 以一个新闻站点爬取为例
  10. 企业利用Pinterest平台推广9条建议
  11. python模块调用模块_Python模块调用
  12. Quartz简答介绍
  13. 学习:如何把备份的bak还原到新的数据库(转)
  14. Golang Gin实践 连载十六 在图片上绘制文字
  15. JavaScript 操作 session 方法
  16. (机器学习)C均值算法(K-Means)之误差平方和推导
  17. 高斯法计算矩阵的行列式
  18. Element系列之阻止el-switch冒泡事件
  19. 计算机网络 路由协议的配置_瑞尔森大学计算机网络专业
  20. 百度 谷歌 Twitter,这么多短链接服务(Short Url)到底哪家强?

热门文章

  1. <datart二开>翻牌器侧(纵)轴对齐方式
  2. pdf编辑软件:Acrobat Pro DC 2022 直装版(win/mac)
  3. 现有SNS产品对比分析
  4. 学习python之——python入门
  5. win7显卡nvidi466.77驱动和补丁KB4474419和KB4490628
  6. ThinkPad T400s之Disport实践
  7. mxGraph 用户手册中文 – JavaScript客户端
  8. 电子元件符号及字母表示
  9. MCP73832T-2ACI/OT
  10. linux内存管理之石器篇