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.mzph.cn/news/786263.shtml

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

相关文章

vsCode 刷 leetcode 使用 Cookie 登录

1. 安装插件 打开 vsCode&#xff0c;选择扩展&#xff0c;搜索 leetcode&#xff0c;选择第一个&#xff0c;带有中文力扣字样&#xff0c;安装后重启 2. 切换终端 插件安装成功之后&#xff0c;侧边栏选择 leetcode 菜单&#xff0c;切换终端&#xff0c;选择中文版本&…

jvm 调优的方式

jvm调优 什么是jvm JVM&#xff08;Java虚拟机&#xff09;是Java编程语言的运行环境&#xff0c;它是一个虚拟的计算机&#xff0c;能够将Java字节码&#xff08;.class文件&#xff09;转换为机器码并执行。JVM是Java应用程序和Java平台之间的中间层&#xff0c;它提供了内…

HarmonyOS 应用开发之XML生成、解析与转换

XML&#xff08;可扩展标记语言&#xff09;是一种用于描述数据的标记语言&#xff0c;旨在提供一种通用的方式来传输和存储数据&#xff0c;特别是Web应用程序中经常使用的数据。XML并不预定义标记。因此&#xff0c;XML更加灵活&#xff0c;并且可以适用于广泛的应用领域。 …

APP自动化测试-Appium元素定位之元素等待

一、什么是元素等待 元素等待的意义 在 Appium 自动化测试中,元素等待是一个重要的环节。当脚本执行速度较快,而页面元素尚未加载完成时,就会导致脚本无法定位到元素,从而使执行失败。因此需要设置元素等待,从而增强脚本的健壮性,提高执行效率。元素等待的主要目的是确保…

mybatis和mysql简单的操作

1.查询&#xff08;Select&#xff09;&#xff1a; MyBatis: 注解 Select("SELECT * FROM users WHERE id #{id}") User selectUserById(int id);MyBatis: xml <!-- 定义查询语句 --> <select id"selectById" resultType"User">S…

海康摄像头插件嵌入iframe时视频播放插件位置问题

参考&#xff1a;https://juejin.cn/post/6857670423971758094 原因&#xff1a;没有按照iframe相对位置计算视频插件位置。 解决&#xff1a; $(window).on(resize, resize);function resize(){// 解决iframe中嵌入海康插件初始化问题:// 1. 获取iframe相比于窗口的偏移量;c…

windows or ubuntu mount 文件

windows 实测环境:windows 11, 64 bit. ref: Mount-DiskImage (Storage) | Microsoft Learn Dismount-DiskImage (Storage) | Microsoft Learn Get-DiskImage (Storage) | Microsoft Learn # 查询 Get-DiskImage -ImagePath "f:\wsl\ext4.vhdx"# mount Mount-…

Flutter仿Boss-2.启动页、引导页

简述 在移动应用开发中&#xff0c;启动页和引导页是用户初次接触应用时的重要组成部分&#xff0c;能够提升用户体验和导航用户了解应用功能。本文将介绍如何使用Flutter实现启动页和引导页&#xff0c;并展示相关代码实现。 启动页 启动页是应用的第一个页面&#xff0c;首…

Jenkins首次安装选择推荐插件时出现”No such plugin cloudbees-folder”解决方案

安装Jenkins成功之后&#xff0c;首次启动Jenkins后台管理&#xff0c;进入到安装插件的步骤&#xff0c;选择"推荐安装"&#xff0c;继续下一步的时候出现错误提示&#xff1a; 出现一个错误 安装过程中出现一个错误&#xff1a;No such plugin&#xff1a;cloudb…

db2数据仓库集群的搭建

db2数据仓库集群的搭建 DB2 集群的搭建通常涉及到多个环节&#xff0c;包括网络配置、DB2 软件安装、集群配置和数据库创建等。以下是搭建DB2集群的基本步骤&#xff0c;并不是实际的命令和配置&#xff0c;因为每个环境的具体配置可能会有所不同。 1、网络配置&#xff1a;确…

ubuntu卸载conda

要在Ubuntu系统中卸载Conda&#xff0c;你可以按照以下步骤操作&#xff1a; 打开终端。 运行以下命令来定位Conda安装的根目录&#xff08;通常为~/miniconda或~/anaconda&#xff09;&#xff1a; conda info 查看输出中的base environment&#xff1a;/home/username/mini…

【大数据存储】实验二 HDFS操作实验

实验二 HDFS操作实验 启动Hadoop&#xff0c;执行jps&#xff0c;检查Hadoop相关进程是否启动成功 启动hadoop 执行jps,可以看到名称节点和数据节点&#xff0c;第二名称节点都打开了&#xff0c;则hadoop相关进程启动成功 在本地文件系统“/home”下新建两个文件夹&#xff…

已解决rabbitmq AMQPConnectionClosedException:管道破裂或连接关闭异常的正确解决方法,亲测有效!!!

已解决rabbitmq AMQPConnectionClosedException&#xff1a;管道破裂或连接关闭异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 一、问题分析 二、报错原因 三、解决思路 四、解决方法 五、总结 博主v&#xff1a;XiaoMing_Java 一、…

第20章-IP路由原理

目录 1. 概述 2. 路由表 3. 查表规则 4. 路由来源类型 5. 路由优先级 6. 路由的度量值 7. 路由器写表规则 1. 概述 1. 定义 路由器:异构网络互联机制; 路由:指导路由器如何进行数据发送的路径信息; 路由表:目的地址、下一跳、出接口等; 2. IP连通的条件 沿途的每…

Dapr(一) 基于云原生了解Dapr

(这期先了解Dapr&#xff0c;之后在推出如何搭建Dapr&#xff0c;以及如何使用。) 目录 引言&#xff1a; Service Mesh定义 Service Mesh解决的痛点 Istio介绍 Service Mesh遇到的挑战 分布式应用的需求 Multiple Runtime 理念推导 Dapr 介绍 Dapr 特性 Dapr 核心…

代码随想录刷题day41| 整数拆分不同的二叉搜索树

文章目录 day41学习内容一、 整数拆分2.1、动态规划五部曲1.1.1、 确定dp数组&#xff08;dp table&#xff09;以及下标的含义1.1.2、确定递推公式1.1.3、 dp数组如何初始化1.1.4、确定遍历顺序1.1.5、计算并返回最终结果 1.2、代码 二、不同的二叉搜索树2.1、动态规划五部曲2…

前后台分离nodejs+vue租房信息网站express-94sk3.

本租房管理系统有管理员&#xff0c;租客&#xff0c;屋主三个角色。管理员功能有个人中心&#xff0c;租客管理&#xff0c;屋主管理&#xff0c;房源信息管理&#xff0c;订单信息管理&#xff0c;屋主申诉管理&#xff0c;通知公告管理&#xff0c;留言板管理&#xff0c;系…

pytorch剪枝

原文&#xff1a;https://blog.51cto.com/u_16213398/10059574 Pytorch剪枝实现指南 指南概述 在这篇文章中&#xff0c;我将向你介绍如何在PyTorch中实现模型剪枝。剪枝是一种优化模型的技术&#xff0c;可以帮助减少模型的大小和计算量&#xff0c;同时保持模型的准确性。…

hcip-datacom英文词汇积累简述2

邻居会话初级阶段: down 不工作的: dead 间隔: interval 失效时间间隔: dead interval 努力尝试: attempt 轮询: poll 轮询时间间隔: poll interval 初始化: init 方法: way 交换: exchange 装载: loading 完全地: full 系统名称: sysname 地址: address 回环: loopback 网络: …

VMware使用PowerCLI 修改分布式虚拟交换机的默认上联接口为LAG

简介 创建VMware 分布式交换机vDS 并配置 LACP接口时&#xff0c;然后创建新的默认分布式端口组不会默认使用LACP的上联接口。这意味着当创建新的端口组时&#xff0c;不可避免地会导致没手动修改上联端口的问题&#xff0c;导致网络不通&#xff0c;因为它们无可用的上联端口…