基于Tars高并发IM系统的设计与实现-实战篇5
基于Tars高并发IM系统的设计与实现-实战篇5
群聊服务 GroupChatServer
群聊服务既可以接受来自BrokerServer的用户请求,也需要接收来自其他服务的RPC请求;所以本服务提供两套RPC接口:通用RPC接口和专用RPC接口。
通用RPC接口
通用RPC接口主要处理如下请求:
- 创建群聊
- 群聊加成员
- 群聊减成员
- 修改群资料
- 发群消息
- 换群主
- 解散群聊
- 同步用户群聊
- 获取群成员
- 解散群聊
- 判断一个人是否为群成员
针对以上每个业务,根据用户请求的类型进行不同的业务逻辑处理,处理代码如下:
switch(req.header.type){case otim::PT_MSG_GROUP_CHAT:this->sendMsg(clientContext, req, resp);break;case otim::PT_GROUPCHAT_SYNC:this->syncGroup(clientContext, req, resp);break;case otim::PT_GROUPCHAT_CREATE:this->createGroup(clientContext, req, resp);break;case otim::PT_GROUPCHAT_JION:this->joinGroup(clientContext, req, resp);break;case otim::PT_GROUPCHAT_QUIT:this->quitGroup(clientContext, req, resp);break;case otim::PT_GROUPCHAT_DISMISS:this->dismissGroup(clientContext, req, resp);break;case otim::PT_GROUPCHAT_UPDATE_CREATOR:this->updateGroupCreator(clientContext, req, resp);break;case otim::PT_GROUPCHAT_INFO_UPDATE:this->updateGroupInfo(clientContext, req, resp);break;case otim::PT_GROUPCHAT_MEMBERS_GET:this->getGroupMember(clientContext, req, resp);break;default:MLOG_DEBUG("the type is invalid:"<<otim::etos((otim::PACK_TYPE)req.header.type));return otim::EC_PROTOCOL;}
群聊相关请求实现方法:
int syncGroup(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int createGroup(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int dismissGroup(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int updateGroupCreator(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int updateGroupInfo(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int joinGroup(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int quitGroup(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);int getGroupMember(const otim::ClientContext & clientContext, const otim::OTIMPack & req, otim::OTIMPack & resp);
专用RPC接口
主要提供两个接口:
interface GroupChatRPCServant
{int getGroupMember(string groupId, out vector<string> memberIds);bool isGroupMember(string groupId, string memberId);};
历史消息服务 HistoryMsgServer
该服务主要处理用户历史消息即相关的业务:
- 热会话同步
- 历史消息存取
- 高优先级消息存取
该服务提供通用RPC服务,主要服务对象为接入服务BrokerServer;
用户所有消息都通过该服务进行存取;为高效存取,历史消息主要存储在redis,存储量及时长可以根据需求进一步来做配置开发。
业务逻辑处理接口
该服务采用通用接口来处理客户端请求;
tars::Int32 processHotsessionReq(const otim::ClientContext & clientContext,const otim::OTIMPack & req,otim::OTIMPack &resp);tars::Int32 processPullHistoryReq(const otim::ClientContext & clientContext,const otim::OTIMPack & reqPack,otim::OTIMPack &respPack);tars::Int32 processHighPriorMsgSyncReq(const otim::ClientContext & clientContext,const otim::OTIMPack & reqPack,otim::OTIMPack &respPack);
冷存储服务器 OlapServer
该服务主要将IM数据存储到mysql中永久保存;专用RPC服务;
业务逻辑处理接口
interface OlapServant
{int saveMsg(otim::ClientContext clientContext, OTIMPack pack, string sessionId, long seqId);
};
消息操作服务 MsgOperatorServer
该服务主要有如下功能逻辑处理:
- 消息控制请求(包含撤回,删除,覆写)
- 消息已读处理
业务逻辑处理接口
tars::Int32 processMsgUnreadReq(const otim::ClientContext & clientContext,const otim::OTIMPack & reqPack,otim::OTIMPack &respPack);tars::Int32 processMsgCTRLReq(const otim::ClientContext & clientContext,const otim::OTIMPack & reqPack,otim::OTIMPack &respPack);
消息撤回,删除,覆写逻辑处理
tars::Int32 MsgOperatorServantImp::processMsgCTRLReq(const otim::ClientContext & clientContext,const otim::OTIMPack & reqPack,otim::OTIMPack &respPack)
{SCOPELOGGER(scopelogger);scopelogger<<"reqPack:"<<reqPack.header.writeToJsonString();otim::MsgControl req;otim::unpackTars<otim::MsgControl>(reqPack.payload, req);MLOG_DEBUG("clientContext:"<<clientContext.writeToJsonString()<<" req:"<<req.writeToJsonString());respPack = reqPack;respPack.header.flags |= otim::PF_ISACK;otim::CommonErrorCode respData;respData.code = otim::EC_SUCCESS;if (req.sessionId.empty() || req.seqId == 0 || req.packId.empty()){respData.code = otim::EC_PARAM;otim::packTars<otim::CommonErrorCode>(respData, respPack.payload);MLOG_DEBUG("sessionId,packId or seqId is is empty req:"<<req.writeToJsonString());return respData.code;}otim::RedisConnPtr redis(otim::RedisPool::instance());//get old msgstd::vector<std::string> msgs;std::vector<std::string> scores;EMRStatus ret = redis->ZRangeByScoreAndLimit(otim::RKEY_MSG + req.sessionId, req.seqId, 5, msgs);if (EMRStatus::EM_KVDB_ERROR == ret){MLOG_ERROR("get msg fail!, sessionId:" << req.sessionId<<" msgId:"<<req.seqId);respData.code = otim::EC_DB_ERROR;otim::packTars<otim::CommonErrorCode>(respData, respPack.payload);return otim::EC_DB_ERROR;}MLOG_DEBUG("get old msg size:"<<msgs.size());otim::OTIMPack packOrg;for (auto item : msgs){std::vector<char> vctItem(item.begin(), item.end());otim::OTIMPack packItem;otim::unpackTars<otim::OTIMPack>(vctItem, packItem);MLOG_DEBUG("msgs :"<<packItem.header.writeToJsonString());if (packItem.header.packId == req.packId){packOrg = packItem;}}if (packOrg.header.packId.empty()){MLOG_WARN("The org msg is not exist:"<<req.sessionId<<" packId:"<<req.packId <<" seqId:"<<req.seqId);respData.code = otim::EC_MSG_NOT_EXIST;otim::packTars<otim::CommonErrorCode>(respData, respPack.payload);return respData.code;}MLOG_DEBUG("org msg:"<<packOrg.header.writeToJsonString());std::string to;if (req.command == otim::MC_REVOKE){packOrg.header.flags |= otim::PF_REVOKE;MLOG_DEBUG("revoke msg:"<<req.packId);}else if (req.command == otim::MC_OVERRIDE){packOrg.header.flags |= otim::PF_OVERRIDE;otim::MsgReq msgReq;otim::unpackTars<otim::MsgReq>(packOrg.payload, msgReq);msgReq.content = req.content;otim::packTars<otim::MsgReq>(msgReq, packOrg.payload);to = msgReq.to;MLOG_DEBUG("override msg:"<<req.packId);}else if (req.command == otim::MC_DELETE){
// packOrg.header.flags |= otim::PF_REVOKE;MLOG_DEBUG("delete msg:"<<req.packId);}else{MLOG_WARN("The command is error:"<<req.command<<" packId:"<<req.packId);respData.code = otim::EC_MSG_OP_CMD;otim::packTars<otim::CommonErrorCode>(respData, respPack.payload);return otim::EC_MSG_OP_CMD;}ret = redis->ZSetRemoveByScore(otim::RKEY_MSG + req.sessionId, req.seqId, req.seqId);if (EMRStatus::EM_KVDB_SUCCESS != ret ){MLOG_ERROR("delete original msg fail:"<<(int)ret);}//增加新的消息if (req.command != otim::MC_DELETE){std::string msgSave;otim::packTars<otim::OTIMPack>(packOrg, msgSave);ret = redis->ZSetAdd(otim::RKEY_MSG + req.sessionId, req.seqId, msgSave);if ( EMRStatus::EM_KVDB_SUCCESS != ret ){MLOG_ERROR("save cancel msg fail!");}}//通知在线接收者其他端otim::sendPackToMySelf(clientContext, reqPack);// send to userstd::vector<std::string> vctUserId;if (packOrg.header.type == otim::PT_MSG_SINGLE_CHAT || packOrg.header.type == otim::PT_MSG_BIZ_NOTIFY){if (to.empty()){otim::MsgReq msgReq;otim::unpackTars<otim::MsgReq>(packOrg.payload, msgReq);to = msgReq.to;}vctUserId.push_back(to);MLOG_DEBUG("single or notify chat packId:"<<packOrg.header.packId<<" to:"<<to);}else if (packOrg.header.type == otim::PT_MSG_GROUP_CHAT){//get groupMemberotim::GroupChatRPCServantPrx groupChatRPCServantPrx = otim::getServantPrx<otim::GroupChatRPCServantPrx>(PRXSTR_GROUP_CHAT_RPC);groupChatRPCServantPrx->getGroupMember(req.sessionId, vctUserId);MLOG_DEBUG("group chat packId:"<<packOrg.header.packId<<" to:"<<req.sessionId<<" member Size:"<<vctUserId.size());}int64_t seqId = otim::genSeqId();for (auto userId : vctUserId){otim::savePriorityMsg(redis.get(), reqPack, userId, seqId);otim::dispatchMsg(clientContext, reqPack, userId);}respData.code = otim::EC_SUCCESS;otim::packTars<otim::CommonErrorCode>(respData, respPack.payload);return otim::EC_SUCCESS;
}
Http接口服务
该服务针对第三方提供消息能力,主要提供如下接口:
- 发送消息(简单文本消息,复杂消息)
- 添加好友
- 删除好友
- 查看好友
功能实现函数
std::string doSendSimpleMsgCmd(TC_HttpRequest &cRequest);std::string doSendMsgCmd(TC_HttpRequest & cRequest);std::string doAddFriend(TC_HttpRequest &request);std::string doDelFriend(TC_HttpRequest &request);std::string doGetFriends(TC_HttpRequest &request);
Push推送服务
该服务主要实现IM消息的离线推送能力, APP客户端不在线的场景下,将消息通过离线通道push用户的手机上,以提高消息的触达率。
主要实现iOS APNS,Android FCM,华为,小米,oppo,vivo等厂商的离线消息推送功能;
需要根据各个厂商开放平台提供的API进行开发集成。
Android 厂商开发平台地址:
- 华为:
https://developer.huawei.com/consumer/cn/hms/huawei-pushkit - 小米:
https://dev.mi.com/console/appservice/push.html - 魅族:
http://open-wiki.flyme.cn/doc-wiki/index - vivo:
https://push.vivo.com.cn/#/ - oppo:
https://push.oppo.com/
RPC接口
enum PushServiceType
{PS_TYPE_NONE = 0, //无 Push服务提供商PS_TYPE_IOS = 1, //IOS Push服务提供商PS_YPE_HUAWEI = 2, //华为 Push服务提供商PS_TYPE_XIAOMI = 3, //小米 Push服务提供商PS_TYPE_MEIZU = 4, //魅族 Push服务提供商PS_TYPE_VIVO = 5, //vivi服务PS_TYPE_OPPO = 6, //oppo服务PS_TYPE_FCM = 7, //FCM服务
};struct RegInfo {0 require string packId = ""; //消息的id1 require PushServiceType serviceType = 0; //push服务提供商2 require string packageName = ""; //包名3 require string userId = ""; //用户id4 optional string appVersion = ""; //app version
};struct PushInfo {0 require string packId = ""; //消息的id1 require string userId = ""; //用户id2 require int unReadCount = 0; //未读消息数3 require string title = ""; /push标题4 require string content = ""; //push内容5 optional string uri = ""; //跳转uri6 optional string extraData=""; //业务自定义字段
};interface PushServant
{int register(RegInfo regInfo);int pushMessage(PushInfo pushInfo);
};
服务端部署
编译打包
所有服务开发完成后,执行如下命令进行编译,打包:
make release
make clean all
make tar
程序包部署
根据前期部署好的Tars框架环境、web管理系统,将程序包逐个发布,发布后的系统如图:
相关文章:

基于Tars高并发IM系统的设计与实现-实战篇5
基于Tars高并发IM系统的设计与实现-实战篇5 群聊服务 GroupChatServer 群聊服务既可以接受来自BrokerServer的用户请求,也需要接收来自其他服务的RPC请求;所以本服务提供两套RPC接口:通用RPC接口和专用RPC接口。 通用RPC接口 通用RPC接口主要处理如下…...
水溶性Cyanine3 N3叠氮化物Cy3 azide星戈瑞
欢迎来到星戈瑞荧光stargraydye! ICG-DBCO点击化学反应在生物标记物探测中应用。通过将ICG-DBCO与具有炔基的生物标记物结合,可以实现快速、选择性和稳定的共价连接,从而实现生物标记和探测。 **以下是ICG-DBCO点击化学反应在生物标记物探测中的一些应用…...

客户案例 | 永续发展,低代码助力“双碳”战略历史使命
关键发现 客户痛点:应对企业数字化转型,新技术能否提升绩效的不确定性,投资带来的风险性,以及企业组织架构的适应性等难点问题。作为业务驱动型企业,欠缺快速构建数字化产品方案的能力。 解决方案:利用西门…...

[保研/考研机试] KY187 二进制数 北京邮电大学复试上机题 C++实现
描述 大家都知道,数据在计算机里中存储是以二进制的形式存储的。 有一天,小明学了C语言之后,他想知道一个类型为unsigned int 类型的数字,存储在计算机中的二进制串是什么样子的。 你能帮帮小明吗?并且,小…...

SpringBoot 热部署
一、启动热部署 1.1 开启开发者工具 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional> </dependency>…...

BLE蓝牙协议栈分析
BLE——协议层次结构 一、BLE Controller Controller实现射频相关的模拟和数字部分,完成最基本的数据发送和接收,Controller对外接口是天线,对内接口是主机控制器接口HCI(Hostcontroller interface); 控制…...
flutter开发实战-BackdropFilter高斯模糊子Widget控件
flutter开发实战-BackdropFilter高斯模糊子Widget。 最近开发过程中遇到需要将控件进行模糊,比如iOS的effect的模糊效果。那在flutter中就需要用到了BackdropFilter 一、BackdropFilter BackdropFilter属性定义 BackdropFilter({Key key, required ImageFilter …...

嵌入式面试刷题(day3)
文章目录 前言一、怎么判断两个float是否相同二、float数据可以移位吗三、数据接收和发送端大小端不一致怎么办四、怎么传输float类型数据1.使用联合进行传输2.使用字节流3.强制类型转换 总结 前言 本篇文章我们继续讲解嵌入式面试刷题,给大家继续分享嵌入式中的面…...

JVM源码剖析之Java命令行参数全解
最近,有一位网友询问关于Java命令行参数方面的问题,因为在Java中参数有很多种,有不少的读者一直没弄明白,所以特意写下此篇文章。 此篇文章分2大块,第一块是不同参数的解释,第2块就是JVM源码论证ÿ…...

抽象工厂模式-java实现
介绍 抽象工厂模式基于工厂方法模式引入了“产品族”的概念,即我们认为具体产品是固定的,具体产品存在等级之分,比如我们常说的手机,有“青春版”,“至尊版”,“至臻版”。一个产品有多个版本族。这时候&a…...
机器学习笔记 - 基于Python发现最佳计算机视觉模型的神经架构搜索技术NAS
一、简述 近年来,随着深度学习技术的兴起,计算机视觉领域取得了巨大进步。事实证明,卷积神经网络 (CNN) 在图像识别任务中异常强大,但针对特定问题设计最佳架构仍然是一项具有挑战性的任务。这就是神经架构搜索(NAS)发挥作用的地方。NAS 是一种尖端技术,可以自动发现高性…...

机器学习---自编码器
自编码器过程 输入一个图片,经过encoder变成一个向量,再通过decoder将这个向量反向生成输入的图片。 这里我们希望输入和输出越接近越好。这个过程我们称为重建。 特点:不需要任何的标注资料。 在2006年这个思想就被提出来了: …...

vuejs 设计与实现 - 渲染器的设计
渲染器与响应式系统的结合 本节,我们暂时将渲染器限定在 DOM 平台。既然渲染器用来渲染真实 DOM 元素,那么严格来说,下面的函数就是一个合格的渲染器: // 渲染器: function renderer(domString, container) {container.innerHTM…...
openCV 图像对象的创建和赋值
文章目录 一、赋值二、克隆三、拷贝四、初始化 一、赋值 赋值操作是将一个cv::Mat对象的数据复制到另一个对象中。赋值操作使用的是浅拷贝(shallow copy),即两个对象共享相同的数据内存。这意味着对一个对象的修改会影响到另一个对象 cv::M…...

idea - 刷新 Git 分支数据 / 命令刷新 Git 分支数据
一、idea - 刷新 Git 分支数据 idea 找到 fetch 选项,重新获取分支数据 二、命令刷新 Git 分支数据 git fetch参考链接 1. 远程Gitlab新建的分支在IDEA里不显示...

线上电影购票选座H5小程序源码开发
搭建一个线上电影购票选座H5小程序源码需要一些基本的技术和步骤。以下是一个大致的搭建过程,可以参考: 1. 确定需求和功能:首先要明确你想要的电影购票选座H5小程序的需求和功能,例如用户登录注册、电影列表展示、选座购票、订单…...
QT正则校验
文章目录 前言一、Qt正则校验1.对输入框进行校验,不允许输入其他字符2.直接校验字符串 二、常用正则校验表达式 前言 项目中会经常遇到需要对字符串进行校验的情况,需要用到正则表达式(Regular Expression,通常简写为RegExp、RE等…...

ChatGPT“侵入”校园,教学评价体制受冲击,需作出调整
北密歇根大学的教授奥曼在学生作业中发现了一篇关于世界宗教的“完美论文”。“这篇文章写得比大多数学生都要好......好到不符合我对学生的预期!”他去问ChatGPT:“这是你写的吗?”ChatGPT回答:“99.9%的概率是的。” ChatGPT“侵…...

函数的声明和定义
1、函数声明 //告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。 //函数的声明一般出现在函数的使用之前。要满足先声明后使用。 //函数的声明一般要放在头文件中的。 2、函数的定义 //函数…...

06微服务间的通信方式
一句话导读 微服务设计的一个挑战就是服务间的通信问题,服务间通信理论上可以归结为进程间通信,进程可以是同一个机器上的,也可以是不同机器的。服务可以使用同步请求响应机制通信,也可以使用异步的基于消息中间件间的通信机制。同…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...