C++项目——集群聊天服务器项目(十一)服务器异常退出与添加好友业务

本节来实现C++集群聊天服务器项目中的服务器异常退出与添加好友业务,一起来试试吧

一、服务器异常退出

在Linux环境下,我们在服务器端使用CTRL+C结束程序执行,即使用CTRL+C让服务器异常退出,这样的后果是本应登录服务器的用户在数据库中在线状态未及时修改为离线,在下次重新登录时会显示用户已登录。

为解决上述问题,编写服务器异常退出函数,只要调用用户操作对象_userModel将所有在线用户状态修改为离线即可

1.1 服务器异常退出代码

代码实现比较简单,在chatservice.hpp中声明函数,在chatservice.cpp中定义

    // 服务端异常,业务重置方法void reset();
// 服务端异常,业务重置方法
void ChatService::reset()
{// 把所有online用户的状态设置为offline_userModel.resetState();
}

在main函数,调用业务模块处理服务器异常退出的函数,signal信号捕获突发事件

#include "chatserver.hpp"
#include "chatservice.hpp"
#include <signal.h>//处理服务器ctrl+c结束程序后,重置用户user的状态信息
void resetHandler(int){ChatService::instance()->reset();exit(0);
}int main()
{signal(SIGINT,resetHandler);EventLoop loop;InetAddress addr("127.0.0.1", 6000);ChatServer server(&loop, addr, "ChatServer");server.start();loop.loop();return 0;
}

1.2 功能实现

使用张三的账号登录服务器

张三已经在线

服务器ctrl+c异常退出

数据库张三状态已修改为离线。

服务器异常退出业务验证成功!

二、添加好友

服务器要实现好友聊天,那么就需要添加好友,才可依据好友id号与您的好友进行通信,本节来实现添加好友操作

2.1 添加好友步骤

(1)从json对象中获取用户userid和欲添加的好友friendid

(2)定义FriendModel类对象操作好友表,编写添加好友与查询好友列表两个函数

// 添加好友关系bool insert(int userid, int friendid);// 返回用户好友列表vector<User> query(int userid);

使用FriendModel类对象向数据库好友表中插入好友关系

(3)添加好友成功,返回响应信息、userid和friendid;添加失败,传回响应信息即可

// 添加好友业务             {"msgid":6,"id":22,"name":"Jiao","friendid":13}
void ChatService::addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time)
{int userid = js["id"].get<int>();               //获取用户idint friendid = js["friendid"].get<int>();       //获取好友id// 存储消息bool state = _friendModel.insert(userid, friendid);if(state){//添加好友成功!json response;response["msgid"] = ADD_FRIENG_MSG_ACK;response["error"] = 0;response["errmsg"]="成功添加好友!";response["id"] = userid;response["friendid"] = friendid;conn->send(response.dump());}else{//添加好友失败json response;response["msgid"] = ADD_FRIENG_MSG_ACK;response["error"] = 1;response["errmsg"]="添加好友失败!";conn->send(response.dump());}
}

(4)在登录业务中,返回用户离线消息后,也返回用户的好友表,用户可以选择与指定好友进行聊天啦

 // 查询该用户的好友信息并返回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;}

2.2 添加好友代码

在public.hpp,msgid=6表示添加好友

#ifndef PUBLIC_H
#define PUBLIC_H/*
server和client的公共文件    MSGID值
*/
enum EnMsgType
{LOGIN_MSG = 1, // 1:登录消息LOGIN_MSG_ACK, // 2:登录响应消息REG_MSG,     // 3:注册消息REG_MSG_ACK, // 4:注册响应消息ONE_CHAT_MSG,   // 5:聊天消息ADD_FRIENG_MSG, // 6:添加好友消息ADD_FRIENG_MSG_ACK, // 7:添加好友响应消息
};#endif

在include/server/model中创建friendmodel.hpp,定义操作好友表的类FriendModel

#ifndef FRIENDMODEL_H
#define FRIENDMODEL_H#include "user.hpp"
#include <vector>
using namespace std;// 维护好友信息的操作接口方法
class FriendModel
{
public:// 添加好友关系bool insert(int userid, int friendid);// 返回用户好友列表vector<User> query(int userid);
};#endif

在src/server/friendmodel.cpp中进行实现

#include "friendmodel.hpp"
#include "db.hpp"// 添加好友关系
bool FriendModel::insert(int userid, int friendid)
{char sql[1024] = {0};sprintf(sql, "insert into friend values(%d, %d)", userid, friendid);    //向friend表插入好友关系MySQL mysql;if (mysql.connect()){mysql.update(sql);return true;}return false;
}// 返回用户好友列表
vector<User> FriendModel::query(int userid)
{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);   //多表联合查询,返回userid的好友列表信息vector<User> Friendvec;             //定义vector数组,存放userid的好友对象MySQL mysql;if (mysql.connect()){MYSQL_RES *res = mysql.query(sql);if (res != nullptr){MYSQL_ROW row;while((row = mysql_fetch_row(res)) != nullptr){User user;user.setId(atoi(row[0]));user.setName(row[1]);user.setState(row[2]);Friendvec.push_back(user);}mysql_free_result(res);return Friendvec;}}return Friendvec;
}

业务模块chatservice.hpp创建添加好友业务函数

#ifndef CHATSERVICE_H
#define CHATSERVICE_H#include <muduo/net/TcpConnection.h>
#include <unordered_map>            //一个消息ID映射一个事件处理
#include <functional>
#include <mutex>
using namespace std;
using namespace muduo;
using namespace muduo::net;#include "usermodel.hpp"
#include "offlinemessagemodel.hpp"
#include "friendmodel.hpp"
#include "json.hpp"
using json = nlohmann::json;// 表示处理消息的事件回调方法类型,事件处理器,派发3个东西
using MsgHandler = std::function<void(const TcpConnectionPtr &conn, json &js, Timestamp)>;// 聊天服务器业务类
class ChatService
{
public:// 获取单例对象的接口函数static ChatService *instance();// 处理登录业务void login(const TcpConnectionPtr &conn, json &js, Timestamp time);// 处理注册业务void reg(const TcpConnectionPtr &conn, json &js, Timestamp time);// 处理点对点聊天消息void oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time);// 添加好友业务void addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time);// 处理客户端异常退出void clientCloseException(const TcpConnectionPtr &conn);// 服务端异常,业务重置方法void reset();// 获取消息对应的处理器MsgHandler getHandler(int msgid);private:ChatService(); // 单例// 存储消息id和其对应的业务处理方法,消息处理器的一个表,写消息id对应的处理操作unordered_map<int, MsgHandler> _msgHandlerMap;// 存储用户的通信连接unordered_map<int, TcpConnectionPtr> _userConnMap;// 定义互斥锁,保证_userConnMap的线程安全mutex _connMutex;// 数据操作类对象UserModel _userModel;                   //用户操作对象offlineMsgModel _offlineMsgModel;       //离线消息操作对象FriendModel _friendModel;               //好友操作对象
};#endif

chatservice.cpp中实现,在构造函数中插入消息处理器map表,绑定相应的事件回调

#include "chatservice.hpp"
#include "public.hpp"
#include <muduo/base/Logging.h> //muduo的日志
using namespace std;
using namespace muduo;// 获取单例对象的接口函数
ChatService *ChatService::instance()
{static ChatService service;return &service;
}// 构造方法,注册消息以及对应的Handler回调操作
ChatService::ChatService()
{// 用户基本业务管理相关事件处理回调注册_msgHandlerMap.insert({LOGIN_MSG, std::bind(&ChatService::login, this, _1, _2, _3)});_msgHandlerMap.insert({REG_MSG, std::bind(&ChatService::reg, this, _1, _2, _3)});_msgHandlerMap.insert({ONE_CHAT_MSG, std::bind(&ChatService::oneChat, this, _1, _2, _3)});_msgHandlerMap.insert({ADD_FRIENG_MSG, std::bind(&ChatService::addFriend, this, _1, _2, _3)});
}// 获取消息对应的处理器
MsgHandler ChatService::getHandler(int msgid)
{// 记录错误日志,msgid没有对应的事件处理回调auto it = _msgHandlerMap.find(msgid);if (it == _msgHandlerMap.end()) // 找不到{// 返回一个默认的处理器,空操作,=按值获取return [=](const TcpConnectionPtr &conn, json &js, Timestamp){LOG_ERROR << "msgid:" << msgid << " can not find handler!"; // muduo日志会自动输出endl};}else // 成功的话{return _msgHandlerMap[msgid]; // 返回这个处理器}
}// 处理登录业务     {"msgid":1,"id":22,"password":"123456"}
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"] = "该账号已经登录,请重新输入新账号";conn->send(response.dump()); // 回调 ,返回json字符串}else{{// 登陆成功,记录用户连接lock_guard<mutex> lock(_connMutex);_userConnMap.insert(make_pair(id, conn));}// 登录成功,更新用户状态信息user.setState("online");_userModel.updateState(user);json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 0;response["errmsg"] = "登录成功!";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;}conn->send(response.dump()); // 回调 ,返回json字符串}}else{// 用户名或者密码错误json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 1;response["errmsg"] = "用户名或者密码错误";conn->send(response.dump()); // 回调 ,返回json字符串}
}// 处理注册业务         {"msgid":3,"name":"Jiao","password":"123456"}
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time)
{string name = js["name"];    // 获取名字string pwd = js["password"]; // 获取密码User user; // 创建用户对象user.setName(name);user.setPwd(pwd);bool state = _userModel.insert(user); // 新用户的插入if (state)                            // 插入成功{// 注册成功json response;response["msgid"] = REG_MSG_ACK;response["errno"] = 0;response["id"] = user.getId();conn->send(response.dump()); // 回调 ,返回json字符串}else // 插入失败{// 注册失败json response;response["msgid"] = REG_MSG_ACK;response["errno"] = 1;conn->send(response.dump()); // 回调 ,返回json字符串}
}// 处理客户端异常退出
void ChatService::clientCloseException(const TcpConnectionPtr &conn)
{User user;{lock_guard<mutex> lock(_connMutex);for (auto it = _userConnMap.begin(); it != _userConnMap.end(); it++)    //遍历{if (it->second == conn){user.setId(it->first);// 从_userConnMap表中删除用户的连接信息_userConnMap.erase(it);break;}}}// 更新用户的状态信息if (user.getId() != -1){user.setState("offline");_userModel.updateState(user);}
}// 处理点对点聊天消息
void ChatService::oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time)
{int to_id = js["to"].get<int>();                //获取聊天对象id号{lock_guard<mutex> lock(_connMutex);auto it = _userConnMap.find(to_id);         //查看聊天对象是否在线if (it != _userConnMap.end()){// 聊天对象在线,转发消息,服务器主动推动消息给to_id用户it->second->send(js.dump());return;}}// 聊天对象不在线,插入离线消息表中_offlineMsgModel.insert(to_id, js.dump());
}// 服务端异常,业务重置方法
void ChatService::reset()
{// 把所有online用户的状态设置为offline_userModel.resetState();
}// 添加好友业务             {"msgid":6,"id":22,"name":"Jiao","friendid":13}
void ChatService::addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time)
{int userid = js["id"].get<int>();               //获取用户idint friendid = js["friendid"].get<int>();       //获取好友id// 存储消息bool state = _friendModel.insert(userid, friendid);if(state){//添加好友成功!json response;response["msgid"] = ADD_FRIENG_MSG_ACK;response["error"] = 0;response["errmsg"]="成功添加好友!";response["id"] = userid;response["friendid"] = friendid;conn->send(response.dump());}else{//添加好友失败json response;response["msgid"] = ADD_FRIENG_MSG_ACK;response["error"] = 1;response["errmsg"]="添加好友失败!";conn->send(response.dump());}
}

2.3功能验证

登录张三账号,id为13的张三要添加id为15的李四为好友

好友表显示:添加成功

张三再次登录时,会返回张三的好友列表

添加好友业务验证成功!

感兴趣的小伙伴一起来试一下吧~

如果有问题还请及时联系我哦,感谢~

三、项目流程

 1、项目环境搭建 

C++项目——集群聊天服务器项目(一)项目介绍、环境搭建、Boost库安装、Muduo库安装、Linux与vscode配置_c++集群聊天服务器-CSDN博客

2、Json第三方库介绍

C++项目——集群聊天服务器项目(二)Json第三方库-CSDN博客

3、muduo网络库介绍

C++项目——集群聊天服务器项目(三)muduo网络库-CSDN博客

4、MySQL数据库创建

C++项目——集群聊天服务器项目(四)MySQL数据库-CSDN博客

5、网络模块与业务模块代码编写

C++项目——集群聊天服务器项目(五)网络模块与业务模块-CSDN博客

6、MySQL模块编写

C++项目——集群聊天服务器项目(六)MySQL模块-CSDN博客

7、Model层设计、注册业务实现

C++项目——集群聊天服务器项目(七)Model层设计、注册业务实现-CSDN博客

8、用户登录业务

C++项目——集群聊天服务器项目(八)用户登录业务-CSDN博客

9、客户端异常退出业务

C++项目——集群聊天服务器项目(九)客户端异常退出业务-CSDN博客

10、点对点聊天业务

C++项目——集群聊天服务器项目(十)点对点聊天业务-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

org.junit.runners.model.InvalidTestClassError:1. No runnable methods

你们好&#xff0c;我是金金金。 场景 很简单的一个测试方法 我的boot版本&#xff1a;2.7.18 依赖 报错信息 排查 看报错信息提示无效的测试类&#xff0c;没有可运行的方法 看了下依赖信息&#xff0c;引入spring-boot-starter-test依赖也自动的引入了juni5依赖&#xff0…

递归究竟是什么?如何快速编写正确的递归代码? —— 力扣经典面试题详解

递归究竟是什么&#xff1f;如何快速编写正确的递归代码&#xff1f; —— 力扣经典面试题详解 一、递归1.1 什么是递归&#xff1f;1.2 为什么会用到递归&#xff1f;1.3 如何快速编写正确的递归代码&#xff1f; 二、力扣相关笔试题解析[面试题 08.06. 汉诺塔问题](https://l…

本地运行github上下载的项目--接Git入门篇

1.了解项目 这是一个基于Spring Boot 和 Mybatis Plus 构建的Java项目&#xff0c;很经典的外卖项目&#xff0c;参考b站的黑马瑞吉外卖。 2.构建项目 SpringBoot项目&#xff0c;首先下载一些常见的项目要求的组件。然后配置如下&#xff1a; 看README&#xff0c;在阅读该…

mongodb sharding分片模式的集群数据库,日志治理缺失导致写入数据库报错MongoWriteConcernException的问题总结(下)

一、接着上文 上文介绍了mongodb sharding的分片集群搭建&#xff0c;本文侧重于讲述日志治理。 这里使用linux自带的日志治理工具logrotate&#xff0c;无论是哪个端口的进程&#xff0c;其日志治理方式类似。 查看/data目录下的文件大小&#xff0c; du -hs *二、Logrota…

从零开始学大模型 | 你必须要知道的三种大模型架构可视化的方法!

引言 大模型架构可视化对于理解、解释和优化这些复杂模型具有重要意义和作用&#xff0c;主要包括以下两个方面&#xff1a; 提高模型透明度和可解释性通过可视化&#xff0c;我们能够直观地观察到模型内部的计算过程、参数分布、特征提取等&#xff0c;从而更好地理解模型是如…

前端学习记录——关于代码规范和代码格式化

代码规范&#xff1a;&#x1f449;详情 代码格式化&#xff1a;&#x1f449;详情 如何配置eslint&#xff1a; eslint配置文件&#xff1a;.eslintrc、.eslint.json 。定义代码风格规则和错误检查规则。eslint插件&#xff1a;应用eslint规则&#xff0c;实时检测代码规范…

【漏洞复现】通天星CMSV6弱口令漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

Linux_应用篇(02) 文件 I/O 基础

本章给大家介绍 Linux 应用编程中最基础的知识&#xff0c;即文件 I/O&#xff08;Input、 Outout&#xff09; &#xff0c; 文件 I/O 指的是对文件的输入/输出操作&#xff0c;说白了就是对文件的读写操作&#xff1b; Linux 下一切皆文件&#xff0c;文件作为 Linux 系统设计…

【C++】list介绍

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. list介绍2. list的构造3. ist iterator的使用4. capacity5. element access6. modifiers7. 迭代器失效8. Operations8.1 reverse8.2 sort8.3 unique8.4 splice 1. list介绍 list是可以在常数范围内在任意位置进行插…

解决pandas的concat表格错位问题。表格拼接错误。

两个表格横向拼接但没拼到一块儿 如图&#xff1a; 图片来源&#xff1a;https://m.163.com/dy/article/HM6T6DRQ0516W3V7.html 拼接错位了。 解决方法&#xff1a;重置左边表格索引。 import pandas as pd df1df1.reset_index(dropTrue) df_newpd.concat([df1,df2],axiis1)…

【算法-PID】

算法-PID ■ PID■ 闭环原理■ PID 控制流程■ PID 比例环节&#xff08;Proportion&#xff09;■ PID 积分环节&#xff08;Integral&#xff09;■ PID 微分环节&#xff08;Differential&#xff09; ■ 位置式PID&#xff0c;增量式PID介绍■ 位置式 PID 公式■ 增量式 PI…

机器学习-生存分析:基于QHScrnomo模型的乳腺癌患者风险评估与个性化预测

一、引言 乳腺癌作为女性常见的恶性肿瘤之一&#xff0c;对女性健康构成威胁。随着医疗技术的不断进步&#xff0c;个性化医疗逐渐成为乳腺癌治疗的重要方向。通过深入研究乳腺癌患者的风险评估和个性化预测&#xff0c;可以帮助医生更准确地制定治疗方案&#xff0c;提高治疗效…