CPP集群聊天服务器开发实践(一):用户注册与登录

目录

1 客户端用户注册与登录

1.1 主要思想

1.2 网络层

1.3 业务层

1.4 数据层

1.5 测试结果


1 客户端用户注册与登录

1.1 主要思想

实现网络层、业务层、数据层的解耦,提高系统的可维护性。

网络层:主要实现对客户端连接、客户端读写请求的捕获与回调,将其分发到多个线程中执行。

业务层:主要实现客户端读写请求回调的具体操作,当前阶段主要包含:登录业务、注册业务、用户异常退出业务

数据层:主要实现数据库中表的CUAD操作(增删改查)

1.2 网络层

利用muduo网络库实现epoll+线程池模式的网络模式。此模式具有模板化的特性,使用时可以原封不动的照搬。其原理可以查看前面关于muduo网络库以及epoll原理讲解的文章。其主要分为以下几个部分:

(1)组合Tcpserver对象以及Eventloop对象

(2)public下定义构造函数,实现上述对象的初始化以及处理连接的onConnection和处理读写的onMessage回调函数的注册

(3)private下定义onConnection和onMessage回调函数的具体实现

其中,在onConnection中:主要实现用户的连接以及用户异常关闭的情况,用户的连接直接调用muduo库的connected()方法;用户异常关闭则调用业务层定义的方法。

在onMessage中,为了实现网络层和业务层的解耦,通过定义哈希map _msgHandlerMap 将用户操作(msgid表征)和对应的回调处理进行一对一映射。即根据用户发来的msgid来获取对应的handler,获取到对应的handler后执行相应的业务。

整体来说由于muduo库强大的功能,实现比较简单,具体源码如下:

#include"chatserver.hpp"
#include"json.hpp"
#include<functional>
#include<string>
#include"chatservice.hpp"
using namespace std;
using namespace placeholders;
using json=nlohmann::json;ChatServer::ChatServer(EventLoop *loop,const InetAddress &listenAddr,const string &nameArg) : _server(loop, listenAddr, nameArg), _loop(loop)
{//注册连接回调_server.setConnectionCallback(std::bind(&ChatServer::onConnection,this,_1));//注册读写回调_server.setMessageCallback(std::bind(&ChatServer::onMessage,this,_1,_2,_3));//设置线程数量_server.setThreadNum(4);
}void ChatServer::start()
{_server.start();
}// 上报连接相关信息的回调函数
void ChatServer::onConnection(const TcpConnectionPtr &conn)
{//客户端断开连接if (!conn->connected()){//客户端异常关闭ChatService::instance()->clientCloseException(conn);conn->shutdown();}
}
// 上报读写事件相关信息的回调函数
void ChatServer::onMessage(const TcpConnectionPtr &conn,Buffer *buffer,Timestamp time)
{string buf=buffer->retrieveAllAsString();//数据的反序列化json js=json::parse(buf);//实现网络模块和业务模块的解耦:通过hs["msgid"]调取对应业务handler(回调业务代码),可以将conn,js,time传给handler,执行回调//解耦操作一般两种:1.面向基类2.回调操作//js["msgid"].get<int>():利用get方法将json对象转换为int类型,get方法提供的是模板auto msgHanndler=ChatService::instance()->getHandler(js["msgid"].get<int>());//回调消息绑定好的事件处理器,来执行相应的业务处理msgHanndler(conn,js,time);
}

1.3 业务层

上文提到,网络层和业务层解耦的关键就是回调操作。网络层获取到用户发来的msgid后,根据_msgHandlerMap找到对应的handler,每个handler都和对应的login或者register操作进行绑定,找到对应的handler后即执行相应的操作。其主要分为以下几个部分:

(1)构造函数中注册消息(msgid)以及对应的handler回调操作

(2)处理登录业务:根据id找到对应的user对象,验证用户是否已经登录,之后验证password是否正确,登录成功修改用户状态并进行响应

(3)处理注册业务:根据用户name 和 password进行注册,将自增的id作为账号返回给用户

(4)处理用户异常退出业务:定义关于用户id和状态信息的映射_userConnMap,从哈希表中获取异常关闭的用户id,修改数据库的状态,并删除哈希表中对应条目

业务层的实现主要靠回调操作,同时涉及单例模式、互斥锁等一些知识,具体可以查看专栏前面分享的文章。

具体源码如下:

#include"chatservice.hpp"
#include"public.hpp"
//利用muduo库的封装好的日志输出
#include<muduo/base/Logging.h>
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)});
}MsgHandler ChatService::getHandler(int msgid)
{//记录错误日志,msgid没有对应的事件处理回调auto it = _msgHandlerMap.find(msgid);if (it == _msgHandlerMap.end()){return [=](const TcpConnectionPtr &conn, json &js, Timestamp time){LOG_ERROR << "msgid: " << msgid << "can not find handler";};}else{return _msgHandlerMap[msgid];}
}// 处理登录业务 id+pwd 验证pwd
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time)
{LOG_INFO<<"DO LOGIN SERVICE!";int id=js["id"];string pwd=js["password"];//查询id对应的user对象User user = _userModel.query(id);if(user.getId()==id&&user.getPwd()==pwd){if(user.getState()=="online"){//该用户已经登录,不允许重复登录json response;response["msgid"]=LOGIN_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 2;response["errmsg"] = "该账号已经登录,请重新输入新账号";//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}else{//登录成功,记录用户连接信息/*群组聊天时,onMessage会被多线程调用,同时这个记录用户连接的map也会被多线程调用并且这个map会不断发生变化,需要考虑 线程安全 的问题*/{lock_guard<mutex> lock(_connMutex);_userConnMap.insert({id,conn});}//登录成功,更新用户state->onlineuser.setState("online");_userModel.updateState(user);json response;response["msgid"]=LOGIN_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 0;response["id"]=user.getId();response["name"]=user.getName();//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}}else{//用户不存在或者用户存在密码错误,登录失败json response;response["msgid"]=LOGIN_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 1;response["errmsg"] = "用户名或者密码错误";//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}
}
// 处理注册业务 
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time)
{LOG_INFO << "DO REGISTER SERVICE!";string name = js["name"];string pwd = js["password"];User user;user.setName(name);user.setPwd(pwd);bool state = _userModel.insert(user);if (state){// 注册成功,返回客户端json消息,并把自增的id返回给用户当作账号json response;response["msgid"]=REG_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 0;response["id"]=user.getId();//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}else{// 注册失败json response;response["msgid"]=REG_MSG_ACK;//响应error number如果为1,表示业务不成功response["errno"] = 1;//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}
}//处理客户端异常退出
void ChatService::clientCloseException(const TcpConnectionPtr &conn)
{User user;//做两件事:1.用户数据库state->offline 2._userConnMap中用户信息删除{lock_guard<mutex> lock(_connMutex);//遍历_userConnMapfor(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);}
}

1.4 数据层

数据层与业务层解耦的关键在于ORM架构的实现,可以有效防止操作数据库过程中重复的sql代码,通过将数据库的信息封装为对象进行操作,其核心主要包含dp.cpp(数据库连接、数据库更新、数据库查询功能);user.hpp(数据表的对象封装);usermodel.cpp(对user对象的增删改查操作)。主要包含以下内容:

(1)dp.cpp:可以理解为数据库的底层操作,主要依赖于mysql库提供的方法,实现数据库的连接、更新、查询操作

(2)user.hpp:主要是对user表的对象实例化封装,根据表的字段定义变量(私有),并提供get、set的公有化方法

(3)usermodel.cpp:这一层与业务层紧密相关,根据业务需要对user对象提供插入、查询、更新状态的方法,并调用底层的mysql库方法(dp.cpp中的内容)。具体步骤包含:a. 组装sql语句 b. 连接数据库 c.调用底层操作。

具体源码如下:

dp.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()
{_conn = mysql_init(nullptr);
}//释放数据库连接资源
MySQL::~MySQL()
{if(_conn!=nullptr){mysql_close(_conn);}
}//连接数据库
bool MySQL::connect()
{MYSQL *p = mysql_real_connect(_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(_conn, "set names gbk");LOG_INFO << "connect mysql success!";}else{LOG_INFO << "connect mysql fail!";}return p;
}//更新操作
bool MySQL::update(string sql)
{if (mysql_query(_conn, sql.c_str())){LOG_INFO << __FILE__ << ":" << __LINE__ << ":"<< sql << "更新失败!";return false;}return true;
}//查询操作
MYSQL_RES* MySQL::query(string sql)
{if (mysql_query(_conn, sql.c_str())){LOG_INFO << __FILE__ << ":" << __LINE__ << ":"<< sql << "查询失败!";return nullptr;}return mysql_use_result(_conn);
}//获取连接
MYSQL *MySQL::getConnection()
{return _conn;
}

user.hpp:

#ifndef USER_H
#define USER_H#include<string>
using namespace std;
/*
mysql> SHOW COLUMNS FROM user;
+----------+--------------------------+------+-----+---------+----------------+
| Field    | Type                     | Null | Key | Default | Extra          |
+----------+--------------------------+------+-----+---------+----------------+
| id       | int                      | NO   | PRI | NULL    | auto_increment |
| username | varchar(50)              | NO   | UNI | NULL    |                |
| password | varchar(50)              | NO   |     | NULL    |                |
| state    | enum('online','offline') | YES  |     | offline |                |
+----------+--------------------------+------+-----+---------+----------------+
*/
//定义数据库对象,将数据库信息整合为一个对象提交给业务层
//匹配User表的ORM类
class User{
public:User(int id = -1, string name = "", string pwd = "", string state = "offline"){this->id = id;this->name = name;this->password = pwd;this->state = state;}void setId(int id){this->id = id;}void setName(string name){this->name = name;}void setPwd(string pwd){this->password = pwd;}void setState(string state){this->state = state;}int getId(){return this->id;}string getName(){return this->name;}string getPwd(){return this->password;}string getState(){return this->state;}
private:int id;string name;string password;string state;
};
#endif

usermodel.cpp:

#include"usermodel.hpp"
#include"db.h"
#include<iostream>
#include<muduo/base/Logging.h>
using namespace std;
//User表的增加方法
bool UserModel::insert(User &user)
{//1. 组成sql语句char sql[1024] = {0};sprintf(sql, "insert into user(username,password,state) values('%s', '%s', '%s')",user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());LOG_INFO<<sql;//2.连接数据库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.连接数据库MySQL mysql;if (mysql.connect()){MYSQL_RES* res= mysql.query(sql);//res不为空,查询成功if(res!=nullptr){//返回查到的行,得到的是字符串,可以用[ ]取值MYSQL_ROW row = mysql_fetch_row(res);if(row!=nullptr){User user;//Convert a string to an integer.user.setId(atoi(row[0]));user.setName(row[1]);user.setPwd(row[2]);user.setState(row[3]);//res使用指针动态分配资源,需要释放,防止内存泄漏mysql_free_result(res);return user;}}}//返回默认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());MySQL mysql;if(mysql.connect()){if(mysql.update(sql)){return true;}}return false;
}

1.5 测试结果

主要针对用户注册、登录、用户异常退出业务的测试。

可以看到,客户端发送json字符串可以实现响应的业务,同时用户异常退出时数据库相应的状态也会变为offline。

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

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

相关文章

【C语言】数 组与指针:深度剖析与等价表达

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;数组与指针的基本关系&#x1f4af;数组与指针的互换使用数组下标与指针的等价性 &#x1f4af;六个表达式的等价性&#x1f4af;指针运算的注意事项&#x1f4af;数组…

寒假2.7

题解 web&#xff1a;[HCTF 2018]WarmUp 打开是张表情包 看一下源代码 访问source.php&#xff0c;得到完整代码 代码审计 <?phphighlight_file(__FILE__);class emmm{public static function checkFile(&$page){$whitelist ["source">"source.p…

sqli-lab靶场学习(五)——Less15-17(post方法盲注、修改密码)

前言 第11-14关开始用post方法&#xff0c;15-17关会用到盲注&#xff0c;post方法盲注和get方法类似。 Less15 这关是单引号闭合&#xff0c;有报错但没有具体情况的回显&#xff0c;因此适合使用错误盲注。 在用户名密码框分别输入 账号&#xff1a;admin and 11 -- asd…

【Spring】什么是Spring?

什么是Spring&#xff1f; Spring是一个开源的轻量级框架&#xff0c;是为了简化企业级开发而设计的。我们通常讲的Spring一般指的是Spring Framework。Spring的核心是控制反转(IoC-Inversion of Control)和面向切面编程(AOP-Aspect-Oriented Programming)。这些功能使得开发者…

VSCode便捷开发

一、常用插件 Vue 3 Snippets、Vetur、Vue - Official 二、常用开发者工具 三、Vue中使用Element-UI 安装步骤&#xff1a; 1、在VSCode的终端执行如下指令&#xff1a; npm i element-ui -S 2、在main.js中全局引入&#xff1a; import Vue from vue; import ElementUI from …

Android studio 创建aar包给Unity使用

1、aar 是什么&#xff1f; 和 Jar有什么区别 aar 和 jar包 都是压缩包&#xff0c;可以使用压缩软件打开 jar包 用于封装 Java 类及其相关资源 aar 文件是专门为 Android 平台设计的 &#xff0c;可以包含Android的专有内容&#xff0c;比如AndroidManifest.xml 文件 &#…

加速汽车软件升级——堆栈刷写技术的应用与挑战

一、背景和挑战 | 背景&#xff1a; 当前汽车市场竞争激烈&#xff0c;多品牌并存&#xff0c;新车发布速度加快&#xff0c;价格逐渐降低&#xff0c;功能日益多样化。随着车辆功能的不断提升与优化&#xff0c;ECU&#xff08;电子控制单元&#xff09;的代码量也随之增加&…

台湾精锐APEX减速机在半导体制造设备中的应用案例

半导体制造设备对传动系统的精度、可靠性和稳定性要求极高&#xff0c;台湾精锐APEX减速机凭借其低背隙、高精度和高刚性等优势&#xff0c;在半导体制造设备中得到了广泛应用。 案例一&#xff1a;晶圆切割设备 1.应用场景 在晶圆切割过程中&#xff0c;设备需要高精度的运…

Windows安装cwgo,一直安装的是linux平台的

Windows安装cwgo&#xff0c;一直安装的是linux平台的 查看 go env &#xff0c;发现 GOOSlinux 临时修改 GOOS &#xff0c;set GOOSwindows &#xff0c;再安装。 此时&#xff0c;安装的就是 windows 的可执行文件。安装之后再将 GOOS 修改回来即可。

【R语言】plyr包和dplyr包

一、plyr包 plyr扩展包主要是实现数据处理中的“分割-应用-组合”&#xff08;split-apply-combine&#xff09;策略。此策略是指将一个问题分割成更容易操作的部分&#xff0c;再对每一部分进行独立的操作&#xff0c;最后将各部分的操作结果组合起来。 plyr扩展包中的主要函…

google 多模态aistudio Stream Realtime体验

参考&#xff1a; https://aistudio.google.com/live 使用gemini多模态能力&#xff0c;支持语音图像文字输入输出&#xff0c;实时交互体验 支持语音实时交互、摄像头加语音、屏幕视频语音 摄像头 屏幕共享

opentelemetry-collector 配置elasticsearch

一、修改otelcol-config.yaml receivers:otlp:protocols:grpc:endpoint: 0.0.0.0:4317http:endpoint: 0.0.0.0:4318 exporters:debug:verbosity: detailedotlp/jaeger: # Jaeger supports OTLP directlyendpoint: 192.168.31.161:4317tls:insecure: trueotlphttp/prometheus: …

四、OSG学习笔记-基础图元

前一章节&#xff1a; 三、OSG学习笔记-应用基础-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/145514021 代码&#xff1a;CuiQingCheng/OsgStudy - Gitee.com 一、绘制盒子模型 下面一个简单的 demo #include<windows.h> #include<osg/Node&…

保姆级AI开发环境搭建

目录 windows下环境搭建1. Python环境搭建2. 下载vLLM2.1 安装CUDA2.2 安装Pytorch2.3 安装vllm 3. 部署Deepseek&#xff08;huggingface&#xff09;3.1 DeepSeek的优化建议 4. ollama快速部署Deepseek4.1 下载Ollama4.2 配置Ollma4.2 运行模型4.3 其他Ollama命令 linux下环境…

MySQL安装与配置

MySQL是常用的数据库&#xff0c;本篇记录MySQL的安装与配置。 1.首先到官网下载MySQL&#xff0c;这里下载5.7版本的。 https://downloads.mysql.com/archives/community/ 2.下载完成后&#xff0c;解压&#xff0c;然后设置环境变量 3.打开解压的要目录&#xff0c;创建一个…

如何参与开源项目

目的 就是说一下如何参与开源的项目&#xff0c;通过参与QXlsx来说明开源项目是如何参与的&#xff0c;其它的github上的开源项目&#xff0c;也是这样的流程。 关于GitHub: GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持Git作为唯一的版本库格式进行…

edu小程序挖掘严重支付逻辑漏洞

edu小程序挖掘严重支付逻辑漏洞 一、敏感信息泄露 打开购电小程序 这里需要输入姓名和学号&#xff0c;直接搜索引擎搜索即可得到&#xff0c;这就不用多说了&#xff0c;但是这里的手机号可以任意输入&#xff0c;只要用户没有绑定手机号这里我们输入自己的手机号抓包直接进…

【论文翻译】DeepSeek-V3论文翻译——DeepSeek-V3 Technical Report——第一部分:引言与模型架构

论文原文链接&#xff1a;DeepSeek-V3/DeepSeek_V3.pdf at main deepseek-ai/DeepSeek-V3 GitHub 特别声明&#xff0c;本文不做任何商业用途&#xff0c;仅作为个人学习相关论文的翻译记录。本文对原文内容直译&#xff0c;一切以论文原文内容为准&#xff0c;对原文作者表示…

Qt之设置QToolBar上的按钮样式

通常给QAction设置icon后,菜单栏的菜单项和工具栏(QToolBar)上对应的按钮会同时显示该icon。工具栏还可以使用setToolButtonStyle函数设置按钮样式,其参数为枚举值: enum ToolButtonStyle {ToolButtonIconOnly,ToolButtonTextOnly,ToolButtonTextBesideIcon,ToolButtonTe…

学习threejs,使用Lensflare模拟镜头眩光

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.Lensflare 二、&…