C++集群聊天服务器(5)——Model数据层代码框架设计
先看看新增的目录与源文件:之前的6个文件(ChatServer、ChatService、public.h、main.cpp)构建了一个高性能网络框架 + 业务分发解耦,但业务(login/reg)只是打印日志,没有真实功能。新增的5个文件(db.h、db.cpp、user.hpp、usermodel.hpp、usermodel.cpp)加入了数据持久化层,实现了真正的用户注册(把用户名、密码存到
前言
先看看新增的目录与源文件:
之前的6个文件(ChatServer、ChatService、public.h、main.cpp)构建了一个高性能网络框架 + 业务分发解耦,但业务(login/reg)只是打印日志,没有真实功能。
新增的5个文件(db.h、db.cpp、user.hpp、usermodel.hpp、usermodel.cpp)加入了数据持久化层,实现了真正的用户注册(把用户名、密码存到 MySQL 数据库,并返回自增 id)。
整体架构现在是:
网络层 → 业务控制层 → 模型操作层 → 实体层 → 数据库连接层
好处:完全解耦,网络层完全不感知数据库,业务层只调用模型接口。
当前已实现:注册业务完整闭环(从客户端 JSON → 数据库插入 → 返回新 id)。登录业务后续会扩展。
逐个文件讲解
1. db.h —— 数据库连接封装头文件(底层 DAO)
目的作用:
封装 MySQL C API,提供简单安全的数据库操作接口。
屏蔽底层细节,让上层(UserModel)只需调用 connect/update/query 即可。
代码:
#ifndef DB_H
#define DB_H
#include<mysql/mysql.h>
#include<string>
using namespace std;
//数据库操作类
class MySQL
{
public:
//初始化数据库连接
MySQL();
//释放数据库连接资源
~MySQL();
//连接数据库
bool connect();
//更新操作
bool update(string sql);
//查询操作
MYSQL_RES *query(string sql);
//获取连接
MYSQL* getConnection();
private:
MYSQL *_conn;
};
#endif
单连接设计:每个 MySQL 对象持有一个连接(局部创建,用完析构关闭)。
日志:失败时用 muduo::LOG_INFO 记录。
字符集:connect 成功后执行 “set names gbk” 防中文乱码。
2. db.cpp —— 数据库连接实现
目的作用:
实现 db.h 中声明的方法,真正连接和执行 SQL。
实现细节:
硬编码配置:static string 定义主机、用户、密码、库名(本地开发方便)。
connect():调用 mysql_real_connect,成功后 set names gbk 并日志。
update():mysql_query 执行,非0失败 → 日志 + false。
query():同上,返回 mysql_use_result(流式读取,省内存)。
资源管理:析构自动 mysql_close。
当前局限:每次操作新建连接(非连接池),高并发下效率低,但简单易懂。
代码:
#include"db.h"
#include<muduo/base/Logging.h>
//数据库配置信息
static string server = "127.0.0.1";
static string user = "root";
static string password = "123456";
static string dbname = "chat";
//初始化数据库连接
MySQL::MySQL()
{
_conn = mysql_init(nullptr);
}
//释放数据库连接资源
MySQL::~MySQL()
{
if(_conn != nullptr) mysql_close(_conn);
}
//连接数据库
bool MySQL::connect()
{
MYSQL *p = mysql_real_connect(_conn,server.c_str(),user.c_str(),password.c_str(),dbname.c_str(),3306,nullptr,0);
if(p != nullptr)
{
//C和C++代码默认的编码字符是ASCII,如果不设置,从MySQL上拉下来的中文显示?
mysql_query(_conn,"set names gbk");
LOG_INFO<<"connect mysql success!";
}
else
{
LOG_INFO<<"connect mysql fail!";
}
return p;
}
//更新操作
bool MySQL::update(string sql)
{
if(mysql_query(_conn,sql.c_str()))
{
LOG_INFO<<__FILE__<<":"<<__LINE__<<":"<<sql<<"更新失败!";
return false;
}
return true;
}
//查询操作
MYSQL_RES* MySQL::query(string sql)
{
if(mysql_query(_conn,sql.c_str()))
{
LOG_INFO<<__FILE__<<":"<<__LINE__<<":"<<sql<<"查询失败!";
return nullptr;
}
return mysql_use_result(_conn);
}
//获取连接
MYSQL* MySQL::getConnection()
{
return _conn;
}
3. user.hpp —— 用户实体类(ORM 实体)
目的作用:
对应数据库 user 表的一条记录,作为数据载体在业务层 ↔ 数据库层之间传递;纯 POJO(Plain Old C++ Object),只存数据 + getter/setter。
代码:
#ifndef USER_H
#define USER_H
#include<string>
using namespace std;
//匹配User表的ORM类
class User
{
public:
User(int id =1,string name="",string pwd="",string state="offline")
{
this->id = id;
this->name = name;
this->password = pwd;
this->state = state;
}
void setId(int id) {this->id = id;}
void setName(string name){this->name = name;}
void setPwd(string pwd) {this->password = pwd;}
void setState(string state) {this->state = state;}
int getId() {return this->id;}
string getName(){return this->name;}
string getPwd() {return this->password;}
string getState() {return this->state;}
private:
int id;
string name;
string password;
string state;
};
#endif
为什么 id 默认 -1:区分“新用户”(未插入)和“已存在用户”。
state 默认 offline:新注册用户默认离线。
4. usermodel.hpp(usermodel.h) —— 用户表操作接口声明
目的作用:
定义 UserModel 类,封装对 user 表的 CRUD 操作;
当前只声明 insert(注册用),后续会扩展 query/update 等。
代码:
#ifndef USERMODEL_H
#define USERMODEL_H
#include"user.hpp"
//User表的数据操作类
class UserModel
{
public:
//User表的增加方法
bool insert(User &user);
};
#endif
职责单一,只操作 user 表,不碰其他。
5. usermodel.cpp —— 用户表插入实现
目的作用:实现注册时的数据库插入,把 User 对象持久化到表中,并写回自增 id
代码:
#include"usermodel.hpp"
#include"db.h"
#include<iostream>
using namespace std;
//User表的增加方法
bool UserModel::insert(User &user)
{
//1 组装sql语句
char sql[1024] = {0};
sprintf(sql,"insert into User(name,password,state)values('%s','%s','%s')",
user.getName().c_str(),user.getPwd().c_str(),user.getState().c_str());
MySQL mysql;
if(mysql.connect())
{
if(mysql.update(sql))
{
//获取插入成功的用户数据生成的主键id
user.setId(mysql_insert_id(mysql.getConnection()));
return true;
}
}
return false;
}
SQL 拼接:简单但有注入风险(后面会优化的)。
写回 id:注册成功后客户端能立刻知道自己的 id。
局部 MySQL 对象:用完自动关闭连接。
文件的整体联系
1.最顶层:网络模块(只负责IO和初步分发)
文件:main.cpp → ChatServer.h / chatserver.cpp(依赖Muduo库)
main.cpp 是程序入口,只负责创建EventLoop、InetAddress、ChatServer对象,调用server.start()和loop.loop()。
ChatServer 是网络主类:
- 构造函数注册Muduo的connectionCallback和messageCallback(绑定onConnection和onMessage)。
- onMessage 是关键入口:收到客户端字节流 → retrieveAllAsString → json::parse → 取出js[“msgid”] → 调用ChatService单例的getHandler(msgid)获取处理器 → 执行处理器(把conn、js、time传进去)。
这一层与其他层的联系:
只依赖业务模块(ChatService单例)。
把所有收到的消息(已转JSON)完全交给业务模块处理。
通过传进来的conn对象,业务模块可以直接发送响应。
完全不依赖数据库层、模型层、实体层(甚至不知道它们存在)。
2.中间层:业务模块(调度中心,根据msgid决定走哪条业务)
文件:public.h + ChatService.h / chatservice.cpp
public.h 定义msgid常量(LOGIN_MSG=1、REG_MSG=2),供网络模块(解析js[“msgid”])和业务模块(_msgHandlerMap注册)共同使用,保证协议一致。
ChatService 是单例(instance()返回静态对象):
- 构造函数注册_msgHandlerMap:LOGIN_MSG → login方法,REG_MSG → reg方法(用std::bind绑定)。
- getHandler(msgid):查找map返回对应handler,如果不存在返回空操作+日志。
- login/reg方法:当前实现是占位(打印日志),但设计上就是在这里调用下层模型操作。
这一层与其他层的联系:
依赖网络模块:handler签名接收conn(用于发送响应)、js(客户端数据)、time。
依赖public.h:用msgid常量注册和分发。
依赖模型操作层(UserModel):在reg/login方法内部会创建UserModel对象,调用insert(或其他后续query/update)。
依赖实体层(User):从js提取字段填充User对象,传给UserModel,之后从User取id或其他字段构造响应。
通过conn发送响应:最终结果回传给网络模块发给客户端。
不直接依赖数据库连接层(MySQL),只通过UserModel间接使用。
3.下层:模型操作层 + 实体层 + 数据库连接层(负责持久化)
文件:usermodel.hpp / usermodel.cpp → user.hpp → db.h / db.cpp
user.hpp 定义User实体类(id、name、password、state + getter/setter),纯数据容器。
usermodel.hpp / usermodel.cpp 定义并实现UserModel类:
- 只提供insert(User &user)(引用传参)。
- insert内部:读取User的name、password、state → 组装SQL → 创建MySQL对象 → connect + update执行 → mysql_insert_id写回User的id → 返回bool。
db.h / db.cpp 定义并实现MySQL类:
- 提供connect、update、query、getConnection、析构关闭。
- 硬编码数据库配置(127.0.0.1、root、123456、chat库)。
这一组内部联系:
UserModel → User:读取字段组SQL,写回id(引用修改)。
UserModel → MySQL:局部创建MySQL对象,调用connect/update/getConnection,函数结束MySQL析构关闭连接。
MySQL内部:db.h声明 + db.cpp实现所有底层API。
User:只被UserModel和业务模块使用,作为数据传递桥梁。
这一层与上层(业务模块)的联系:
只通过接口被业务模块调用(insert返回bool + 修改User)。
完全不依赖业务模块、网络模块(不知道msgid、conn、JSON等概念)。
完结,下一篇会对用户注册业务、用户登录业务等进行实现。
更多推荐


所有评论(0)