公司网站的设计规划,阿里巴巴网站建设改图片,罗湖商城网站设计制作,怎么做网站弹幕效果跟着施磊老师做C项目#xff0c;施磊老师_腾讯课堂 (qq.com)
本文在此篇博客的基础上继续实现数据模块和业务模块代码#xff1a;
C集群聊天服务器 网络模块业务模块CMake构建项目 笔记 #xff08;上#xff09;-CSDN博客https://blog.csdn.net/weixin_41987016/article…跟着施磊老师做C项目施磊老师_腾讯课堂 (qq.com)
本文在此篇博客的基础上继续实现数据模块和业务模块代码
C集群聊天服务器 网络模块业务模块CMake构建项目 笔记 上-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135991635?spm1001.2014.3001.5501一、mysql 项目数据库和表的设计
myql 项目数据库和表的设计-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135981407?spm1001.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 stateoffline) {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)) {// 获取插入成功的用户数据生成的主键iduser.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行出错了 hehedalinux:~/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等都行。实现了网络模块业务模块以及数据模块的低耦合。
五、记录用户的连接信息以及线程安全问题
在ChatService.hpp文件中private处添加
private:// 存储在线用户的通信连接unordered_mapint,TcpConnectionPtr m_userConnMap;// 定义互斥锁,保证m_userConnMap的线程安全mutex m_connMutex;
修改ChatService.cpp中的login函数在登录成功记录用户连接信息将id和conn数据信息插入m_userConnMap,使用lock_guard使得线程安全
// 处理登录业务 user表:id password
void ChatService::login(const TcpConnectionPtr conn, json js, Timestamp time) {int id js[id].getint();string pwd js[password];User user m_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] 该账号已经登录,请重新输入新账号;conn-send(response.dump());}else{// 登录成功,记录用户连接信息{lock_guardmutex lock(m_connMutex);m_userConnMap.insert({id, conn});}// 登录成功,更新用户状态信息 state: offline onlineuser.setState(online);m_userModel.updateState(user);json response;response[msgid] LOGIN_MSG_ACK;response[errno] 0;response[id] user.getId();response[name] user.getName();conn-send(response.dump());}}else {// 该用户不存在/用户存在但是密码错误,登录失败json response;response[msgid] LOGIN_MSG_ACK;response[errno] 1;response[errmsg] 该用户不存在,您输入用户名或者密码可能错误!;conn-send(response.dump());}
}
六、客户端异常退出业务代码和测试
在ChatService.hpp中添加处理客户端异常退出的函数声明
public:// 处理客户端异常退出void clientCloseException(const TcpConnectionPtr conn);
在ChatService.cpp中编写处理客户端异常退出的函数
// 处理客户端异常退出
void ChatService::clientCloseException(const TcpConnectionPtr conn) {User user;{lock_guardmutex lock(m_connMutex); for(auto it m_userConnMap.begin();it!m_userConnMap.end();it) {if(it-second conn) {// 从map表删除用户的链接信息user.setId(it-first);m_userConnMap.erase(it);break;}}}// 更新用户的状态信息if(user.getId() ! -1) {user.setState(offline);m_userModel.updateState(user);}}
表里原先有Tom登录用户的信息然后我们登录了该账号就从offline状态更新为online状态 按下ctrl],切换到telnet输入quit,此时客户端异常退出也就执行了从online更新为offline 七、离线消息业务代码实现和测试
如果用户登录成功的话查询该用户是否有离线消息desc offlinemessage offlinemessagemodel.hpp
#ifndef OFFLINEMESSAGEMODEL_H
#define OFFLINEMESSAGEMODEL_H
#include string
#include vector
using namespace std;// 提供离线消息表的操作接口方法
class OfflineMsgModel {
public:// 存储用户的离线消息void insert(int userid, string msg);// 删除用户的离线消息void remove(int userid);// 查询用户的离线消息vectorstring query(int userid);
};#endif // OFFLINEMESSAGEMODEL_H
offlinemessagemodel.cpp
#include offlinemessagemodel.hpp
#include db.h
// 存储用户的离线消息
void OfflineMsgModel::insert(int userid, string msg) {// 1.组装sql语句char sql[1024] {0};sprintf(sql, insert into offlinemessage values(%d, %s), userid, msg.c_str());// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}// 删除用户的离线消息
void OfflineMsgModel::remove(int userid) {// 1.组装sql语句char sql[1024] {0};sprintf(sql, delete from offlinemessage where userid %d, userid);// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}// 查询用户的离线消息
vectorstring OfflineMsgModel::query(int userid) {// 1.组装sql语句char sql[1024] {0};sprintf(sql, select message from offlinemessage where userid %d, userid);// 2.执行sql语句Mysql mysql;vectorstring vec;if(mysql.connect()) {MYSQL_RES *res mysql.query(sql);if(res ! nullptr) {// 把userid用户的所有离线消息放入vec中返回MYSQL_ROW row;while((row mysql_fetch_row(res)) ! nullptr) {vec.push_back(row[0]);}mysql_free_result(res);return vec;}}return vec;
}在chatservice.hpp中添加
#include offlinemessagemodel.hpp// 聊天服务器业务类
class ChatService {
private: OfflineMsgModel m_offlineMsgModel;
}
chatservice.cpp
// 处理登录业务 user表:id password
void ChatService::login(const TcpConnectionPtr conn, json js, Timestamp time) {int id js[id].getint();string pwd js[password];User user m_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] 该账号已经登录,请重新输入新账号;conn-send(response.dump());}else{// 登录成功,记录用户连接信息{lock_guardmutex lock(m_connMutex);m_userConnMap.insert({id, conn});}// 登录成功,更新用户状态信息 state: offline onlineuser.setState(online);m_userModel.updateState(user);json response;response[msgid] LOGIN_MSG_ACK;response[errno] 0;response[id] user.getId();response[name] user.getName();// 查询该用户是否有离线消息vectorstring vec m_offlineMsgModel.query(id);if(!vec.empty()) {response[offlinemsg] vec;// 读取该用户的离线消息后,把该用户的所有离线消息删除掉m_offlineMsgModel.remove(id);}conn-send(response.dump());}}else {// 该用户不存在/用户存在但是密码错误,登录失败json response;response[msgid] LOGIN_MSG_ACK;response[errno] 1;response[errmsg] 该用户不存在,您输入用户名或者密码可能错误!;conn-send(response.dump());}
}// 一对一聊天业务
void ChatService::oneChat(const TcpConnectionPtr conn, json js, Timestamp time) {int toid js[to].getint();{lock_guardmutex lock(m_connMutex);auto it m_userConnMap.find(toid);if(it ! m_userConnMap.end()) {// toid在线,转发消息 服务器主动推送消息给toid用户it-second-send(js.dump());return;}}// toid不在线,存储离线消息m_offlineMsgModel.insert(toid, js.dump());
} 八、服务器异常退出处理代码和测试
main.cpp
#include chatserver.hpp
#include chatservice.hpp
#include iostream
#include signal.h
using namespace std;// 处理服务器ctrlc结束后,重置user的状态信息
void resetHandler(int) {ChatService::getInstance()-reset();exit(0);
}int main() {signal(SIGINT,resetHandler);...
}
在chatservice.hpp添加reset()方法声明服务器异常业务重置方法
// 服务器异常业务重置方法
void reset(); 在chatservice.cpp中编写reset()方法
// 服务器异常业务重置方法
void ChatService::reset() {// 把online状态的用户,设置成offlinem_userModel.resetState();
}在usermodel.hpp中添加重置用户的状态信息resetState方法声明
// 重置用户的状态信息
void resetState(); 在usermodel.cpp中编写resetState()方法
// 重置用户的状态信息
void UserModel::resetState() {// 1.组装sql语句char sql[1024] update user set state offline where state online;// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
} ctrlc终止服务 九、添加好友业务代码和测试
public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*server和client的公共文件
*/
enum EnMsgType {LOGIN_MSG 1, // 登录消息LOGIN_MSG_ACK, // 登录响应消息REG_MSG, // 注册消息REG_MSG_ACK, // 注册响应消息ONE_CHAT_MSG, // 聊天消息ADD_FRIEND_MSG, // 添加好友消息
};
#endif // PUBLIC_H
friendmodel.hpp
#ifndef FRIENDMODEL_H
#define FRIENDMODEL_H#include user.hpp
#include vector
using namespace std;// 维护好友信息的操作接口方法
class FriendModel {
public:// 添加好友关系void insert(int userid, int friendid);// 返回用户好友列表 friendid vectorUser query(int userid);
};#endif // FRIENDMODEL_H
friendmodel.cpp
#include friendmodel.hpp
#include db.h
// 添加好友关系
void FriendModel::insert(int userid, int friendid) {// 1.组装sql语句char sql[1024] {0};sprintf(sql, insert into friend values (%d, %d), userid, friendid);// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}
// 返回用户好友列表 friendid
vectorUser FriendModel::query(int userid) {// 1.组装sql语句char sql[1024] {0};sprintf(sql, select a.id, a.name, a.state from user a inner join friend b on b.friendid a.id where b.userid %d, userid); vectorUser vec;Mysql mysql;if(mysql.connect()) {MYSQL_RES * res mysql.query(sql);if(res ! nullptr) {// 把userid用户的所有离线消息放入vec中返回MYSQL_ROW row;while((row mysql_fetch_row(res)) ! nullptr) {User user;user.setId(atoi(row[0])); // iduser.setName(row[1]); // nameuser.setState(row[2]); // statevec.push_back(user);}mysql_free_result(res); // 释放资源return vec;}}return vec;
}// select a.id,a.name,a.state from user a inner join
// friend b on b.friendid a.id
// where b.userid %d
chatservice.hpp
// 聊天服务器业务类
class ChatService {
public:// 添加好友业务void addFriend(const TcpConnectionPtr conn,json js,Timestamp time);
private:FriendModel m_friendModel;
}
chatservice.cpp
// 注册消息以及对应的Handler回调操作
ChatService::ChatService() {m_msgHandlerMap.insert({LOGIN_MSG,std::bind(ChatService::login, this, _1, _2, _3)}); m_msgHandlerMap.insert({REG_MSG,std::bind(ChatService::reg, this, _1, _2, _3)}); m_msgHandlerMap.insert({ONE_CHAT_MSG,std::bind(ChatService::oneChat, this, _1, _2, _3)});m_msgHandlerMap.insert({ADD_FRIEND_MSG,std::bind(ChatService::addFriend, this, _1, _2, _3)});
}// 处理登录业务 user表:id password
void ChatService::login(const TcpConnectionPtr conn, json js, Timestamp time) {int id js[id].getint();string pwd js[password];User user m_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] 该账号已经登录,请重新输入新账号;conn-send(response.dump());}else{// 登录成功,记录用户连接信息{lock_guardmutex lock(m_connMutex);m_userConnMap.insert({id, conn});}// 登录成功,更新用户状态信息 state: offline onlineuser.setState(online);m_userModel.updateState(user);json response;response[msgid] LOGIN_MSG_ACK;response[errno] 0;response[id] user.getId();response[name] user.getName();// 查询该用户是否有离线消息vectorstring vec m_offlineMsgModel.query(id);if(!vec.empty()) {response[offlinemsg] vec;// 读取该用户的离线消息后,把该用户的所有离线消息删除掉m_offlineMsgModel.remove(id);}// 查询该用户的好友信息并返回vectorUseruserVec m_friendModel.query(id);if(!userVec.empty()) {vectorstring 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;}conn-send(response.dump());}}else {// 该用户不存在/用户存在但是密码错误,登录失败json response;response[msgid] LOGIN_MSG_ACK;response[errno] 1;response[errmsg] 该用户不存在,您输入用户名或者密码可能错误!;conn-send(response.dump());}
}// 添加好友业务 msgid id friendid
void ChatService::addFriend(const TcpConnectionPtr conn, json js, Timestamp time) {int userid js[id].getint();int friendid js[friendid].getint();// 存储好友信息m_friendModel.insert(userid, friendid);
} 十、模拟QQ好友添加呵呵哒改造
public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*server和client的公共文件
*/
enum EnMsgType {LOGIN_MSG 1, // 登录消息LOGIN_MSG_ACK, // 登录响应消息REG_MSG, // 注册消息REG_MSG_ACK, // 注册响应消息ONE_CHAT_MSG, // 聊天消息ADD_FRIEND_REQ_MSG, // 添加好友请求消息ADD_FRIEND_MSG_ACK, // 添加好友响应消息
};
#endif // PUBLIC_H chatservice.hpp
// 聊天服务器业务类
class ChatService {
public:// 添加好友业务请求void addFriendRequest(const TcpConnectionPtr conn,json js,Timestamp time);// 添加好友业务响应void addFriendResponse(const TcpConnectionPtr conn,json js,Timestamp time);
}
chatservice.cpp
// 注册消息以及对应的Handler回调操作
ChatService::ChatService() {m_msgHandlerMap.insert({LOGIN_MSG,std::bind(ChatService::login, this, _1, _2, _3)}); m_msgHandlerMap.insert({REG_MSG,std::bind(ChatService::reg, this, _1, _2, _3)}); m_msgHandlerMap.insert({ONE_CHAT_MSG,std::bind(ChatService::oneChat, this, _1, _2, _3)});m_msgHandlerMap.insert({ADD_FRIEND_REQ_MSG,std::bind(ChatService::addFriendRequest, this, _1, _2, _3)}); m_msgHandlerMap.insert({ADD_FRIEND_MSG_ACK,std::bind(ChatService::addFriendResponse, this, _1, _2, _3)});
}// 添加好友业务请求
void ChatService::addFriendRequest(const TcpConnectionPtr conn, json js, Timestamp time) {int userid js[id].getint();int friendid js[friendid].getint();json response;response[msgid] ADD_FRIEND_REQ_MSG;response[msg] Please add me as a friend, thank you!;response[from] userid;response[to] friendid;// std::cout来到这里了std::endl;oneChat(conn,response,time);
}// 添加好友业务 msgid id friendid
void ChatService::addFriendResponse(const TcpConnectionPtr conn, json js, Timestamp time) {int userid js[id].getint();int friendid js[friendid].getint();bool flag js[flag].getbool();json response;response[msgid] ADD_FRIEND_MSG_ACK;response[from] userid;response[to] friendid;if(flag) {response[msg] I very happy to make friends with you!!!;m_friendModel.insert(userid, friendid);}else{response[msg] I am very sorry, you are not my friend!!!;}oneChat(conn,response,time);
}
情景一id1的用户 想要和 id2的在线用户交个朋友向其发送好友请求id2的在线用户响应同意互为好友 先登录两个账号
{msgid:1,id:1,password:1024} // 登录
{msgid:1,id:2,password:520} // 登录 1id1的用户发送好友请求
{msgid:6,id:1,friendid:2} // 发送好友请求 2响应好友请求允许
{msgid:7,id:2,friendid:1,flag:true} // 响应好友请求允许 情景二id1的用户 想要和 id3的离线用户交个朋友向其发送好友请求id2的离线用户登录后在线时看到离线消息响应不同意互为好友
1发送好友请求 先登录一个账号id1的用户
{msgid:1,id:1,password:1024} // 登录 1发送好友请求
{msgid:6,id:1,friendid:3} // 发送好友请求 2响应好友请求拒绝
再登录id3的用户账号查看到来自id1用户发来的离线消息
{msgid:1,id:3,password:1314} // 登录 id3的用户响应好友请求拒绝
{msgid:7,id:3,friendid:1,flag:false} // 响应好友请求拒绝