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

news/2024/6/24 7:57:33/文章来源:https://blog.csdn.net/mingyuexin235/article/details/137163941

本节来实现C++集群聊天服务器项目中的点对点聊天业务,一起来试试吧

一、点对点聊天业务

聊天服务器中一个重要的功能就是实现点对点聊天,客户端发送的信息包含聊天业务msgid、自身

的id和姓名、聊天对象的id号以及聊天信息,例如:

{"msgid":5,"id":13,"name":"zhang san","to":15,"msg":"Hello,Boy!"}

id号为13的张三用户要与id号为15的用户进行 点对点聊天业务,发送消息为Hello,Boy!

如果要聊天,那便是双方的

{"msgid":5,"id":15,"name":"lisi","to":13,"msg":"Hello,Hello."}

id号为15的李四用户与id号为13的用户聊天,发送消息为Hello,Hello.

二、点对点业务步骤

(1)从json传来的数据中,获取聊天对象的id号

    int to_id = js["to"].get<int>();

(2)点对点聊天,必须双方在线,如果不在线,则发送离线消息。因此需判断聊天对象是否在线,即是否在存储用户的通信连接的哈希表_userConnMap中,遍历哈希表查找在线用户id是否为聊天对象id,这里需保证线程安全,加锁

        lock_guard<mutex>lock(_connMutex);auto it = _userConnMap.find(to_id);

<1>如果聊天用户在线

服务器作为中转,主动推送消息给聊天对象用户

      if(it!=_userConnMap.end()){//to_id在线,转发消息,服务器主动推动消息给to_id用户it->second->send(js.dump());return ;}

<2>如果聊天用户不在线,转而处理离线消息

底层数据库中,有一张offlinemessage表存储了离线消息

与处理user表类似,我们定义offlineMsgModel类处理离线消息业务,实现插入离线消息、删除离线消息、查询离线消息功能

class offlineMsgModel{public://存储用户的离线消息void insert(int userid,string msg);//删除用户的离线消息void remove(int userid);//查询用户的离线消息vector<string> query(int userid);
};

因此,当点对点聊天发现聊天对象不在线时,我们将json对象序列化后存入底层数据库中

因此,存储离线消息函数如下:

// 存储用户的离线消息
void offlineMsgModel::insert(int userid, string msg)
{char sql[1024] = {0};sprintf(sql, "insert into offlinemessage values(%d, '%s')", userid, msg.c_str());MySQL mysql;if (mysql.connect()){mysql.update(sql);}
}

(3)第(八)节用户登录模块,用户登录后需查看是否有对应的离线消息,如果有服务器就将数据库中存储的离线消息推送给相应的客户端,然后删除库中存储的离线消息

<1>查看用户是否有离线消息

实现offlineMsgModel的查询函数,用一个vector容器来接收字符串消息

// 查询用户的离线消息
vector<string> offlineMsgModel::query(int userid)
{char sql[1024] = {0};sprintf(sql, "select message from offlinemessage where userid = %d", userid);vector<string> 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){vec.push_back(row[0]);}mysql_free_result(res);return vec;}}return vec;
}

(2)如果查询离线消息不为空,在响应json对象添加离线消息,然后将底层数据库中离线消息清除

// 删除用户的离线消息
void offlineMsgModel::remove(int userid)
{char sql[1024] = {0};sprintf(sql, "delete from offlinemessage where userid = %d", userid);MySQL mysql;if (mysql.connect()){mysql.update(sql);}
}

(3)回调返回json字符串,登录业务中查看离线消息模块如下:

         // 查询该用户是否有离线消息vector<string> vec = _offlineMsgModel.query(id);if (!vec.empty()){response["offlinemsg"] = vec;// 读取该用户的离线消息后,把该用户的所有离线消息删除掉_offlineMsgModel.remove(id);}conn->send(response.dump()); // 回调 ,返回json字符串

三、点对点业务代码实现

3.1offlineMsgModel类

在include/server/model中创建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);//查询用户的离线消息vector<string> query(int userid);
};#endif

3.2 在src/server/model中创建offlinemessagemodel.cpp进行实现

#include "offlinemessagemodel.hpp"
#include "db.hpp"// 存储用户的离线消息
void offlineMsgModel::insert(int userid, string msg)
{char sql[1024] = {0};sprintf(sql, "insert into offlinemessage values(%d, '%s')", userid, msg.c_str());MySQL mysql;if (mysql.connect()){mysql.update(sql);}
}// 删除用户的离线消息
void offlineMsgModel::remove(int userid)
{char sql[1024] = {0};sprintf(sql, "delete from offlinemessage where userid = %d", userid);MySQL mysql;if (mysql.connect()){mysql.update(sql);}
}// 查询用户的离线消息
vector<string> offlineMsgModel::query(int userid)
{char sql[1024] = {0};sprintf(sql, "select message from offlinemessage where userid = %d", userid);vector<string> 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){vec.push_back(row[0]);}mysql_free_result(res);return vec;}}return vec;
}

3.2 publi.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,   //聊天消息
};#endif

3.3 处理用户点对点聊天业务

chatservice.hpp中创建离线消息操作对象,创建处理点对点聊天业务函数oneChat

#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 "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 clientCloseException(const TcpConnectionPtr &conn);//获取消息对应的处理器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;};#endif

在chatservice.cpp中进行实现

#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)});
}// 获取消息对应的处理器
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]; // 返回这个处理器}
}// 处理登录业务  id  pwd   pwd
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);}conn->send(response.dump()); // 回调 ,返回json字符串}}else{// 用户名或者密码错误json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 1;response["errmsg"] = "用户名或者密码错误";conn->send(response.dump()); // 回调 ,返回json字符串}
}// 处理注册业务  name  password
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);// 从map表中删除用户的连接信息_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>();{lock_guard<mutex>lock(_connMutex);auto it = _userConnMap.find(to_id);if(it!=_userConnMap.end()){//to_id在线,转发消息,服务器主动推动消息给to_id用户it->second->send(js.dump());return ;}}//to_id不在线,处理离线消息_offlineMsgModel.insert(to_id,js.dump());}

三、功能验证

分别使用张三和李四的账号登录聊天服务器

可以看到张三和李四登录成功,数据库显示在线

两人分别互通消息,验证点对点聊天业务是否成功实现

点对点聊天业务验证成功

两人分别离线,异常退出,服务器记录

张三再次上线,给离线的李四发消息

张三问李四How are you!

此时李四不在线,存储入离线消息数据库中

李四登录,可以看到张三发来的离线消息已成功推送,此外底层数据库离线表也清空!

点对点聊天业务功能验证完毕!

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

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

 四、项目流程

 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博客

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

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

相关文章

Springboot自动获取接口实现

ServiceLoader加载接口实现步骤 1.编写接口 public interface CommunicationAdapterFactory {void setKernel(LocalKernel kernel);boolean providesAdapterFor(Vehicle vehicle);BasicCommunicationAdapter getAdapterFor(Vehicle vehicle); }2.编写实现 // 实现类 1 publi…

Flutter应用发布流程详解:从开发到上架一站式指南

引言 Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;其强大的性能和流畅的用户体验使其备受开发者青睐。然而&#xff0c;开发一款应用只是第一步&#xff0c;将其成功上架到苹果商店才是实现商业目标的关键一步。本文将详细介绍如何使用Flutter将应用程序上…

ubuntu2204配置zabbix6.4高可用

zabbix6.4-HA 配置keepalived配置haproxy数据库高可用配置zabbix-server配置proxy配置客户端agent 本实验VMware搭建zabbix6.4高可用集群&#xff0c;搭配haproxykeepalived。 master&#xff0c;node节点搭建haproxykeepalibed主备并配置vip地址 三台控制节点搭建数据库高可用…

部署项目遇到的各种问题总结

文章目录 前言一、后端问题 jar包运行出现错误宝塔面板使用jdk17二、数据库问题 版本问题三、前端问题 连不上后端总结 前言 在做完项目之后&#xff0c;为了让别人访问到自己的网站&#xff0c;就需要部署前端后端以及数据库&#xff0c;但是在部署的过程中出现了各种问题和困…

【BlossomRPC】接入注册中心

文章目录 NacosZookeeper自研配置中心 RPC项目 配置中心项目 网关项目 这是BlossomRPC项目的最后一篇文章了&#xff0c;接入完毕注册中心&#xff0c;一个完整的RPC框架就设计完成了。 对于项目对注册中心的整合&#xff0c;其实我们只需要再服务启动的时候将ip/port/servic…

安全测试重点思考(上)--AWVS使用/XSS漏洞复现

AWVS使用/XSS漏洞复现 AWVS功能使用Dashboard功能Targets功能Vulnerabilities功能Scans功能Reports功能Discovery功能Users功能Scan ProfilesNetwork Scanner功能Issue Trackers功能WAFs功能Proxy Settings功能 漏洞测试实操DVWA介绍XSS分类反射型xss解决存储型xss解决 安全测试…

手搓 Docker Image Creator(DIC)工具(02):预备知识

此节主要简单介绍一下 Docker、Dockerfile 的基本概念&#xff0c;Dockerfile 对的基本语法&#xff0c;Windows 和 macOS 下 Docker 桌面的安装&#xff0c;Docker 镜像的创建和运行测试等。 1 关于 Docker Docker 是一个开源的应用容器引擎&#xff0c;它允许开发者打包应用…

从“量子”到分子:探索计算的无限可能 | 综述荐读

在2023年年末&#xff0c;两篇划时代的研究报告在《科学》&#xff08;Science&#xff09;杂志上引发了广泛关注。这两篇论文分别来自两个研究小组&#xff0c;它们共同揭示了单氟化钙分子间相互作用的研究成果&#xff0c;成功地在这些分子间创造出了分子量子比特。这一成就不…

C++的字节对齐

什么是字节对齐 参考什么是字节对齐&#xff0c;为什么要对齐? 现代计算机中&#xff0c;内存空间按照字节划分&#xff0c;理论上可以从任何起始地址访问任意类型的变量。但实际中在访问特定类型变量时经常在特定的内存地址访问&#xff0c;这就需要各种类型数据按照一定的规…

基于SpringBoot的在线答疑系统的研究与实现

摘 要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大师生的喜爱&#xff0c;也逐渐进入了每个学生的使用。互联网具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自…

【数字图像处理】二值图和灰度图的形态学处理

文章目录 形态学处理二值图形态学处理二值图形态学基本算子二值图连通分量提取、区域标记二值图细化算法 灰度图形态学处理灰度图形态学基本算子灰度图形态学梯度灰度图 tophat 算法 形态学处理 二值图形态学处理 二值图形态学基本算子 二值图形态学图像处理通常在目标图像中…

Spring Boot 学习(1)——环境搭建

一只老辣鸟的自我救赎 不科普&#xff0c;简单记录学习过程。 开发环境约束&#xff1a; jdk1.8 Spring Boot 1.5.9 Spring 4.3.13 Maven 3.3.3 Intellij IDEA 2017 【脑瓜灵光的开发环境随意&#xff0c;不灵光尽量按上述约束设置。看了好些教程总…

基于SSM+MySQL的校园在线点餐系统设计与实现(包运行调试)

介绍 SSM&#xff1a;采用主流的SpringMVC、Spring、Mybatis框架构建 layui&#xff1a;Layui是一套开源的 Web UI 解决方案&#xff0c;采用自身经典的模块化规范&#xff0c;并遵循原生 HTML/CSS/JS 的开发方式&#xff0c;常适合网页界面的快速开发 源码论文获取 文章链接…

Docker实例

华子目录 docker实例1.为Ubuntu镜像添加ssh服务2.Docker安装mysql docker实例 1.为Ubuntu镜像添加ssh服务 (1)访问https://hub.docker.com&#xff0c;寻找合适的Ubuntu镜像 (2)拉取Ubuntu镜像 [rootserver ~]# docker pull ubuntu:latest latest: Pulling from library/ub…

VMware虚拟机三种网络模式配置

vmware有三种网络工作模式&#xff1a;Bridged&#xff08;桥接模式&#xff09;、NAT&#xff08;网络地址转换模式&#xff09;、Host-Only&#xff08;仅主机模式&#xff09;。 1. 打开网络编辑器&#xff08;编辑 --> 虚拟网络编辑器&#xff09; 在主机上有VMware Ne…

pytest--python的一种测试框架--pytest初阶

前言 使用pytest去做测试时我们对文件名的命名其实是有规范的&#xff0c;要用test_开头&#xff01;&#xff01;&#xff01; 一、pytest初阶 def test_one():expect1actual1assert expectactual#测试专用语句&#xff1a;assert&#xff0c;识别期望与实际值是否相等这个…

【Node.js】大文件上传

概述 大文件上传通常采用分片上传。如果因为某些原因上传突然中断&#xff0c;解决问题之后可以接着之前的分片上传&#xff0c;而不需要从头开始上传&#xff0c;也就是断点续传。此外还可以利用多个网络连接并行上传多个分片&#xff0c;提高上传速度。 注&#xff1a;前端不…

Linux 著名的sudo、su是什么?怎么用?

一、su 什么是su&#xff1f; su命令&#xff08;简称是&#xff1a;substitute 或者 switch user &#xff09;用于切换到另一个用户&#xff0c;没有指定用户名&#xff0c;则默认情况下将以root用户登录。 为了向后兼容&#xff0c;su默认不改变当前目录&#xff0c;只设…

C++ 中的 vector 的模拟实现【代码纯享】

文章目录 C 中的 vector 模拟实现1. vector 的基本概念2. vector 的基本操作3. vector 的模拟实现4.代码纯享5. 总结 C 中的 vector 模拟实现 在 C 中&#xff0c;vector 是一个非常重要的容器&#xff0c;它提供了动态数组的功能。在本篇博客中&#xff0c;我们将尝试模拟实现…

基于CycleGan开源项目实战图像合成

项目代码及数据链接见文末 没有配对图像能够进行图像生成吗&#xff1f;当然可以&#xff0c;CycleGan不需要配对的数据&#xff0c;例如&#xff0c;只要我们知道马的样子和斑马的样子&#xff0c;我们就能够很轻松的将马转化为斑马。 1.网络结构 传统GAN网络是&#xff0c;我…