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>();               //获取用户id
    int 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>();               //获取用户id
    int 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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux实验室】NFS、DHCP的搭建

NFS、DHCP的搭建 1、nfs服务搭建及测试什么是NFS&#xff1f;环境准备服务端机器安装nfs-utils和rpcbind包启动NFS服务创建/data/NFSdata目录&#xff0c;配置nfs文件启动服务挂载测试在服务端在共享目录下创建文件测试在客户端在共享目录下创建文件 2、dhcp服务搭建及测试什么…

【算法】回形遍历N*N的二维数组

题目 通常&#xff0c;可以按照逐行、逐列等不同方法输出二维数组中的全部元素。 如果按照回形的路线&#xff08;如下图&#xff09;输出数组中的全部元素&#xff0c;你能给出比较有效的解法吗&#xff1f; 第一行为正整数N(1≤N≤10) 之后有N行、N列个正整数&#xff08;即…

Caddy之静态站点应用场景

一、背景与介绍 无意之中看到公司部门的软件介质下载站点不是使用Nginx部署&#xff0c;而是使用Caddy。就比较好奇了&#xff0c;这个Caddy是个什么东西? 为啥他们没用Nginx呢&#xff0c;带着好奇心搜索了一下相关资料。 官方解释: Caddy is a powerful, extensible platfo…

Linux如何连接github仓库

一.创建一个github账号 如何创建一个github账号 二.在github上创建一个仓库 登录上github后出现这个界面 然后点击左上角头像&#xff0c;在按照图片位置点击&#xff1a; 继续按照图片上的位置进行点击&#xff1a; 创建成功&#xff1a; 三.云主机连接Github仓库 1.在linux中…

【T5模型源码】深入T5模型:源码解析与实现细节

【T5模型源码】深入T5模型&#xff1a;源码解析与实现细节 文章脉络 模型结构图 类关系分析 简单类的源码 T5LayerNorm T5DenseActDense T5DenseGatedActDense T5LayerFF 复杂类的源码 常见参数介绍 最难的T5Attention源码 常见面试提问 总结 我们在日常业务中可能…

QuillEditor无法识别显示含有div和转义字符的问题

在解决前端根据爬取抓过来的数据时&#xff0c;要显示到富文本框中&#xff0c;如下&#xff1a; <div class\"info_cont\" id\"zoomcon\">\n <p><br></p><p style\";text-align: justify;font-family: Calibri;font-size…

解决Quartus与modelsim联合仿真问题:# Error loading design解决,是tb文件中没加:`timescale 1ns/1ns

解决Quartus与modelsim联合仿真问题&#xff1a;# Error loading design解决&#xff0c;是tb文件中没加&#xff1a;timescale 1&#xff0c;一直走下来&#xff0c;在modelsim中出现了下面问题2&#xff0c;rtl文件、tb文件2.1&#xff0c;rtl代码2.2&#xff0c;tb测试2.3&a…

设计模式23--观察者模式

定义 案例一 案例二 优缺点

《Python之路:系统自学指南》

引言 在当今信息时代&#xff0c;编程已经成为一项越来越重要的技能。而Python作为一门功能强大、易学易用的编程语言&#xff0c;受到了越来越多人的青睐。然而&#xff0c;学习Python并不是一蹴而就的事情&#xff0c;尤其是对于没有编程基础的初学者来说&#xff0c;往往需…

智慧城市一屏统览,数字孪生综合治理

现代城市作为一个复杂系统&#xff0c;牵一发而动全身&#xff0c;城市化进程中产生新的矛盾和社会问题都会影响整个城市系统的正常运转。智慧城市是应对这些问题的策略之一。城市工作要树立系统思维&#xff0c;从构成城市诸多要素、结构、功能等方面入手&#xff0c;系统推进…

什么是JPA,JPA的概念

什么是JPA JPA&#xff08;Java Persistence API&#xff0c;Java持久化API&#xff09;&#xff0c;定义了对象-关系映射&#xff08;ORM&#xff09;以及实体对象持久化的标准接口,它是一套标准,具体的实现要根据不同的厂商来提供,就跟JDBC类型 持久化单元 持久化单元是运行…

进程概念篇

1. 冯诺依曼体系结构 目前市面上&#xff0c;几乎所有计算机的硬件构成都遵循冯诺依曼体系结构 与最原始的【输入设备->CPU->输出设备】这样简单的结构相比&#xff0c;冯诺依曼体系结构有何好处&#xff1f; 每次我们在键盘输入数据&#xff0c;经过CPU处理&#xff0c…

train拦截器

拦截器拦截到的请求&#xff0c;设置本地变量member&#xff0c;主要为了获取memberId&#xff0c;在passenger表中存放memberId。 拦截器&#xff1a; 乘客表外键memberId关联member表

开抖店卖什么产品最好?这些类目建议新手收藏好,带你快速起店!

大家好&#xff0c;我是电商小布。 各位小伙伴们加入到抖店这个市场当中&#xff0c;当然就是为了利用我们的小店实现产品转化工作。 而在我们开店的时候&#xff0c;就要考虑好一个东西&#xff0c;就是我们开店到底要卖什么&#xff1f;什么样的产品好卖&#xff1f; 跟这…

OSPF协议详解

静态缺点 1、中大型复杂网络----配置量大 2、不能实时收敛 动态-----可以实时收敛 IGP----内部网关路由协议 RIP OSPF EIGRP ISIS EGP----外部网关路由协议 BGP IGP &#xff08;选路佳 占用资源 收敛快&#xff09;----一个协议好需满足这三个 距离矢量 DV RIP…

Java 线程的状态

线程状态 操作系统的线程 从上图可以详细分析出线程的五种状态以及状态之间的切换。 1&#xff09;新建状态&#xff08;New&#xff09;&#xff1a;当线程对象对创建后&#xff0c;即进入了新建状态&#xff0c;如&#xff1a;Thread t new MyThread(); 2&#xff09;就绪…

【Vue3进阶】- 第2学堂小商城实战课程前言

该教程为进阶教程&#xff0c;如果你还不了解Vue3的基础知识&#xff0c;可以先前往Vue3基础教程&#xff0c;从入门到实战。 学习时遇到的任何疑问都欢迎在相应课文页面下方的问答区进行提问哦 我能学到什么&#xff1f; 编程写法千千万&#xff0c;实现需求是第一。 教程中…

智慧工地可视化解决方案(2)

系统功能 总体应用设计 系统业务分为中心管理、视频联网、人员管理、环境管理、起重机械管理、车辆管理。 每个业务对应一个或者多个硬件系统。中心管理为监控中心。视频联网包含视频联网监控和移动执法子系统。人员管理分为实名制考勤子系统和人员安全子系统(安全帽)。起…

5.11 Vue配置Element UI框架

Vue配置Element UI框架 目录一、 概要二、 开发前准备1. 搭建Vue框架 三、 安装 Element UI1. 引入 Element UI 依赖2. 在 main.js 中引入 Element UI 和相关样式&#xff1a;3. 按需引入(非必须, 可忽略)4. 简单构建一个主页面 目录 一、 概要 Element UI 是一个基于 Vue.js …

安卓Termux系统安装MariaDB结合内网穿透实现公网连接本地数据库

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…