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

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

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以内都能比较高效的进行表查询操作而不需要修改表结构或者是分库分表操作。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值