本节来实现C++集群聊天服务器项目中的服务器异常退出与添加好友业务,一起来试试吧
一、服务器异常退出
在Linux环境下,我们在服务器端使用CTRL+C结束程序执行, 即使用CTRL+C让服务器异常退出,这样的后果是本应登录服务器的用户在数据库中在线状态未及时修改为离线,在下次重新登录时会显示用户已登录。
为解决上述问题,编写服务器异常退出函数,只要调用用户操作对象_userModel将所有在线用户状态修改为离线即可
1.1 服务器异常退出代码
代码实现比较简单,在chatservice.hpp中声明函数,在chatservice.cpp中定义
cpp
// 服务端异常,业务重置方法
void reset();
cpp
// 服务端异常,业务重置方法
void ChatService::reset()
{
// 把所有online用户的状态设置为offline
_userModel.resetState();
}
在main函数,调用业务模块处理服务器异常退出的函数,signal信号捕获突发事件
cpp
#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类对象操作好友表,编写添加好友与查询好友列表两个函数
cpp
// 添加好友关系
bool insert(int userid, int friendid);
// 返回用户好友列表
vector<User> query(int userid);
使用FriendModel类对象向数据库好友表中插入好友关系
(3)添加好友成功,返回响应信息、userid和friendid;添加失败,传回响应信息即可
cpp
// 添加好友业务 {"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)在登录业务中,返回用户离线消息后,也返回用户的好友表,用户可以选择与指定好友进行聊天啦
cpp
// 查询该用户的好友信息并返回
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表示添加好友
cpp
#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
cpp
#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中进行实现
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创建添加好友业务函数
cpp
#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表,绑定相应的事件回调
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)});
_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、点对点聊天业务