C++项目 | 集群聊天服务器 | 用户登录业务 && 记录用户的连接信息以及线程安全问题

1.query函数

功能:根据用户号码查询用户信息

封装sql的select查询语句

把查询结果封装到User中返回

没查询到就返回默认结果(id==-1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//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语句,用户登录后状态改为在线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//更新用户的状态信息
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说明没有这个账户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//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.测试

1
2
3
4
5
6
7
//登录成功
{"msgid":1,"id":1,"password":"123456"}
//没有该用户
{"msgid":1,"id":2,"password":"123456"}

//测试完成以后改回未登录状态
update user set state = 'offline' where id = 1

用户已经登录

image-20250119161241946

用户名或密码错误

image-20250119161317003

登录成功

image-20250119161353164

image-20250119161424535

5.记录用户的连接信息以及线程安全问题

为什么要记录用户的连接信息?

一个用户登录后服务器要记录用户的连接信息

如果A和B用户发送消息,而服务器不知道A用户的连接的话就无法确切的把B的消息发给A

所以chatServer是一个长连接的服务器,只要用户登录着,那这个Tcp连接就会保持着

而一个用户接收另一个用户的消息肯定是服务器推给用户的,而不是用户从服务器上面拉取的。

chatservice.hpp修改内容

加入了记录用户连接的userConnMap

为确保userConnMap的线程安全加入mutex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//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都是局部变量,线程的栈是自己的,所以不需要线程安全

可是把这些操作都放在锁的作用范围锁的粒度太大了,所以加个大括号,锁出了作用域自动释放资源了

1
2
3
4
5
6
7
8
//mysql操作线程安全由mysql server保证
//json都是局部变量,线程的栈是自己的,所以不需要线程安全
//可是都放在锁的作用范围锁的粒度太大了,所以加个大括号,锁出了作用域自动释放资源了
{
//登录成功,记录用户连接信息
lock_guard<mutex> lock(_connMutex);
_userConnMap.insert({id,conn});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//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());
}
}