C++项目 | 集群聊天服务器 | 用户登录业务 && 记录用户的连接信息以及线程安全问题
文章目录
- C++项目 | 集群聊天服务器 | 用户登录业务 && 记录用户的连接信息以及线程安全问题
-
- 1.query函数
- 2.updateState函数
- 3.login函数
- 4.测试
-
- 用户已经登录
- 用户名或密码错误
- 登录成功
- 5.记录用户的连接信息以及线程安全问题
-
- chatservice.hpp修改内容
- chatservice.cpp修改内容
1.query函数
功能:根据用户号码查询用户信息
封装sql的select查询语句
把查询结果封装到User中返回
没查询到就返回默认结果(id==-1)
//usermodel.cpp
//根据用户号码查询用户信息
User UserModel::query(int id)
{
//根据用户号码查询用户信息
//1.组装sql语句
char sql[1024]={0};
sprintf(sql,"select * from user where id = %d",
id);
MySQL mysql;
//建立连接
if(mysql.connect())
{
//获取结果
MYSQL_RES *res=mysql.query(sql);
if(res!=nullptr)
{
//取出结果
MYSQL_ROW row=mysql_fetch_row(res);
if(row!=nullptr)
{
User user;
//row拿出来的是一行记录
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPwd(row[2]);
user.setState(row[3]);
//释放资源
mysql_free_result(res);
return user;
}
}
}
//没查询到结果
return User();
}
2.updateState函数
修改用户在线状态
封装sql语句,用户登录后状态改为在线
//更新用户的状态信息
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;
}
3.login函数
1.json中的id默认是字符串要转为int
2.id==-1表示没有这个用户
3.调用query查询id对应的user
4.在线状态为offline才登录,不然就是登录失败
5.没查到id说明没有这个账户
//chatservice.cpp
//处理登录业务
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);
//id==-1 表示没找着
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());
}
else
{
//登录成功,更新用户状态信息
user.setState("online");
_userModel.updateState(user);
json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=0;
response["id"]=user.getId();
response["name"]=user.getName();
conn->send(response.dump());
}
}
else
{
//登录失败 该用户不存在或者用户存在但密码错误
json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=1;
response["errmsg"]="用户名或者密码错误";
conn->send(response.dump());
}
}
4.测试
//登录成功
{"msgid":1,"id":1,"password":"123456"}
//没有该用户
{"msgid":1,"id":2,"password":"123456"}
//测试完成以后改回未登录状态
update user set state = 'offline' where id = 1
用户已经登录
用户名或密码错误
登录成功
5.记录用户的连接信息以及线程安全问题
为什么要记录用户的连接信息?
一个用户登录后服务器要记录用户的连接信息
如果A和B用户发送消息,而服务器不知道A用户的连接的话就无法确切的把B的消息发给A
所以chatServer是一个长连接的服务器,只要用户登录着,那这个Tcp连接就会保持着
而一个用户接收另一个用户的消息肯定是服务器推给用户的,而不是用户从服务器上面拉取的。
chatservice.hpp修改内容
加入了记录用户连接的userConnMap
为确保userConnMap的线程安全加入mutex
//chatservice.hpp
#ifndef CHATSERVICE_H
#define CHATSERVICE_H
#include<muduo/net/TcpConnection.h>
#include<unordered_map>
#include<functional>
#include<mutex>
#include"json.hpp"
#include"usermodel.hpp"
using namespace std;
using namespace muduo;
using namespace muduo::net;
using json=nlohmann::json;
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);
//获取消息对应的处理器
MsgHandler getHandler(int msgid);
private:
ChatService();
//存储消息id和其对应的业务处理方法
unordered_map<int,MsgHandler> _msgHandlerMap;
//数据操作类对象
UserModel _userModel;
//定义互斥锁,保证_userConnMap的线程安全
mutex _connMutex;
//存储在线用户的通信连接
unordered_map<int,TcpConnectionPtr> _userConnMap;
};
#endif
chatservice.cpp修改内容
加的内容都是为了保证_userConnMap的线程安全操作
mysql操作线程安全由mysql server保证
json都是局部变量,线程的栈是自己的,所以不需要线程安全
可是把这些操作都放在锁的作用范围锁的粒度太大了,所以加个大括号,锁出了作用域自动释放资源了
//mysql操作线程安全由mysql server保证
//json都是局部变量,线程的栈是自己的,所以不需要线程安全
//可是都放在锁的作用范围锁的粒度太大了,所以加个大括号,锁出了作用域自动释放资源了
{
//登录成功,记录用户连接信息
lock_guard<mutex> lock(_connMutex);
_userConnMap.insert({id,conn});
}
//chatservice.cpp
//处理登录业务
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);
//id==-1 表示没找着
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());
}
else
{
//mysql操作线程安全由mysql server保证
//json都是局部变量,线程的栈是自己的,所以不需要线程安全
//可是都放在锁的作用范围锁的粒度太大了,所以加个大括号,锁出了作用域自动释放资源了
{ //登录成功,记录用户连接信息
lock_guard<mutex> lock(_connMutex);
_userConnMap.insert({id,conn});
}
//登录成功,更新用户状态信息
user.setState("online");
_userModel.updateState(user);
json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=0;
response["id"]=user.getId();
response["name"]=user.getName();
conn->send(response.dump());
}
}
else
{
//登录失败 该用户不存在或者用户存在但密码错误
json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=1;
response["errmsg"]="用户名或者密码错误";
conn->send(response.dump());
}
}
评论前必须登录!
注册