集群聊天服务器项目(一)——模块分层设计

news2024/10/3 6:36:52

本项目对程序不同功能进行分层设计,分为网络层、业务层、和数据层。

C++面向接口编程也就是面向抽象类,网络模块和业务模块尽量解耦。

网络层

网络层主要封装的是网络连接方面的一些功能,即socket相关操作,这里该项目采用的是muduo网络库作为网络层的底层支撑,主要是设置连接到来消息到来的回调设置以及服务器基本设置(如子Loop数、启动服务)。

本项目消息使用json格式,通过解析消息格式,来确定调用业务层的某一具体功能:

void ChatServer::onMessage(const TcpConnectionPtr &conn,
                    Buffer *buffer,
                    Timestamp time)
{
    string buf = buffer->retrieveAllAsString();

    // 数据的反序列化
    json js = json::parse(buf);
    auto msgHandler = ChatService::instance()->getHandler(js["msgid"].get<int>());
    // 回调指定的绑定好的事件处理器,来执行相应的业务处理
    msgHandler(conn, js, time);
}

业务层

业务层主要处理具体的业务,如登录业务、注册业务、一对一聊天业务、群聊业务等,其中业务类ChatService是一个单例模式,其中使用unordered_map存储函数表,通过消息id来映射具体的业务处理函数。还要用一个unordered_map来存储用户id与其对应的TcpConnectionPtr,使其能够对某一客户端连接进行IO操作。

对于某一具体业务,以登录业务举例,其流程如下

在这里插入图片描述

// 处理登录业务
// 检测id 和 pwd是否在user表中存在,并将状态修改
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int id = js["id"].get<int>();
    string pwd = js["password"];

    User user = _userModel.query(id);
    if (user.getId() == id && user.getPwd() == pwd)
    {
        if (user.getState() == "online")
        {
            // 该用户已登录,不允许重复登录
            json response;
            response["msgid"] = LOGIN_MSG_ACK;
            response["errno"] = 2;
            response["errmsg"] = "The account has already been logged in. Please enter again.";
            conn->send(response.dump());
        }
        else
        {
            // 登录成功,记录用户连接信息
            {
                // 锁粒度不要太大,过大就丧失并发性
                lock_guard<mutex> lock(_connMutex);
                _userConnMap.insert({id, conn});
            }

            // 用户登录成功后向redis订阅channel (用户id)
            _redis.subscribe(id);

            // 登录成功,更新用户状态信息
            json response;

            user.setState("online");
            _userModel.updateState(user);

            response["msgid"] = LOGIN_MSG_ACK;
            response["errno"] = 0;
            response["id"] = user.getId();
            response["name"] = user.getName();
            // 查询该用户是否有离线消息,若有则读取
            vector<string> vec = _offlineMsgModel.query(id);
            if (!vec.empty())
            {
                response["offlinemsg"] = vec;
                // 读取该用户离线消息后,把该用户所有离线消息从数据库中删除
                _offlineMsgModel.remove(id);
            }

            // 查询该用户好友信息并返回
            vector<User> userVec = _friendModel.query(id);
            if (!userVec.empty())
            {
                vector<string> vec2;
                for (User &user : userVec)
                {
                    json js;
                    js["id"] = user.getId();
                    js["name"] = user.getName();
                    js["state"] = user.getState();
                    vec2.push_back(js.dump());
                }
                response["friends"] = vec2; // 嵌套使用
            }

            // 查询用户群组信息,一个用户有多个群组,一个群组有多个组员
            vector<Group> groupuserVec = _groupModel.queryGroups(id);
            if (!groupuserVec.empty())
            {
                // "group":[{groupid:[xxx, xxx, xxx, xxx]}]
                vector<string> groupV; // 存储一个用户的所有组的信息
                for (Group &group : groupuserVec)
                {
                    json grpjson;
                    grpjson["id"] = group.getId();
                    grpjson["groupname"] = group.getName();
                    grpjson["groupdesc"] = group.getDesc();
                    vector<string> userV; // 存储一个组内所有组员信息
                    // 遍历每个组内的所有组员信息
                    for (GroupUser &user : group.getUsers())
                    {
                        json js;
                        js["id"] = user.getId();
                        js["name"] = user.getName();
                        js["state"] = user.getState();
                        js["role"] = user.getRole();
                        userV.push_back(js.dump());
                    }
                    grpjson["users"] = userV;
                    groupV.push_back(grpjson.dump());
                }
                response["groups"] = groupV;
            }

            conn->send(response.dump());
        }
    }
    else
    {
        // 登录失败,密码错误
        json response;
        response["msgid"] = LOGIN_MSG_ACK;
        response["errno"] = 1;
        response["errmsg"] = "id or password is invalid!";
        conn->send(response.dump());
    }
}

其他业务处理函数都类似。

数据层

为了使数据模块业务模块分离

加入 ORM(object Relation Model)类也就是将表的字段封装为一个类并提供对应的 getset 方法,业务层操作的都是对象,DAO层(数据访问层)即xxxmodel类才访问数据。

例如,userModel层提供的方法接受的数据都是User对象,而不是直接裸数据传递.

model类是db类和ORM类的桥梁,model类使用db类提供的方法,使用ORM类对象成员进行SQL的CRUD操作。

数据库的表设计

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

一般来讲,本项目在表数据量为5w以内都能比较高效的进行表查询操作而不需要修改表结构或者是分库分表操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436625.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Netty》从零开始学netty源码(三十九)之PoolSubPage的内存释放

PoolSubPage.free PoolSubPage的内存释放相对来说比较简单&#xff1a; 首先根据段的偏移量bitmapIdx找到bitmap的long[]数组的索引q&#xff0c;将bitmap[q]这个long的二进制位的占用位r置为0&#xff0c;表示已经释放。如果PoolSubPage的段已经全部释放了&#xff0c;且池中…

测试开发岗 - 常见面试题

1. 什么是软件测试&#xff0c; 谈谈你对软件测试的了解 软件测试就是验证产品特性是否符合用户需求, 软件测试贯穿于软件的整个生命周期. >>> 那软件测试具体是什么呢 ? 就拿生活中的例子来说, 比如说我们去商场买衣服, 会有以下几个步骤 : 第一步: 我们会走进门店…

【网络安全】命令执行漏洞

命令执行漏洞 命令执行漏洞原理危害检测方法有回显检测方法; (分号) 从左到右执行| (管道符) 将见面命令的输入为后面命令的标准输入&(后台任务符号) 命令从左到右执行&&(与) 逻辑与&#xff0c;前面命令执行成功后才会执行||(或) 逻辑或&#xff0c;前面执行失败才…

LeetCode算法小抄-- 图的遍历

LeetCode算法小抄-- 图的遍历 图基本概念遍历广度优先算法(BFS)框架[111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/)[752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/)[773. 滑动谜题](https://leetcode.cn/problems/sli…

文章伪原创生成器在线-文章伪原创工具免费入口

文章自动生成器 在现代科技快速发展的时代中&#xff0c;自动化技术已经深入到了各个领域。而随着人工智能技术的提高&#xff0c;自动化技术在创意和写作领域越来越成熟。现在有一款名为“文章自动生成器”的软件&#xff0c;可以轻松地生成高质量的文章。 今天&#xff0c;我…

STM32之MPU6050获取欧拉角

STM32之MPU6050获取欧拉角 MPU6050MPU6050特点MPU6050电路图以及框图MPU6050框图MPU6050电路图 MPU6050相关寄存器电源管理寄存器1&#xff08;0x6B&#xff09;陀螺仪配置寄存器&#xff08;0x1B&#xff09;加速度计配置寄存器&#xff08;0x1C&#xff09;陀螺仪采样率分频寄…

Vue中的ajax【Vue】

4. Vue 中的 ajax 4.1 解决开发环境 Ajax 跨域问题 方法一&#xff1a; 在vue.config.js中添加如下配置&#xff1a; devServer:{proxy:"http://localhost:5000" }说明&#xff1a; 优点&#xff1a;配置简单&#xff0c;请求资源时直接发给前端&#xff08;808…

更懂业务的用友iuap平台,助力企业升级数智化底座

4月19日&#xff0c;一年一度的用友BIP技术大会如约而至。近千位来自三十个行业的企业家、CIO/CDO、企业主管、专家学者、媒体、分析师代表现场参与大会。伴随企业数智化推进&#xff0c;越来越多的企业需要升级数智底座平台。会上&#xff0c;用友介绍了更懂企业业务的用友BIP…

Android 开发为什么会要用到组件化与插件化?好处在哪?

对于开发者来说&#xff0c;写好代码的第一步就是具备良好的架构能力。但是这项基本的能力&#xff0c;也很少有人具备。就拿最常用的项目架构组件化来说&#xff0c;有多少人用过&#xff1f;又有谁去了解过组件化开发中真正会遇到的问题&#xff0c;以及如何解决&#xff1f;…

Nacos 1.4.x 升级至 2.x 详细步骤及遇到的问题,亲测可行

此前使用的nacos版本是1.4.5&#xff0c;现在nacos最新版本为2.2.2&#xff0c;且修复了旧版本的一些安全问题&#xff0c;下面把详细的升级步骤记录一下&#xff0c;大家一起学习&#xff0c;亲测有效。 主要参考nacos官方升级文档&#xff1a;https://nacos.io/zh-cn/docs/v2…

瑞吉外卖项目——读写分离

读写分离 读和写所有压力都由一台数据库承担&#xff0c;压力大数据库服务器磁盘损坏则数据丢失&#xff0c;单点故障 Mysql主从复制 介绍 MySQL主从复制是一个异步的复制过程&#xff0c;底层是基于Nysql数据库自带的二进制日志功能。 就是一台或多台MysQL数据库&#xf…

字符串 --- 找子串匹配算法

1.基本介绍 主串&#xff1a;形如 “hello world”的字符串作为一个整体 子串&#xff1a;上面主串的一部分如“world” 在计算机世界&#xff0c;主串找子串的模式很常见&#xff0c;比如要在word文件中找一句指定的话&#xff0c;那么面对海量的信息&#xff0c;我们匹配算法…

最新入河排污口设置论证、水质影响预测与模拟、污水处理工艺分析及典型建设项目入河排污口方案报告书实例分析

目录 专题一 入河排污口设置论证相关法律与制度解读 专题二 水域纳污能力核算方法 &#xff08;讲授与实操相结合&#xff09; 专题三 入河排污口设置方案、分析范围、论证范围、模型预测范围确定方法 专题四 入河排污口所在水域水质现状与取水、排污状况分析 专题五 入河…

React State 状态

React State(状态) React 把组件看成是一个状态机&#xff08;State Machines&#xff09;。通过与用户的交互&#xff0c;实现不同状态&#xff0c;然后渲染 UI&#xff0c;让用户界面和数据保持一致。 React 里&#xff0c;只需更新组件的 state&#xff0c;然后根据新的 s…

SPI通讯

1、介绍 SPI是高速、全双工、同步的通信总线。 SPI应用于存储芯片、AD转换器及LCD中。 同步、异步区别&#xff1a;是否有时钟线&#xff0c;例如SPI、I2C是同步通信&#xff0c;需要用到时钟线&#xff0c;串口是异步通信&#xff0c;没有时钟线。 SPI通信需要四根线&…

【数据结构】单链表(详解)

【数据结构】单链表&#xff08;详解&#xff09; 1.前言1.1本章节重点1.2 什么是单链表1.3 结构体设计1.4结构体传参 2. SList.h展示3. SList.c展示4. 各个接口函数的实现4.1 尾插4.2 打印4.3 头插4.3.1内存开辟函数4.3.2插入 4.4 尾删4.5 头删4.6 查找4.7 给定一个位置在这个…

今天面了个字节跳动拿35K出来的,真是砂纸擦屁股,给我露了一手啊

今年的金三银四已经快要结束了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&#xff0c…

为什么选择学习python?

对于编程初学者来说&#xff0c;python更加简单易学&#xff0c;便于初学者入门~ 学Python之前&#xff1a;这玩意真有传说中那么好么&#xff1f; 学Python之后&#xff1a;唉呀妈呀&#xff0c;真香~ 别人花2天时间处理的Excel数据&#xff0c;你用Python 只花1小时&#…

若依移动端Ruoyi-App——字典使用

1. 引入dict 将若依前后端分离中的dict文件夹拷贝到api的system里 2.在页面中引入方法 import { getDicts } from "/api/system/dict/data"; 3. 前端 <span>{{statusType}}</span> 4. 加载数据字典 export default {data() {return {statusOptions…

LeetCode:剑指 Offer 58 - II. 左旋转字符串

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;剑指 Offer 58 - II. 左旋转字符串 题目描述&#xff1a;字符串的左旋…