跟着施磊老师做C++项目,施磊老师_腾讯课堂 (qq.com)
本文在此篇博客的基础上继续实现数据模块和业务模块代码:
C++集群聊天服务器 网络模块+业务模块+CMake构建项目 笔记 (上)-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135991635?spm=1001.2014.3001.5501一、mysql 项目数据库和表的设计
myql 项目数据库和表的设计-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135981407?spm=1001.2014.3001.5501二、mysql数据库代码封装
- include/public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*
server和client的公共文件
*/
enum EnMsgType {
LOGIN_MSG = 1, // 登录消息
LOGIN_MSG_ACK, // 登录响应消息
REG_MSG, // 注册消息
REG_MSG_ACK // 注册响应消息
};
#endif // PUBLIC_H
- include/server/db/db.h
#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 *m_conn;
};
#endif // DB_H
src/server/db/db.cpp
#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() {
m_conn = mysql_init(nullptr);
// 这里相当于只是给它开辟了一块存储连接数据的资源空间
}
// 释放数据库连接资源
Mysql::~Mysql() {
if(m_conn != nullptr) {
mysql_close(m_conn);
}
// 析构的时候把这块资源空间用mysql_close掉
}
// 连接数据库
bool Mysql::connect() {
MYSQL *p = mysql_real_connect(m_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(m_conn, "set names gbk");
LOG_INFO << "connect mysql success!!!";
} else{
LOG_INFO << "connect mysql failed!!!";
}
return p;
}
// 更新操作
bool Mysql::update(string sql) {
if(mysql_query(m_conn, sql.c_str())) {
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql <<"更新失败!";
return false;
}
return true;
}
// 查询操作
MYSQL_RES* Mysql::query(string sql) {
if(mysql_query(m_conn, sql.c_str())) {
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql <<"查询失败!";
return nullptr;
}
return mysql_use_result(m_conn);
}
// 获取连接
MYSQL* Mysql::getConnection() {
return m_conn;
}
三、Model数据层代码框架设计
- include/server/user.hpp
#ifndef USER_H
#define USER_H
#include <string>
using namespace std;
// 匹配User表的ORM类
class User {
public:
User(int id=-1, string name="", string password="", string state="offline") {
m_id = id;
m_name = name;
m_password = password;
m_state = state;
}
void setId(int id) { m_id = id; }
void setName(string name) { m_name = name; }
void setPwd(string pwd) { m_password = pwd; }
void setState(string state) { m_state = state; }
int getId() const { return m_id; }
string getName() const { return m_name; }
string getPwd() const { return m_password; }
string getState() const { return m_state; }
private:
int m_id;
string m_name;
string m_password;
string m_state;
};
#endif // USER_H
- include/server/usermodel.hpp
#ifndef USERMODEL_H
#define USERMODEL_H
#include "user.hpp"
// User表的数据操作类
class UserModel {
public:
// user表的增加方法
bool insert(User& user);
// 根据用户号码查询用户信息
User query(int id);
// 更新用户的状态信息
bool updateState(User user);
};
#endif // USERMODEL_H
- src/server/usermodel.cpp
#include "usermodel.hpp"
#include "db.h"
#include <iostream>
// User表的增加方法
bool UserModel::insert(User &user) {
// 1.组装sql语句
char sql[1024] = {0};
std::sprintf(sql,"insert into user(name,password,state) values('%s','%s', '%s')",
user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());
// 2.执行sql语句
Mysql mysql;
if(mysql.connect()) {
if(mysql.update(sql)) {
// 获取插入成功的用户数据生成的主键id
user.setId(mysql_insert_id(mysql.getConnection()));
return true;
}
}
return false;
}
// 根据用户号码查询用户信息
User UserModel::query(int id) {
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql,"select * from user where id = %d", id);
// 2.执行sql语句
Mysql mysql;
if(mysql.connect()) {
MYSQL_RES* res = mysql.query(sql);
if(res != nullptr) {
MYSQL_ROW row = mysql_fetch_row(res);
if(row != nullptr) {
User user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPwd(row[2]);
user.setState(row[3]);
// 释放资源
mysql_free_result(res);
return user;
}
}
}
return User();
}
// 更新用户的状态信息
bool UserModel::updateState(User user) {
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql,"update user set state = '%s' where id = %d",
user.getState().c_str(), user.getId());
// 2.执行sql语句
Mysql mysql;
if(mysql.connect()) {
if(mysql.update(sql)) {
return true;
}
}
return false;
}
四、CMake 构建项目
- src/server/CMakeLists.txt
# 定义了一个SRC_LIST变量 包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)
aux_source_directory(./db DB_LIST)
# 指定生成可执行文件
add_executable(ChatServer ${SRC_LIST} ${DB_LIST})
# 指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient pthread)
- src/CMakeLists.txt
add_subdirectory(server)
- 和src,include,thirdparty同级目录的CMakeLists.txt
cmake_minimum_required(VERSION 3.28.0)
project(chat)
# 配置编译选项
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)
# 配置可执行文件生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 配置头文件搜索路径
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/include/server)
include_directories(${PROJECT_SOURCE_DIR}/include/server/db)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty)
# 加载子目录
add_subdirectory(src)
cmake -B build
cmake --build build
1.测试注册:
telnet 127.0.0.1 6000
{"msgid":3,"name":"heheda","password":"1024"} // 注册
{"msgid":3,"name":"Tom","password":"520"} // 注册
{"msgid":3,"name":"Jerry","password":"1314"} // 注册
2.测试登录:
(1)未登录
(2) 已经登录
telnet 127.0.0.1 6000
{"msgid":1,"id":4,"password":"1024"}
telnet 127.0.0.1 6000
{"msgid":1,"id":4,"password":"1024"}
(3)登录失败
3.gdb排错练习
比如输入以下这句,其实"id":5才对,但是如果误输入的会引起核心中断,如何排查错误呢?
{"msgid":1,"id":"5","password":"520"}
>>gdb调试,比如我们怀疑可能是chatservice.cpp的20行出错了
heheda@linux:~/Linux/Server$ gdb ./bin/ChatServer
(gdb) break chatservice.cpp 20
(gdb) run
telnet 127.0.0.1 6000
输入:
{"msgid":1,"id":"5","password":"520"}
检查出错误了:
reason: [json.exception.type_error.302] type must be number, but is string
故我们把
{"msgid":1,"id":"5","password":"520"}
修改为以下:
{"msgid":1,"id":5,"password":"520"}
总结:客户端发送过来一个注册的业务,先从最开始的网络,再通过事件的分发,到业务层的相关的handler处理注册,接着访问底层的model。其中在业务类设计,这里看到的都是对象,方便你把底层的数据模块改成你想要的,例如mysql,sql,oracle,mongoDB等都行。实现了网络模块,业务模块以及数据模块的低耦合。