C++集群聊天服務(wù)器 網(wǎng)絡(luò)模塊+業(yè)務(wù)模塊+CMake構(gòu)建項(xiàng)目 筆記 (上)-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135991635?spm=1001.2014.3001.5501C++集群聊天服務(wù)器 數(shù)據(jù)模塊+業(yè)務(wù)模塊+CMake構(gòu)建項(xiàng)目 筆記 (上)-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/136007616?spm=1001.2014.3001.5501C++集群聊天服務(wù)器 nginx+redis安裝 筆記 (中)-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/136119985?spm=1001.2014.3001.5501基于C++11的數(shù)據(jù)庫(kù)連接池【C++/數(shù)據(jù)庫(kù)/多線程/MySQL】_c++ 數(shù)據(jù)庫(kù) 句柄 連接池管理-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135719057?spm=1001.2014.3001.5501MysqlConn.h
#pragma once
#include <mysql/mysql.h>
#include <string>
#include <chrono>
using namespace std;
using namespace std::chrono;
class MysqlConn {
public:
// 初始化數(shù)據(jù)庫(kù)連接
MysqlConn();
// 釋放數(shù)據(jù)庫(kù)連接
~MysqlConn();
// 連接數(shù)據(jù)庫(kù)
bool connect(string user, string passwd, string dbName, string ip, unsigned short port = 3306);
// 更新數(shù)據(jù)庫(kù): select,update,delete
bool update(string sql);
// 查詢數(shù)據(jù)庫(kù)
MYSQL_RES* query(string sql);
// 遍歷查詢得到的結(jié)果集
bool next();
// 得到結(jié)果集中的字段值
string value(int index);
// 事務(wù)操作
bool transaction();
// 提交事務(wù)
bool commit();
// 事務(wù)回滾
bool rollback();
// 刷新起始的空閑時(shí)間點(diǎn)
void refreshAliveTime();
// 計(jì)算連接存活的總時(shí)長(zhǎng)
long long getAliveTime();
// 獲取連接
MYSQL* getConnection();
private:
void freeResult();
MYSQL* m_conn = nullptr; // 數(shù)據(jù)庫(kù)連接
MYSQL_RES* m_result = nullptr;
MYSQL_ROW m_row = nullptr;
steady_clock::time_point m_aliveTime;
};
ConnPool.h
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>//條件變量
#include "MysqlConn.h"
using namespace std;
class ConnPool {
public:
static ConnPool* getConnPool();// 獲得單例對(duì)象
ConnPool(const ConnPool& obj) = delete; // 刪除拷貝構(gòu)造函數(shù)
ConnPool& operator=(const ConnPool& obj) = delete; // 刪除拷貝賦值運(yùn)算符重載函數(shù)
shared_ptr<MysqlConn> getConn(); // 從連接池中取出一個(gè)連接
~ConnPool(); // 析構(gòu)函數(shù)
private:
ConnPool(); // 構(gòu)造函數(shù)私有化
bool parseJsonFile(); // 解析json格式文件
void produceConn(); // 生產(chǎn)數(shù)據(jù)庫(kù)連接
void recycleConn(); // 銷毀數(shù)據(jù)庫(kù)連接
void addConn(); // 添加數(shù)據(jù)庫(kù)連接
// 連接服務(wù)器所需信息
string m_ip; // 數(shù)據(jù)庫(kù)服務(wù)器ip地址
string m_user; // 數(shù)據(jù)庫(kù)服務(wù)器用戶名
string m_dbName; // 數(shù)據(jù)庫(kù)服務(wù)器的數(shù)據(jù)庫(kù)名
string m_passwd; // 數(shù)據(jù)庫(kù)服務(wù)器密碼
unsigned short m_port; // 數(shù)據(jù)庫(kù)服務(wù)器綁定的端口
// 連接池信息
queue<MysqlConn*> m_connQ;
unsigned int m_maxSize; // 連接數(shù)上限值
unsigned int m_minSize; // 連接數(shù)下限值
int m_timeout; // 連接超時(shí)時(shí)長(zhǎng)
int m_maxIdleTime; // 最大的空閑時(shí)長(zhǎng)
mutex m_mutexQ; // 獨(dú)占互斥鎖
condition_variable m_cond; // 條件變量
};
MysqlConn.cpp
#include "MysqlConn.h"
#include <muduo/base/Logging.h>
// 初始化數(shù)據(jù)庫(kù)連接
MysqlConn::MysqlConn() {
m_conn = mysql_init(nullptr);
mysql_set_character_set(m_conn, "GBK"); // 設(shè)置字符集
}
// 釋放數(shù)據(jù)庫(kù)連接
MysqlConn::~MysqlConn() {
if (m_conn != nullptr) {
mysql_close(m_conn);
}
freeResult();
}
// 連接數(shù)據(jù)庫(kù)
bool MysqlConn::connect(string user, string passwd, string dbName, string ip, unsigned short port) {
MYSQL* ptr = mysql_real_connect(m_conn, ip.c_str(), user.c_str(), passwd.c_str(), dbName.c_str(), port, nullptr, 0);
return ptr != nullptr;
}
// 更新數(shù)據(jù)庫(kù):insert,update,delete
bool MysqlConn::update(string sql) {
if (mysql_query(m_conn, sql.c_str())) {
return false;
}
return true;
}
// 查詢數(shù)據(jù)庫(kù)
MYSQL_RES* MysqlConn::query(string sql) {
if(mysql_query(m_conn, sql.c_str())) {
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql <<"查詢失敗!";
return nullptr;
}
return mysql_use_result(m_conn);
}
// 遍歷查詢得到的結(jié)果集
bool MysqlConn::next() {
if (m_result != nullptr) {
m_row = mysql_fetch_row(m_result);
if (m_row != nullptr) {
return true;
}
}
return false;
}
// 得到結(jié)果集中的字段值
string MysqlConn::value(int index) {
int rowCount = mysql_num_fields(m_result);
if (index >= rowCount || index < 0) {
return string();
}
char* val = m_row[index];
unsigned long length = mysql_fetch_lengths(m_result)[index];
return string(val, length);
}
// 事務(wù)操作
bool MysqlConn::transaction() {
return mysql_autocommit(m_conn, false);
}
// 提交事務(wù)
bool MysqlConn::commit() {
return mysql_commit(m_conn);
}
// 事務(wù)回滾
bool MysqlConn::rollback() {
return mysql_rollback(m_conn);
}
// 刷新起始的空閑時(shí)間點(diǎn)
void MysqlConn::refreshAliveTime() {
// 這個(gè)時(shí)間戳就是某個(gè)數(shù)據(jù)庫(kù)連接,它起始存活的時(shí)間點(diǎn)
// 這個(gè)時(shí)間點(diǎn)通過(guò)時(shí)間類就可以得到了
m_aliveTime = steady_clock::now();
}
// 計(jì)算連接存活的總時(shí)長(zhǎng)
long long MysqlConn::getAliveTime() {
nanoseconds duration = steady_clock::now() - m_aliveTime;
milliseconds millsec = duration_cast<milliseconds>(duration);
return millsec.count();
}
// 獲取連接
MYSQL *MysqlConn::getConnection() {
return m_conn;
}
void MysqlConn::freeResult() {
if (m_result != nullptr) {
mysql_free_result(m_result);
m_result = nullptr;
}
}
ConnPool.cpp
#include "ConnPool.h"
// #include <json/json.h>
// #include <json.h>
#include "json.hpp"
#include <fstream>
#include <thread>
#include <iostream>
// using namespace Json;
using json = nlohmann::json;
ConnPool* ConnPool::getConnPool() {
static ConnPool pool;
return &pool;
}
// 從連接池中取出一個(gè)連接
shared_ptr<MysqlConn> ConnPool::getConn() {
unique_lock<mutex> locker(m_mutexQ);
while (m_connQ.empty()) {
if (cv_status::timeout == m_cond.wait_for(locker, chrono::milliseconds(m_timeout))) {
if (m_connQ.empty()) {
//return nullptr;
continue;
}
}
}
shared_ptr<MysqlConn>connptr(m_connQ.front(), [this](MysqlConn* conn) {
lock_guard<mutex>locker(m_mutexQ); // 自動(dòng)管理加鎖和解鎖
conn->refreshAliveTime();// 更新連接的起始的空閑時(shí)間點(diǎn)
m_connQ.push(conn); // 回收數(shù)據(jù)庫(kù)連接,此時(shí)它再次處于空閑狀態(tài)
});// 智能指針
m_connQ.pop();
m_cond.notify_one(); // 本意是喚醒生產(chǎn)者
return connptr;
}
ConnPool::~ConnPool() {
while (!m_connQ.empty()) {
MysqlConn* conn = m_connQ.front();
m_connQ.pop();
delete conn;
}
}
ConnPool::ConnPool() {
// 加載配置文件
if (!parseJsonFile()) {
std::cout << "加載配置文件失?。。?!" << std::endl;
return;
}
for (int i = 0; i < m_minSize; ++i) {
addConn();
}
thread producer(&ConnPool::produceConn, this);// 生產(chǎn)連接
thread recycler(&ConnPool::recycleConn, this);// 銷毀連接
producer.detach();
recycler.detach();
}
bool ConnPool::parseJsonFile() {
ifstream ifs;
ifs.open("/home/heheda/Linux/Chat/configuration/dbconf.json");
if (!ifs.is_open()) {
std::cout << "無(wú)法打開(kāi) dbconf.json 配置文件!";
return false;
}
std::cout << "開(kāi)始解析 dbconf.json 配置文件..." << std::endl;
json data; // 創(chuàng)建一個(gè)空的JSON對(duì)象
ifs>>data; // 將文件內(nèi)容加載到JSON對(duì)象中
m_ip = data["ip"];
m_port = data["port"];
m_user = data["userName"];
m_passwd = data["password"];
m_dbName = data["dbName"];
m_minSize = data["minSize"];
m_maxSize = data["maxSize"];
m_maxIdleTime = data["maxIdleTime"];
m_timeout = data["timeout"];
/*
ifstream ifs("dbconf.json");
Reader rd;
Value root;
rd.parse(ifs, root);
if (root.isObject()) {
std::cout << "開(kāi)始解析配置文件..." << std::endl;
m_ip = root["ip"].asString();
m_port = root["port"].asInt();
m_user = root["userName"].asString();
m_passwd = root["password"].asString();
m_dbName = root["dbName"].asString();
m_minSize = root["minSize"].asInt();
m_maxSize = root["maxSize"].asInt();
m_maxIdleTime = root["maxIdleTime"].asInt();
m_timeout = root["timeout"].asInt();
return true; // 解析成功返回true,否則返回false。
}
return false;
*/
return true;
}
void ConnPool::produceConn() {
while (true) { // 生產(chǎn)者線程不斷生產(chǎn)連接,直到連接池達(dá)到最大值
unique_lock<mutex> locker(m_mutexQ); // 加鎖,保證線程安全
while (m_connQ.size() >= m_minSize) {
m_cond.wait(locker); // 等待消費(fèi)者通知
}
addConn(); // 生產(chǎn)連接
m_cond.notify_all();// 通知消費(fèi)者(喚醒)
}
}
// 回收數(shù)據(jù)庫(kù)連接
void ConnPool::recycleConn() {
while (true) {
this_thread::sleep_for(chrono::milliseconds(500));// 每隔半秒鐘檢測(cè)一次
lock_guard<mutex> locker(m_mutexQ); // 加鎖,保證線程安全
while (m_connQ.size() > m_minSize) { // 如果連接池中的連接數(shù)大于最小連接數(shù),則回收連接
MysqlConn* conn = m_connQ.front(); // 取出連接池中的連接
if (conn->getAliveTime() >= m_maxIdleTime) {
m_connQ.pop(); // 回收連接
delete conn; // 釋放連接資源
}
else {
break; // 如果連接的空閑時(shí)間小于最大空閑時(shí)間,則跳出循環(huán)
}
}
}
}
// 添加連接到連接池
void ConnPool::addConn() {
MysqlConn* conn = new MysqlConn;
conn->connect(m_user, m_passwd, m_dbName, m_ip, m_port);
conn->refreshAliveTime();// 記錄建立連接的時(shí)候的對(duì)應(yīng)的時(shí)間戳
m_connQ.push(conn);
}
dbconf.json
{
"ip": "127.0.0.1",
"port": 3306,
"userName": "root",
"password": "123456",
"dbName": "chat",
"minSize":100,
"maxSize":1024,
"maxIdleTime":5000,
"timeout":1000
}
執(zhí)行sql語(yǔ)句:?
create table user(
id int not null auto_increment primary key,
name varchar(50) not null unique,
password varchar(50) not null,
state enum('online','offline') default 'offline'
);
- user.hpp
#ifndef USER_H
#define USER_H
#include <string>
using namespace std;
// 匹配User表的ORM類
class User {
public:
User(int id=-1, string name="", string password="", string state="offline") {
m_id = id;
m_name = name;
m_password = password;
m_state = state;
}
// 設(shè)置相應(yīng)字段
void setId(int id) { m_id = id; }
void setName(string name) { m_name = name; }
void setPwd(string pwd) { m_password = pwd; }
void setState(string state) { m_state = state; }
// 獲取相應(yīng)字段
int getId() const { return m_id; }
string getName() const { return m_name; }
string getPwd() const { return m_password; }
string getState() const { return m_state; }
private:
int m_id; // 用戶id
string m_name; // 用戶名
string m_password; // 用戶密碼
string m_state; // 當(dāng)前登錄狀態(tài)
};
#endif // USER_H
/*
數(shù)據(jù)層代碼框架設(shè)計(jì)
數(shù)據(jù)庫(kù)操作與業(yè)務(wù)代碼進(jìn)行分離,業(yè)務(wù)代碼處理的都為對(duì)象,數(shù)據(jù)庫(kù)層操作
具體SQL語(yǔ)句,因此我們定義相應(yīng)的類,每一個(gè)類對(duì)應(yīng)數(shù)據(jù)庫(kù)中一張表,將
數(shù)據(jù)庫(kù)讀出來(lái)的字段提交給業(yè)務(wù)使用。
*/
- usermodel.hpp
#ifndef USERMODEL_H
#define USERMODEL_H
#include "user.hpp"
#include "ConnPool.h"
// User表的數(shù)據(jù)操作類:針對(duì)表的增刪改查
class UserModel {
public:
// user表的增加方法
bool insert(ConnPool* pool,User& user);
// 根據(jù)用戶號(hào)碼查詢用戶信息
User query(ConnPool* pool,int id);
// 更新用戶的狀態(tài)信息
bool updateState(ConnPool* pool,User user);
// 重置用戶的狀態(tài)信息
void resetState(ConnPool* pool);
};
#endif // USERMODEL_H
- usermodel.cpp
#include "usermodel.hpp"
#include "MysqlConn.h"
#include <iostream>
#include <memory>
// User表的增加方法
bool UserModel::insert(ConnPool* pool,User &user) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
std::sprintf(sql,"insert into user(name,password,state) values('%s','%s', '%s')",
user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());
// 2.執(zhí)行sql語(yǔ)句,進(jìn)行處理
shared_ptr<MysqlConn> conn = pool->getConn();
if(conn->update(sql)) {
// 獲取插入成功的用戶數(shù)據(jù)生成的主鍵id
// id為自增鍵,設(shè)置回去user對(duì)象添加新生成的用戶id
user.setId(mysql_insert_id(conn->getConnection()));
return true;
}
return false;
}
// 根據(jù)用戶號(hào)碼查詢用戶信息
User UserModel::query(ConnPool* pool,int id) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql,"select * from user where id = %d", id);
// 2.執(zhí)行sql語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
// 查詢id對(duì)應(yīng)的數(shù)據(jù)
MYSQL_RES* res = conn->query(sql);
if(res != nullptr) { // 查詢成功
MYSQL_ROW row = mysql_fetch_row(res);// 獲取行數(shù)據(jù)
if(row != nullptr) {
User user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPwd(row[2]);
user.setState(row[3]);
// 釋放res動(dòng)態(tài)開(kāi)辟的資源
mysql_free_result(res);
return user;// 返回user對(duì)應(yīng)的信息
}
}
return User(); // 未找到,返回默認(rèn)的user對(duì)象
}
// 更新用戶的狀態(tài)信息
bool UserModel::updateState(ConnPool* pool,User user) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql,"update user set state = '%s' where id = %d",
user.getState().c_str(), user.getId());
// 2.執(zhí)行sql語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
if(conn->update(sql)) {
return true;
}
return false;
}
// 重置用戶的狀態(tài)信息
void UserModel::resetState(ConnPool* pool) {
// 1.組裝sql語(yǔ)句
char sql[1024] = "update user set state = 'offline' where state = 'online'";
// 2.執(zhí)行sql語(yǔ)句,進(jìn)行相應(yīng)處理
shared_ptr<MysqlConn> conn = pool->getConn();
conn->update(sql);
}
- 執(zhí)行sql語(yǔ)句:??
create table friend(
userid int not null,
friendid int not null
);
alter table friend
add constraint pk_friend primary key(userid,friendid);
- friendmodel.hpp
#ifndef FRIENDMODEL_H
#define FRIENDMODEL_H
#include "user.hpp"
#include "ConnPool.h"
#include <vector>
using namespace std;
// Friend用戶表的數(shù)據(jù)操作類:針對(duì)類的增刪改查(維護(hù)好友信息的操作接口方法)
class FriendModel {
public:
// 添加好友關(guān)系
void insert(ConnPool* pool,int userid, int friendid);
// 返回用戶好友列表:返回用戶好友id,名稱,登錄狀態(tài)信息
vector<User> query(ConnPool* pool,int userid);
};
#endif // FRIENDMODEL_H
- friendmodel.cpp
#include "friendmodel.hpp"
// 添加好友關(guān)系
void FriendModel::insert(ConnPool* pool,int userid, int friendid) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql, "insert into friend values (%d, %d)", userid, friendid);
// 2.執(zhí)行sql語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
conn->update(sql);
}
//返回用戶好友列表:返回用戶好友id、名稱、登錄狀態(tài)信息
vector<User> FriendModel::query(ConnPool* pool,int userid) {
// 1.組裝sql語(yǔ)句
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);
sprintf(sql, "select a.id, a.name, a.state from user a inner join friend b on b.userid = a.id where b.friendid = %d \
union (select a.id, a.name, a.state from user a inner join friend b on b.friendid = a.id where b.userid = %d \
or b.friendid = %d and a.id!=%d)",userid,userid,userid,userid);
// 2.發(fā)送SQL語(yǔ)句,進(jìn)行相應(yīng)處理
vector<User> vec;
shared_ptr<MysqlConn> conn = pool->getConn();
MYSQL_RES * res = conn->query(sql);
if(res != nullptr) {
// 把userid用戶的所有離線消息放入vec中返回
MYSQL_ROW row;
//將userid好友的詳細(xì)信息返回
while((row = mysql_fetch_row(res)) != nullptr) {
User user;
user.setId(atoi(row[0])); // id
user.setName(row[1]); // name
user.setState(row[2]); // state
vec.push_back(user);
}
mysql_free_result(res); // 釋放資源
return vec;
}
return vec;
}
// select a.id,a.name,a.state from user a inner join
// friend b on b.friendid = a.id
// where b.userid = %d
- 執(zhí)行sql語(yǔ)句:???
create table offlinemessage(
userid int not null primary key,
message varchar(500) not null
);
- offlinemessage.hpp
#ifndef OFFLINEMESSAGEMODEL_H
#define OFFLINEMESSAGEMODEL_H
#include <string>
#include <vector>
#include "ConnPool.h"
using namespace std;
// 離線消息表的數(shù)據(jù)操作類:針對(duì)表的增刪改查(提供離線消息表的操作接口方法)
class OfflineMsgModel {
public:
// 存儲(chǔ)用戶的離線消息
void insert(ConnPool* pool,int userid, string msg);
// 刪除用戶的離線消息
void remove(ConnPool* pool,int userid);
// 查詢用戶的離線消息:離線消息可能有多個(gè)
vector<string> query(ConnPool* pool,int userid);
};
#endif // OFFLINEMESSAGEMODEL_H
- offlinemessage.cpp
#include "offlinemessagemodel.hpp"
// 存儲(chǔ)用戶的離線消息
void OfflineMsgModel::insert(ConnPool* pool,int userid, string msg) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql, "insert into offlinemessage values(%d, '%s')", userid, msg.c_str());
// 2.執(zhí)行sql語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
conn->update(sql);
}
// 刪除用戶的離線消息
void OfflineMsgModel::remove(ConnPool* pool,int userid) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql, "delete from offlinemessage where userid = %d", userid);
// 2.執(zhí)行sql語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
conn->update(sql);
}
// 查詢用戶的離線消息:離線消息可能有多個(gè)
vector<string> OfflineMsgModel::query(ConnPool* pool,int userid) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql, "select message from offlinemessage where userid = %d", userid);
// 2.執(zhí)行sql語(yǔ)句
vector<string> vec;// 存儲(chǔ)離線消息,離線消息可能有多條
shared_ptr<MysqlConn> conn = pool->getConn();
MYSQL_RES *res = conn->query(sql);
if(res != nullptr) {
// 把userid用戶的所有離線消息放入vec中返回
MYSQL_ROW row;
while((row = mysql_fetch_row(res)) != nullptr) { //循環(huán)查找離線消息
vec.push_back(row[0]);
}
mysql_free_result(res);
return vec;
}
return vec;
}
- ?執(zhí)行sql語(yǔ)句:??
create table allgroup(
id int not null auto_increment primary key,
groupname varchar(50) not null,
groupdesc varchar(200) default ''
);
- group.hpp
#ifndef GROUP_H
#define GROUP_H
#include <vector>
#include <string>
using namespace std;
#include "groupuser.hpp"
// User表的ORM類
// Group群組表的映射類:映射表的相應(yīng)字段
class Group{
public:
Group(int id=-1,string name="",string desc="")
: m_id(id)
,m_name(name)
,m_desc(desc) {
}
void setId(int id) { m_id = id; }
void setName(string name) { m_name = name; }
void setDesc(string desc) { m_desc = desc; }
int getId() const { return m_id; }
string getName() const { return m_name; }
string getDesc() const { return m_desc; }
vector<GroupUser> &getUsers() { return m_users; }
private:
int m_id; // 群組id
string m_name; // 群組名稱
string m_desc; // 群組功能描述
vector<GroupUser> m_users;// 存儲(chǔ)組成員
};
#endif // GROUP_H
- 執(zhí)行sql語(yǔ)句:????
create table groupuser(
groupid int not null,
userid int not null,
grouprole enum('creator','normal') default 'normal'
);
alter table groupuser
add constraint pk_friend primary key(groupid,userid);
- groupuser.hpp
#ifndef GROUPUSER_H
#define GROUPUSER_H
#include <string>
#include "user.hpp"
using namespace std;
// 群組用戶,多了一個(gè)role角色信息,從User類直接繼承,復(fù)用User的其他信息
// GroupUser群組員表的映射類:映射表的相應(yīng)字段
class GroupUser : public User {
public:
void setRole(string role) { m_role = role; }
string getRole() { return m_role; }
private:
string m_role;
};
#endif // GROUPUSER_H
- groupmodel.hpp
#ifndef GROUPMODEL_H
#define GROUPMODEL_H
#include "group.hpp"
#include <string>
#include <vector>
using namespace std;
#include "ConnPool.h"
// 群組表的數(shù)據(jù)操作類:維護(hù)數(shù)組信息的操作接口方法
class GroupModel {
public:
// 創(chuàng)建數(shù)組
bool createGroup(ConnPool* pool,Group &group);
// 加入群組
void joinGroup(ConnPool* pool,int userid, int groupid, string role);
// 查詢用戶所在群組信息
vector<Group> queryGroups(ConnPool* pool,int userid);
// 根據(jù)指定的groupid查詢?nèi)航M用戶id列表,除userid自己,主要用戶群聊業(yè)務(wù)給群組其他成員群發(fā)消息
vector<int> queryGroupUsers(ConnPool* pool,int userid, int groupid);
};
#endif // GROUPMODEL_H
- groupmodel.cpp
#include "groupmodel.hpp"
#include <iostream>
// 創(chuàng)建群組
bool GroupModel::createGroup(ConnPool* pool,Group &group) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql,"insert into allgroup(groupname,groupdesc) values('%s','%s')"
,group.getName().c_str(),group.getDesc().c_str());
// 2.執(zhí)行sql語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
if(conn->update(sql)) {
// 獲取到自增id
group.setId(mysql_insert_id(conn->getConnection()));
return true;
}
return false;
}
// 加入群組:即給群組員groupuser表添加一組信息
void GroupModel::joinGroup(ConnPool* pool,int userid, int groupid, string role) {
// 1.組裝sql語(yǔ)句
char sql[1024] = {0};
sprintf(sql,"insert into groupuser values(%d,%d,'%s')",
groupid,userid,role.c_str());
// 2.執(zhí)行sqls語(yǔ)句
shared_ptr<MysqlConn> conn = pool->getConn();
conn->update(sql);
}
// 查詢用戶所在群組信息:群信息以及組員信息
vector<Group> GroupModel::queryGroups(ConnPool* pool,int userid) {
/*
1.先根據(jù)userid在groupuser表中查詢出該用戶所屬的群組信息
2.在根據(jù)群組信息,查詢屬于該群組的所有用戶的userid,并且和user表
進(jìn)行多表聯(lián)合查詢,查出用戶的詳細(xì)信息
*/
char sql[1024] = {0};
sprintf(sql,"select a.id,a.groupname,a.groupdesc from allgroup a inner join \
groupuser b on a.id = b.groupid where b.userid = %d",userid);
vector<Group> groupVec;
shared_ptr<MysqlConn> conn = pool->getConn();
MYSQL_RES *res = conn->query(sql);
if(res != nullptr) {
MYSQL_ROW row;
// 查出userid所有的群組信息
while((row = mysql_fetch_row(res)) != nullptr) {
std::cout<<"group row[0]: "<<row[0]<<" row[1]: "<<row[1]<<" row[2]: "<<row[2]<<std::endl;
Group group;
group.setId(atoi(row[0]));
group.setName(row[1]);
group.setDesc(row[2]);
groupVec.push_back(group);
}
mysql_free_result(res);
}
// 查詢?nèi)航M的用戶信息
for(Group& group:groupVec) {
sprintf(sql,"select a.id,a.name,a.state,b.grouprole from user a \
inner join groupuser b on b.userid = a.id where b.groupid=%d",group.getId());
MYSQL_RES *res = conn->query(sql);
if(res != nullptr) {
MYSQL_ROW row;
while((row = mysql_fetch_row(res)) != nullptr) {
std::cout<<"group user row[0]: "<<row[0]<<" row[1]: "<<row[1]<<" row[2]: "<<row[2]<<" row[3]: "<<row[3]<<std::endl;
GroupUser user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setState(row[2]);
user.setRole(row[3]);
group.getUsers().push_back(user);
}
mysql_free_result(res);
}
}
return groupVec;
}
//查詢用戶所在群組信息:群信息以及組員信息
// vector<Group> GroupModel::queryGroups(ConnPool* pool,int userid)
// {
// /*
// 1、先根據(jù)userid在groupuser表中查詢出該用戶所屬的群組詳細(xì)信息
// 2、再根據(jù)群組信息,查詢屬于該群組的所有用戶的userid,并且和user表進(jìn)行多表聯(lián)合查詢出用戶的詳細(xì)信息
// */
// //1、組裝SQL語(yǔ)句
// char sql[1024] = {0};
// sprintf(sql, "select a.id,a.groupname,a.groupdesc from allgroup a inner join \
// groupuser b on a.id = b.groupid where b.userid=%d", userid);
// //2、發(fā)送SQL語(yǔ)句,進(jìn)行相應(yīng)處理
// vector<Group> groupVec;
// // MySQL mysql;
// shared_ptr<MysqlConn> conn = pool->getConn();
// MYSQL_RES *res = conn->query(sql);
// if (res != nullptr)
// {
// MYSQL_ROW row;
// //查出userid所有的群信息
// while ((row = mysql_fetch_row(res)) != nullptr)
// {
// Group group;
// group.setId(atoi(row[0]));
// group.setName(row[1]);
// group.setDesc(row[2]);
// groupVec.push_back(group);
// }
// mysql_free_result(res);
// }
// //查詢?nèi)航M的用戶信息
// for (Group &group : groupVec)
// {
// sprintf(sql, "select a.id,a.name,a.state,b.grouprole from user a \
// inner join groupuser b on b.userid = a.id where b.groupid=%d", group.getId());
// MYSQL_RES *res = conn->query(sql);
// if (res != nullptr)
// {
// MYSQL_ROW row;
// while ((row = mysql_fetch_row(res)) != nullptr)
// {
// GroupUser user;
// user.setId(atoi(row[0]));
// user.setName(row[1]);
// user.setState(row[2]);
// user.setRole(row[3]);
// group.getUsers().push_back(user);
// }
// mysql_free_result(res);
// }
// }
// }
// 根據(jù)指定的groupid查詢?nèi)航M用戶id列表,除userid自己,主要用戶群聊業(yè)務(wù)給群組其他成員群發(fā)消息
vector<int> GroupModel::queryGroupUsers(ConnPool* pool,int userid, int groupid) {
char sql[1024]={0};
sprintf(sql,"select userid from groupuser \
where groupid = %d and userid!=%d",groupid,userid);
vector<int> idVec;
shared_ptr<MysqlConn> conn = pool->getConn();
MYSQL_RES *res = conn->query(sql);
if(res != nullptr) {
MYSQL_ROW row;
while((row = mysql_fetch_row(res)) != nullptr) {
idVec.push_back(atoi(row[0]));
}
mysql_free_result(res);
}
return idVec;
}
redis.hpp
#ifndef REDIS_H
#define REDIS_H
#include <hiredis/hiredis.h>
#include <thread>
#include <functional>
using namespace std;
class Redis {
public:
Redis();
~Redis();
// 連接redis服務(wù)器
bool connect();
// 向redis指定的通道channel發(fā)布消息
bool publish(int channel,string message);
// 向redis指定的通道subscribe訂閱消息
bool subscribe(int channel);
// 向redis指定的通道unsubscribe取消訂閱消息
bool unsubscribe(int channel);
// 在獨(dú)立線程中接收訂閱通道中的消息
void observer_channel_message();
// 初始化向業(yè)務(wù)層上報(bào)通道消息的回調(diào)對(duì)象
void init_notify_handler(function<void(int,string)> fn);
private:
// hiredis同步上下文對(duì)象,負(fù)責(zé)publish消息:相當(dāng)于我們客戶端一個(gè)redis-cli跟連接相關(guān)的所有信息,需要兩個(gè)上下文處理
redisContext* m_publish_context;
// hiredis同步上下文對(duì)象,負(fù)責(zé)subscribe消息
redisContext* m_subscribe_context;
// 回調(diào)操作,收到訂閱的消息,給service層上報(bào):主要上報(bào)通道號(hào)、數(shù)據(jù)
function<void(int,string)>m_notify_message_handler;
};
#endif
redis.cpp
#include <iostream>
using namespace std;
#include "redis.hpp"
//構(gòu)造函數(shù):初始化兩個(gè)上下文指針
Redis::Redis()
: m_publish_context(nullptr)
, m_subscribe_context(nullptr)
{
}
//析構(gòu)函數(shù):釋放兩個(gè)上下文指針占用資源
Redis::~Redis() {
if (m_publish_context != nullptr) {
redisFree(m_publish_context);
// m_publish_context = nullptr;
}
if (m_subscribe_context != nullptr) {
redisFree(m_subscribe_context);
// m_subscribe_context = nullptr;
}
}
//連接redis服務(wù)器
bool Redis::connect() {
//負(fù)責(zé)publish發(fā)布消息的上下文連接
m_publish_context = redisConnect("127.0.0.1", 6379);
if (nullptr == m_publish_context) {
cerr << "connect redis failed!" << endl;
return false;
}
//負(fù)責(zé)subscribe訂閱消息的上下文連接
m_subscribe_context = redisConnect("127.0.0.1", 6379);
if (nullptr == m_subscribe_context) {
cerr << "connect redis failes!" << endl;
return false;
}
//在單獨(dú)的線程中監(jiān)聽(tīng)通道上的事件,有消息給業(yè)務(wù)層上報(bào) 讓線程阻塞去監(jiān)聽(tīng)
thread t([&](){
observer_channel_message();
});
t.detach();
cout << "connect redis-server success!" << endl;
return true;
}
//向redis指定的通道channel publish發(fā)布消息:調(diào)用redisCommand發(fā)送命令即可
bool Redis::publish(int channel, string message) {
redisReply *reply = (redisReply *)redisCommand(m_publish_context, "PUBLISH %d %s", channel, message.c_str()); //相當(dāng)于給channel通道發(fā)送消息
if (nullptr == reply) {
cerr << "publish command failed!" << endl;
return false;
}
freeReplyObject(reply);
return true;
}
/* 為什么發(fā)布消息使用redisCommand函數(shù)即可,而訂閱消息卻不使用?
redisCommand本身會(huì)先調(diào)用redisAppendCommand將要發(fā)送的命令緩存到本地,再調(diào)用redisBufferWrite將命令發(fā)送到redis服務(wù)器上,再調(diào)用redisReply以阻塞的方式等待命令的執(zhí)行。
subscribe會(huì)以阻塞的方式等待發(fā)送消息,線程是有限,每次訂閱一個(gè)線程會(huì)導(dǎo)致線程阻塞住,這肯定是不行的。
publish一執(zhí)行馬上會(huì)回復(fù),不會(huì)阻塞當(dāng)前線程,因此調(diào)用redisCommand函數(shù)。
*/
//向redis指定的通道subscribe訂閱消息:
bool Redis::subscribe(int channel) {
// SUBSCRIBE命令本身會(huì)造成線程阻塞等待通道里面發(fā)生消息,這里只做訂閱通道,不接收通道消息
// 通道消息的接收專門在observer_channel_message函數(shù)中的獨(dú)立線程中進(jìn)行
// 只負(fù)責(zé)發(fā)送命令,不阻塞接收redis server響應(yīng)消息,否則和notifyMsg線程搶占響應(yīng)資源
if (REDIS_ERR == redisAppendCommand(this->m_subscribe_context, "SUBSCRIBE %d", channel)) { //組裝命令寫入本地緩存
cerr << "subscribe command failed!" << endl;
return false;
}
// redisBufferWrite可以循環(huán)發(fā)送緩沖區(qū),直到緩沖區(qū)數(shù)據(jù)發(fā)送完畢(done被置為1)
int done = 0;
while (!done) {
if (REDIS_ERR == redisBufferWrite(this->m_subscribe_context, &done)) { //將本地緩存發(fā)送到redis服務(wù)器上
cerr << "subscribe command failed!" << endl;
return false;
}
}
// redisGetReply
return true;
}
//向redis指定的通道unsubscribe取消訂閱消息,與subscrible一樣
bool Redis::unsubscribe(int channel) {
if (REDIS_ERR == redisAppendCommand(this->m_subscribe_context, "UNSUBSCRIBE %d", channel)) {
cerr << "unsubscribe command failed!" << endl;
return false;
}
// redisBufferWrite可以循環(huán)發(fā)送緩沖區(qū),直到緩沖區(qū)數(shù)據(jù)發(fā)送完畢(done被置為1)
int done = 0;
while (!done) {
if (REDIS_ERR == redisBufferWrite(this->m_subscribe_context, &done)) {
cerr << "unsubscribe command failed!" << endl;
return false;
}
}
return true;
}
//在獨(dú)立線程中接收訂閱通道中的消息:以循環(huán)阻塞的方式等待響應(yīng)通道上發(fā)生消息
void Redis::observer_channel_message() {
redisReply *reply = nullptr;
while (REDIS_OK == redisGetReply(this->m_subscribe_context, (void**)&reply)) {
//訂閱收到的消息是一個(gè)帶三元素的數(shù),通道上發(fā)送消息會(huì)返回三個(gè)數(shù)據(jù),數(shù)據(jù)下標(biāo)為2
if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr) {
//給業(yè)務(wù)層上報(bào)通道上發(fā)送的消息:通道號(hào)、數(shù)據(jù)
m_notify_message_handler(atoi(reply->element[1]->str), reply->element[2]->str);
}
freeReplyObject(reply);
}
}
//初始化向業(yè)務(wù)層上報(bào)通道消息的回調(diào)對(duì)象
void Redis::init_notify_handler(function<void(int, string)> fn) {
this->m_notify_message_handler = fn;
}
chatserver.hpp
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
using namespace muduo;
using namespace muduo::net;
// 聊天服務(wù)器的主類
class ChatServer {
public:
// 初始化聊天服務(wù)器對(duì)象
ChatServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg);
// 啟動(dòng)服務(wù)
void start();
private:
// 上報(bào)鏈接相關(guān)信息的回調(diào)函數(shù):參數(shù)為連接信息
void onConnection(const TcpConnectionPtr& conn);
// 上報(bào)讀寫事件相關(guān)信息的回調(diào)函數(shù):參數(shù)分別為連接/緩沖區(qū)/接收到數(shù)據(jù)的時(shí)間信息
void onMessage(const TcpConnectionPtr& conn,Buffer* buffer,Timestamp time);
TcpServer m_server; // 組合的muduo庫(kù),實(shí)現(xiàn)服務(wù)器功能的類對(duì)象
EventLoop *m_loop; // 指向事件循環(huán)的指針
};
#endif
chatserver.cpp
#include "chatserver.hpp"
#include "chatservice.hpp"
#include "json.hpp"
#include <functional>
#include <string>
#include <iostream>
using namespace std;
using namespace placeholders;
using json = nlohmann::json;
// 初始化聊天服務(wù)器對(duì)象
ChatServer::ChatServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg)
: m_server(loop, listenAddr, nameArg), m_loop(loop) {
// 注冊(cè)用戶連接的創(chuàng)建和斷開(kāi)事件的回調(diào)
m_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1));
// 注冊(cè)用戶讀寫事件的回調(diào)
m_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3));
// 設(shè)置服務(wù)器線程數(shù)量 1個(gè)I/O線程,3個(gè)工作線程
m_server.setThreadNum(4);
}
// 啟動(dòng)服務(wù),開(kāi)啟事件循環(huán)
void ChatServer::start() {
m_server.start();
}
// 上報(bào)鏈接相關(guān)信息的回調(diào)函數(shù):參數(shù)為連接信息
void ChatServer::onConnection(const TcpConnectionPtr &conn) {
// 客戶端斷開(kāi)連接,釋放連接資源 muduo庫(kù)會(huì)打印相應(yīng)日志
if(!conn->connected()) {
ChatService::getInstance()->clientCloseException(conn);// 處理客戶端異常關(guān)閉
conn->shutdown();// 釋放socket fd資源
}
}
// 網(wǎng)絡(luò)模塊與業(yè)務(wù)模塊解耦:不直接調(diào)用相應(yīng)方法,業(yè)務(wù)發(fā)生變化此處代碼也不需要改動(dòng)
// 上報(bào)讀寫事件相關(guān)信息的回調(diào)函數(shù):參數(shù)分別為連接/緩沖區(qū)/接收到數(shù)據(jù)的時(shí)間信息
void ChatServer::onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time) {
// 將buffer緩沖區(qū)收到的數(shù)據(jù)存入字符串
string buf = buffer->retrieveAllAsString();
std::cout<<"buf: "<<buf.c_str()<<std::endl;
// 數(shù)據(jù)的反序列化
json js = json::parse(buf);
// 達(dá)到的目的:完全解耦網(wǎng)絡(luò)模塊的代碼和業(yè)務(wù)模塊的代碼
// 通過(guò)js["msgid"] 獲取 => 業(yè)務(wù)handler => conn js time
auto msghandler = ChatService::getInstance()->getHandler(js["msgid"].get<int>());
// 回調(diào)消息綁定好的事件處理器,來(lái)執(zhí)行相應(yīng)的業(yè)務(wù)處理
msghandler(conn,js,time);
}
chatservice.hpp
#ifndef CHATSERVICE_H
#define CHATSERVICE_H
#include <muduo/net/TcpConnection.h>
#include <unordered_map>
#include <functional>
#include <mutex>
using namespace std;
using namespace muduo;
using namespace muduo::net;
#include "json.hpp"
using json = nlohmann::json;
#include "usermodel.hpp"
#include "offlinemessagemodel.hpp"
#include "friendmodel.hpp"
#include "groupmodel.hpp"
#include "redis.hpp"
#include "ConnPool.h"
// 表示處理消息的事件回調(diào)方法類型
using MsgHandler = std::function<void(const TcpConnectionPtr& conn,json& js,Timestamp)>;
// 聊天服務(wù)器業(yè)務(wù)類,設(shè)計(jì)為單例模式:給msgid映射事件回調(diào)(一個(gè)消息id映射一個(gè)事件處理)
class ChatService {
public:
// 獲取單例對(duì)象的接口函數(shù)
static ChatService* getInstance();
// 處理登錄業(yè)務(wù)
void login(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 處理注冊(cè)業(yè)務(wù)(register)
void reg(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 處理一對(duì)一聊天業(yè)務(wù)
void oneChat(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 添加好友業(yè)務(wù)
// void addFriend(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 添加好友業(yè)務(wù)請(qǐng)求
void addFriendRequest(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 添加好友業(yè)務(wù)響應(yīng)
void addFriendResponse(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 獲取消息msgid對(duì)應(yīng)的處理器
MsgHandler getHandler(int msgid);
// 處理客戶端異常退出
void clientCloseException(const TcpConnectionPtr& conn);
// 服務(wù)器異常,業(yè)務(wù)重置方法
void reset();
// 創(chuàng)建群組業(yè)務(wù)
void createGroup(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 加入群組業(yè)務(wù)
void joinGroup(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 群組聊天業(yè)務(wù)
void groupChat(const TcpConnectionPtr& conn,json& js,Timestamp time);
// 處理注銷業(yè)務(wù)
void loginOut(const TcpConnectionPtr &conn, json &js, Timestamp time);
// 從redis消息隊(duì)列中獲取訂閱的消息:通道號(hào) + 消息
void handleRedisSubscribeMessage(int userid, string msg);
ChatService(const ChatService&) = delete;
ChatService& operator=(const ChatService&) = delete;
ConnPool* getConnPool() const { return m_connPool;}
private:
// 注冊(cè)消息以及對(duì)應(yīng)的Handler回調(diào)操作
ChatService();
// 存儲(chǔ)消息id和其對(duì)應(yīng)的業(yè)務(wù)處理方法
unordered_map<int,MsgHandler> m_msgHandlerMap;
// 存儲(chǔ)在線用戶的通信連接
unordered_map<int,TcpConnectionPtr> m_userConnMap; // 消息處理器map表 每一個(gè)msgid對(duì)應(yīng)一個(gè)業(yè)務(wù)處理方法
// 定義互斥鎖,保證m_userConnMap的線程安全
mutex m_connMutex;
// 數(shù)據(jù)操作類對(duì)象
UserModel m_userModel; // 存儲(chǔ)在線用戶的通信連接map表
OfflineMsgModel m_offlineMsgModel; // 離線消息表的數(shù)據(jù)操作類對(duì)象
FriendModel m_friendModel; // 好友表的數(shù)據(jù)操作類對(duì)象
GroupModel m_groupModel;
Redis m_redis; // redis操作對(duì)象
ConnPool* m_connPool; // 數(shù)據(jù)庫(kù)連接池
};
#endif // CHATSERVICE_H
/*
3.1 用戶注冊(cè)業(yè)務(wù):
我們業(yè)務(wù)層與數(shù)據(jù)層分離,需要操作數(shù)據(jù)層數(shù)據(jù)對(duì)象即可,因此需要在
ChatService類中實(shí)例化一個(gè)數(shù)據(jù)操作類對(duì)象進(jìn)行業(yè)務(wù)開(kāi)發(fā)
UserModel m_userModel;// 數(shù)據(jù)操作類對(duì)象
服務(wù)器注冊(cè)業(yè)務(wù)流程:
1.客戶端注冊(cè)的消息過(guò)來(lái)后,網(wǎng)絡(luò)模塊將json數(shù)據(jù)反序列化后上報(bào)到注冊(cè)業(yè)務(wù)中,
因?yàn)閁ser表中id字段為自增的,state字段是默認(rèn)的,因此注冊(cè)業(yè)務(wù)只需要獲取
name與password字段即可
2.實(shí)例化User表對(duì)應(yīng)的對(duì)象user,將獲取到的name與password設(shè)置進(jìn)去,再向
UserModel數(shù)據(jù)操作類對(duì)象進(jìn)行新用戶user的注冊(cè)
3.注冊(cè)完成后,服務(wù)器返回相應(yīng)json數(shù)據(jù)給客戶端:若注冊(cè)成功,返回注冊(cè)響應(yīng)消息
REG_MSG_ACK,錯(cuò)誤標(biāo)識(shí)errno(0:成功,1:失敗),用戶id等組裝好的json數(shù)據(jù);
若注冊(cè)失敗,返回注冊(cè)響應(yīng)消息REG_MSG_ACK,錯(cuò)誤標(biāo)識(shí)
3.2 用戶登錄業(yè)務(wù)
3.2.1 基礎(chǔ)登錄業(yè)務(wù)實(shí)現(xiàn)
用戶登錄:服務(wù)器反序列化數(shù)據(jù)后,依據(jù)id,密碼字段后判斷賬號(hào)是否正確,依據(jù)是否
登陸成功給客戶端返回響應(yīng)消息
服務(wù)器登錄業(yè)務(wù)流程:
1.服務(wù)器獲取輸入用戶id,密碼字段
2.查詢id對(duì)應(yīng)的數(shù)據(jù),判斷用戶id與密碼是否正確,分為以下三種情況返回相應(yīng)json數(shù)據(jù)給客戶端:
(1)若用戶名/密碼正確且未重復(fù)登錄,及時(shí)更新登錄狀態(tài)為在線,,返回登錄響應(yīng)消息
LOGIN_MSG_ACK,錯(cuò)誤標(biāo)識(shí)errno(0:成功,1:失敗,2:重復(fù)登錄),用戶id,用戶名等信息
(2)若用戶名/密碼正確但重復(fù)登錄,返回登錄響應(yīng)消息、錯(cuò)誤標(biāo)識(shí)、錯(cuò)誤提示信息;
(3)若用戶不存在或密碼錯(cuò)誤,返回登錄響應(yīng)消息,錯(cuò)誤標(biāo)識(shí),錯(cuò)誤提示信息;
3.2.2 記錄用戶連接信息處理
用戶連接信息處理:假設(shè)此時(shí)用戶1向用戶2發(fā)送消息(源id, 目的id,消息內(nèi)容),
此時(shí)服務(wù)器收到用戶1的數(shù)據(jù)了,要主動(dòng)向用戶2推送該條消息,那么如何知道用戶2
是那條連接呢。因此我們需要專門處理下,用戶一旦登錄成功,就會(huì)建立一條連接,
我們便要將該條連接存儲(chǔ)下來(lái),方便后續(xù)消息收發(fā)的處理.
3.2.3 客戶端異常退出處理
客戶端異常退出處理:假設(shè)用戶客戶端直接通過(guò)Ctrl+C中斷,并沒(méi)有給服務(wù)器發(fā)送合法的json過(guò)來(lái),
我們必須及時(shí)修改用戶登錄狀態(tài),否則后續(xù)再想登錄時(shí)為"online"狀態(tài),便無(wú)法登錄了。
客戶端異常退出處理流程:
1.通過(guò)conn連接去m_userConnMap表中查找,刪除conn鍵值對(duì)記錄;
2.將conn連接對(duì)應(yīng)用戶數(shù)據(jù)庫(kù)的狀態(tài)從"online"改為"offline";
3.2.4 服務(wù)器異常退出處理
服務(wù)器異常退出處理:假設(shè)用戶服務(wù)器直接通過(guò)Ctrl+C中斷,并沒(méi)有給客戶端發(fā)送
合法的json過(guò)去,我們必須及時(shí)修改所有用戶登錄狀態(tài)未"offline",否則后續(xù)再
想登錄時(shí)為"online"狀態(tài),便無(wú)法登錄了。
服務(wù)器異常退出處理流程:主動(dòng)截獲Ctcl+c信號(hào)(SIGINT),在信號(hào)處理函數(shù)中將
數(shù)據(jù)庫(kù)中用戶狀態(tài)重置為"offline"。
3.3 點(diǎn)對(duì)點(diǎn)聊天業(yè)務(wù)
點(diǎn)對(duì)點(diǎn)聊天:源用戶向目的用戶發(fā)送消息,目的用戶若在線則將消息發(fā)出,
目的用戶若不在線將消息存儲(chǔ)至離線消息表中,待目的用戶上線后離線
消息發(fā)出
在進(jìn)行點(diǎn)對(duì)點(diǎn)聊天業(yè)務(wù)處理前,需要提前處理好以下幾點(diǎn):
在EnMsgType中增加一個(gè)聊天消息類型,給客戶端標(biāo)識(shí)此時(shí)是一個(gè)聊天消息.
將點(diǎn)對(duì)點(diǎn)業(yè)務(wù)的消息id與對(duì)應(yīng)的事件處理器提前在聊天服務(wù)器業(yè)務(wù)類的構(gòu)造
函數(shù)里綁定好
服務(wù)器點(diǎn)對(duì)點(diǎn)聊天業(yè)務(wù)流程
1.源id向目的id發(fā)送消息時(shí)候,消息里會(huì)包含消息類型,源id,源用戶名,
目的id,消息內(nèi)容,服務(wù)器解析到這些數(shù)據(jù)后,先獲取到目的id字段
2.找到id判斷是否在線,若在線則服務(wù)器將源id的消息中轉(zhuǎn)給目的id;若
不在線則將消息內(nèi)容存入離線消息表中,待目的id上線后離線消息發(fā)出
3.4 離線消息業(yè)務(wù)
離線消息業(yè)務(wù):當(dāng)用戶一旦登錄成功,我們查詢用戶是否有離線消息要發(fā)送,
若有則發(fā)送相應(yīng)數(shù)據(jù),發(fā)送完后刪除本次存儲(chǔ)的離線數(shù)據(jù),防止數(shù)據(jù)重復(fù)發(fā)送
在進(jìn)行點(diǎn)對(duì)點(diǎn)聊天業(yè)務(wù)處理前,我們需要提前處理好以下幾點(diǎn):
1、建立與離線消息表的映射OfflineMsgModel類:我們數(shù)據(jù)庫(kù)中有創(chuàng)建的
OfflineMessage離線消息表,因?yàn)槲覀償?shù)據(jù)層與業(yè)務(wù)層要分離開(kāi)來(lái),所以
這里與前面一樣提供離線消息表的數(shù)據(jù)操作類,提供給業(yè)務(wù)層對(duì)應(yīng)的操作接口。
服務(wù)器離線消息業(yè)務(wù)流程:
1.無(wú)論是一對(duì)一聊天,還是群聊,若接收方用戶不在線,則將發(fā)送方消息先存儲(chǔ)至離線消息表里
2.一旦接收方用戶登錄成功,檢查該用戶是否有離線消息(可能有多條),若有則服務(wù)器
將離線消息發(fā)送給接收方用戶
3.服務(wù)器發(fā)送完成后刪除本次存儲(chǔ)的離線消息,保證接收方不會(huì)每次登錄都收到重復(fù)的離線消息
3.5 添加好友業(yè)務(wù)
添加好友業(yè)務(wù):源用戶id、目的用戶id發(fā)送給服務(wù)器,服務(wù)器在數(shù)據(jù)庫(kù)中進(jìn)行好友關(guān)系的添加。
添加完成用戶登錄后,服務(wù)器返回好友列表信息給用戶,用戶可以依據(jù)好友列表進(jìn)行聊天,這里實(shí)現(xiàn)的比較簡(jiǎn)單,后續(xù)可擴(kuò)充更細(xì)化的業(yè)務(wù)。
在進(jìn)行添加好友業(yè)務(wù)處理前,我們需要提前處理好以下幾點(diǎn):
1、我們需要在消息類型EnMsgType中增加一個(gè)聊天消息類型,給客戶端標(biāo)識(shí)此時(shí)是一個(gè)添加好友消息:
2、將添加好友業(yè)務(wù)的消息id與對(duì)應(yīng)的事件處理器提前在聊天服務(wù)器業(yè)務(wù)類的構(gòu)造函數(shù)里綁定好。
3、建立好友表與類的映射FriendModel類:表中userid與friendid關(guān)系只需要存儲(chǔ)一次即可,因此為聯(lián)合主鍵。這里與前面一樣提供好友表的數(shù)據(jù)操作類,提供給業(yè)務(wù)層對(duì)應(yīng)的操作接口。
服務(wù)器添加好友業(yè)務(wù)流程:
1.服務(wù)器獲取當(dāng)前用戶id,要添加好友的id;
2.業(yè)務(wù)層調(diào)用數(shù)據(jù)層接口往數(shù)據(jù)庫(kù)中添加相應(yīng)好友信息;
用戶登錄成功時(shí),查詢?cè)撚脩舻暮糜研畔⒉⒎祷?
3.6 群組業(yè)務(wù)
群組業(yè)務(wù):群組業(yè)務(wù)分為三塊,群管理員創(chuàng)建群組,組員加入群組與群組聊天功能
在進(jìn)行群組業(yè)務(wù)處理前,我們需要提前處理好以下幾點(diǎn):
1.我們需要在消息類型EnMsgType中增加不同的消息類型,創(chuàng)建群組,
加入群組、群組聊天三種類型消息,給客戶端標(biāo)識(shí)此時(shí)要做什么事情:
3.6.1 創(chuàng)建群組
服務(wù)器創(chuàng)建群組業(yè)務(wù),業(yè)務(wù)流程:
1.服務(wù)器獲取創(chuàng)建群的用戶id,要?jiǎng)?chuàng)建群名稱,群功能等信息
2.業(yè)務(wù)層創(chuàng)建數(shù)據(jù)層對(duì)象,調(diào)用數(shù)據(jù)層方法進(jìn)行群組創(chuàng)建,創(chuàng)建成功保存群組創(chuàng)建人信息;
3.6.2 加入群組
服務(wù)器組員加入群組業(yè)務(wù)流程:
1、服務(wù)器獲取要加入群用戶的id、要加入的群組id;
2、業(yè)務(wù)層調(diào)用數(shù)據(jù)層方法將普通用戶加入;
3.6.3 群組聊天
服務(wù)器群組聊天業(yè)務(wù)流程:
1、獲取要發(fā)送消息的用戶id、要發(fā)送的群組id;
2、查詢?cè)撊航M其它用戶id;
3、查詢同組用戶id,若用戶在線則發(fā)送消息;若用戶不在線則存儲(chǔ)離線消息;
3.7 注銷業(yè)務(wù)
注銷業(yè)務(wù): 客戶端用戶正常退出,更新其在線狀態(tài)。
在進(jìn)行注銷業(yè)務(wù)處理前,我們需要提前處理好以下幾點(diǎn):
1、我們需要在消息類型EnMsgType中增加一個(gè)注銷業(yè)務(wù)類型,給客戶端標(biāo)識(shí)此時(shí)是一個(gè)注銷業(yè)務(wù)消息:
2、將注銷業(yè)務(wù)的消息id與對(duì)應(yīng)的事件處理器提前在聊天服務(wù)器業(yè)務(wù)類的構(gòu)造函數(shù)里綁定好。
服務(wù)器注銷業(yè)務(wù)業(yè)務(wù)流程:
1、服務(wù)器獲取要注銷用戶的id,刪除其對(duì)應(yīng)的連接。
2、更新用戶狀態(tài)信息,從在線更新為離線。
四 服務(wù)器支持跨服務(wù)器通信功能
redis主要業(yè)務(wù)流程:
1.用戶登錄成功后相應(yīng)的服務(wù)器需要向redis上依據(jù)用戶id訂閱相應(yīng)通道的消息
2.當(dāng)服務(wù)器上用戶之間跨服務(wù)器發(fā)送消息時(shí),需要向通道上發(fā)送消息
3、redis接收到消息通知相應(yīng)服務(wù)器進(jìn)行處理
*/
chatservice.cpp
#include "chatservice.hpp"
#include "public.hpp"
#include <muduo/base/Logging.h>
#include <vector>
#include <map>
#include <string>
#include <string.h>
#include <iostream>
using namespace std;
using namespace muduo;
// 獲取單例對(duì)象的接口函數(shù) 線程安全的單例對(duì)象
ChatService* ChatService::getInstance() {
static ChatService service;
return &service;
}
// 構(gòu)造函數(shù):注冊(cè)消息以及對(duì)應(yīng)的Handler回調(diào)操作 實(shí)現(xiàn)網(wǎng)絡(luò)模塊與業(yè)務(wù)模塊解耦的核心
// 將群組業(yè)務(wù)的消息id分別與對(duì)應(yīng)的事件處理器提前在聊天服務(wù)器業(yè)務(wù)類的構(gòu)造函數(shù)里綁定好
ChatService::ChatService() {
m_msgHandlerMap.insert({LOGIN_MSG,std::bind(&ChatService::login, this, _1, _2, _3)});
m_msgHandlerMap.insert({REG_MSG,std::bind(&ChatService::reg, this, _1, _2, _3)});
m_msgHandlerMap.insert({ONE_CHAT_MSG,std::bind(&ChatService::oneChat, this, _1, _2, _3)});
// m_msgHandlerMap.insert({ADD_FRIEND_MSG,std::bind(&ChatService::addFriend, this, _1, _2, _3)});
m_msgHandlerMap.insert({ADD_FRIEND_REQ_MSG,std::bind(&ChatService::addFriendRequest, this, _1, _2, _3)});
m_msgHandlerMap.insert({ADD_FRIEND_MSG_ACK,std::bind(&ChatService::addFriendResponse, this, _1, _2, _3)});
m_msgHandlerMap.insert({LOGIN_OUT_MSG, std::bind(&ChatService::loginOut, this, _1, _2, _3)});
m_msgHandlerMap.insert({CREATE_GROUP_MSG, std::bind(&ChatService::createGroup, this, _1, _2, _3)});
m_msgHandlerMap.insert({ADD_GROUP_MSG, std::bind(&ChatService::joinGroup, this, _1, _2, _3)});
m_msgHandlerMap.insert({GROUP_CHAT_MSG, std::bind(&ChatService::groupChat, this, _1, _2, _3)});
// 連接redis服務(wù)器
if(m_redis.connect()) {
// 設(shè)置上報(bào)消息的回調(diào)
m_redis.init_notify_handler(std::bind(&ChatService::handleRedisSubscribeMessage, this, _1, _2));
}
// 初始化數(shù)據(jù)庫(kù)
m_connPool = ConnPool::getConnPool();
}
// 處理登錄業(yè)務(wù) user表:id password字段
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time) {
// 1.獲取ids,password字段
int id = js["id"].get<int>();
string pwd = js["password"];
// 傳入用戶id,返回相應(yīng)數(shù)據(jù)
ConnPool* connPool = this->getConnPool();
User user = m_userModel.query(connPool,id);
if(user.getId() == id && user.getPwd() == pwd) { // 登錄成功
if(user.getState() == "online") {
//該用戶已經(jīng)登錄,不允許重復(fù)登錄
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 2; // 重復(fù)登錄
// response["errmsg"] = "該賬號(hào)已經(jīng)登錄,請(qǐng)重新輸入新賬號(hào)";
response["errmsg"] = "this account has logined, please input a new account";
conn->send(response.dump());
}
else{ // 用戶未登錄,此時(shí)登錄成功
// 登錄成功,記錄用戶連接信息
/*
在用戶登錄成功時(shí)便將用戶id與連接信息記錄在一個(gè)map映射表里,方便后續(xù)查找與使用
線程安全問(wèn)題:上述我們雖然建立了用戶id與連接的映射,但是在多線程環(huán)境下,不同的用戶
可能會(huì)在不同的工作線程中調(diào)用同一個(gè)業(yè)務(wù),可能同時(shí)有多個(gè)用戶上線,下線操作,因此要
保證map表的線程安全
*/
{
lock_guard<mutex> lock(m_connMutex);
m_userConnMap.insert({id, conn}); // 登錄成功記錄用戶連接信息
}
// id用戶登錄成功后,向redis訂閱channel(id)通道的事件
m_redis.subscribe(id);
// 登錄成功,更新用戶狀態(tài)信息 state: offline => online
user.setState("online");
m_userModel.updateState(connPool,user); // 更新用戶狀態(tài)信息
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 0;
response["id"] = user.getId();
response["name"] = user.getName();
// 查詢?cè)撚脩羰欠裼须x線消息
vector<string> vec = m_offlineMsgModel.query(connPool,id);
if(!vec.empty()) {
response["offlinemsg"] = vec;// 查詢到離線消息,發(fā)送給用戶
cout<<"查詢到離線消息,發(fā)送給用戶 :" <<response["offlinemsg"]<<endl;
// 讀取該用戶的離線消息后,把該用戶的所有離線消息刪除掉
m_offlineMsgModel.remove(connPool,id);
}
// 登錄成功,查詢?cè)撚脩舻暮糜研畔⒉⒎祷? vector<User>userVec = m_friendModel.query(connPool,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;
}
vector<Group> groupVec = m_groupModel.queryGroups(connPool,id);
if(groupVec.size() > 0) {
// cout<<"................sdsdfasas................."<<endl;
vector<string> vec3;
for(Group& group:groupVec) {
vector<GroupUser> users = group.getUsers();
json js;
js["id"] = group.getId();
js["groupname"] = group.getName();
js["groupdesc"] = group.getDesc();
vector<string> userVec;
for(GroupUser& user:users) {
json js_tmp;
js_tmp["id"] = user.getId();
js_tmp["name"] = user.getName();
js_tmp["state"] = user.getState();
js_tmp["role"] = user.getRole();
userVec.push_back(js_tmp.dump());
}
js["users"] = userVec;
vec3.push_back(js.dump());
// cout<<"js.dump() = "<<js.dump()<<endl;
}
response["groups"] = vec3;
}
conn->send(response.dump());
}
}
else {
// 該用戶不存在/用戶存在但是密碼錯(cuò)誤,登錄失敗
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 1;
// response["errmsg"] = "該用戶不存在,您輸入用戶名或者密碼可能錯(cuò)誤!";
response["errmsg"] = "This user does not exist, or the password you entered may be incorrect!";
conn->send(response.dump());
}
}
// 處理注冊(cè)業(yè)務(wù) user表:name password
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time) {
// 1.獲取name,password字段
string name = js["name"];
string pwd = js["password"];
// 處理業(yè)務(wù),操作的都是數(shù)據(jù)對(duì)象
// 2.創(chuàng)建User對(duì)象,進(jìn)行注冊(cè)
User user;
user.setName(name);
user.setPwd(pwd);
// 新用戶的插入
ConnPool* connPool = this->getConnPool();
bool state = m_userModel.insert(connPool,user);
if(state) { // 注冊(cè)成功
json response;
response["msgid"] = REG_MSG_ACK; // 注冊(cè)響應(yīng)消息
response["errno"] = 0; // 錯(cuò)誤標(biāo)識(shí) 0:成功 1:失敗
response["id"] = user.getId();
conn->send(response.dump());
}
else { // 注冊(cè)失敗
json response;
response["msgid"] = REG_MSG_ACK;
response["errno"] = 1;
conn->send(response.dump());
}
}
// 處理一對(duì)一聊天業(yè)務(wù)
void ChatService::oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time) {
// 1.先獲取目的id
int toid = js["toid"].get<int>();
{
lock_guard<mutex> lock(m_connMutex);
auto it = m_userConnMap.find(toid);
// 2.目的id在線 進(jìn)行消息轉(zhuǎn)發(fā),服務(wù)器將源id發(fā)送的消息中轉(zhuǎn)給目的id
if(it != m_userConnMap.end()) {
// toid在線,轉(zhuǎn)發(fā)消息 服務(wù)器主動(dòng)推送消息給toid用戶
it->second->send(js.dump());
return;
}
}
// 查詢toid是否在線
/*
* A向B說(shuō)話,在map表中未找到B,B可能不在本臺(tái)服務(wù)器上但通過(guò)
* 數(shù)據(jù)庫(kù)查找在線,要發(fā)送的消息直接發(fā)送以B用戶為id的通道上;
* 也可能是離線狀態(tài),發(fā)送離線消息
*/
cout<<"發(fā)送消息 :" <<js.dump()<<endl;
ConnPool* connPool = this->getConnPool();
User user = m_userModel.query(connPool,toid);
if(user.getState() == "online") {
m_redis.publish(toid, js.dump());
return;
}
// 目的id不在線,將消息存儲(chǔ)到離線消息里
m_offlineMsgModel.insert(connPool,toid, js.dump());
}
// 添加好友業(yè)務(wù) msgid id friendid
// void ChatService::addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time) {
// std::cout<<"添加好友業(yè)務(wù) msgid id friendid"<<std::endl;
// // 1.獲取當(dāng)前用戶id,要添加好友id
// int userid = js["id"].get<int>();
// int friendid = js["friendid"].get<int>();
// std::cout<<"打印當(dāng)前用戶id:"<<userid<<std::endl;
// std::cout<<"打印要添加好友id:"<<friendid<<std::endl;
// // 2.數(shù)據(jù)庫(kù)中存儲(chǔ)要添加好友的信息
// ConnPool* connPool = this->getConnPool();
// m_friendModel.insert(connPool,userid, friendid);
// }
// 添加好友業(yè)務(wù)請(qǐng)求
void ChatService::addFriendRequest(const TcpConnectionPtr &conn, json &js, Timestamp time) {
int userid = js["id"].get<int>();
int friendid = js["friendid"].get<int>();
json response;
response["msgid"] = ADD_FRIEND_REQ_MSG;
string msgStr = "用戶ID: "+to_string(userid)+" ,請(qǐng)求添加您為好友"+to_string(friendid);
response["msg"] = msgStr;
response["from"] = userid;
response["toid"] = friendid;
std::cout<<"來(lái)到這里了:"<<response.dump()<<std::endl;
oneChat(conn,response,time);
}
// 添加好友業(yè)務(wù) msgid id friendid
void ChatService::addFriendResponse(const TcpConnectionPtr &conn, json &js, Timestamp time) {
int userid = js["id"].get<int>();
int friendid = js["friendid"].get<int>();
bool flag = js["flag"].get<bool>();
json response;
response["msgid"] = ADD_FRIEND_MSG_ACK;
response["from"] = userid;
response["toid"] = friendid;
if(flag) {
response["msg"] = "I very happy to make friends with you!!!";
ConnPool* connPool = this->getConnPool();
m_friendModel.insert(connPool,userid, friendid);
}
else{
response["msg"] = "I am very sorry, you are not my friend!!!";
}
cout<<"response.dump() : "<<response.dump()<<endl;
oneChat(conn,response,time);
}
// 獲取消息msgid對(duì)應(yīng)的處理器
MsgHandler ChatService::getHandler(int msgid) {
// 記錄錯(cuò)誤日志,msgid沒(méi)有對(duì)應(yīng)的事件處理回調(diào)
auto it = m_msgHandlerMap.find(msgid);
if(it == m_msgHandlerMap.end()) {
// 返回一個(gè)默認(rèn)的處理器,空操作
return [=](const TcpConnectionPtr &conn, json &js, Timestamp) {
LOG_ERROR << "msgid:" << msgid << " can not find handler!";
};//msgid沒(méi)有對(duì)應(yīng)處理器,打印日志,返回一個(gè)默認(rèn)處理器,空操作
}
else {
return m_msgHandlerMap[msgid];
}
}
// 處理客戶端異常退出
void ChatService::clientCloseException(const TcpConnectionPtr &conn) {
User user;
{
lock_guard<mutex> lock(m_connMutex);
// 1.從map表刪除用戶的連接信息
for(auto it = m_userConnMap.begin();it!=m_userConnMap.end();++it) {
if(it->second == conn) {
// 從map表刪除用戶的鏈接信息
user.setId(it->first);
m_userConnMap.erase(it);
break;
}
}
}
// 用戶注銷,相當(dāng)于就是下線,在redis中取消訂閱通道
m_redis.unsubscribe(user.getId());
// 2.更新用戶的狀態(tài)信息
if(user.getId() != -1) {
user.setState("offline");
ConnPool* connPool = this->getConnPool();
m_userModel.updateState(connPool,user);
}
}
// 服務(wù)器異常,業(yè)務(wù)重置方法
void ChatService::reset() {
// 把online狀態(tài)的用戶,設(shè)置成offline
ConnPool* connPool = this->getConnPool();
m_userModel.resetState(connPool);
}
// 創(chuàng)建群組業(yè)務(wù)
void ChatService::createGroup(const TcpConnectionPtr &conn, json &js, Timestamp time) {
// 1.獲取創(chuàng)建群的用戶id,群名稱,群功能
int userid = js["id"].get<int>();
string name = js["groupname"];
string desc = js["groupdesc"];
// 2.存儲(chǔ)新創(chuàng)建的群組信息
ConnPool* connPool = this->getConnPool();
Group group(-1, name, desc);
if(m_groupModel.createGroup(connPool,group)) {
// 存儲(chǔ)群組創(chuàng)建人信息
m_groupModel.joinGroup(connPool,userid,group.getId(),"creator");
}
}
// 加入群組業(yè)務(wù)
void ChatService::joinGroup(const TcpConnectionPtr &conn, json &js, Timestamp time) {
int userid = js["id"].get<int>();
int groupid = js["groupid"].get<int>();
// 存儲(chǔ)用戶加入的群組信息
ConnPool* connPool = this->getConnPool();
m_groupModel.joinGroup(connPool,userid,groupid,"normal");
}
// 群組聊天業(yè)務(wù)
void ChatService::groupChat(const TcpConnectionPtr &conn, json &js, Timestamp time) {
// 1.獲取要發(fā)送消息的用戶id,要發(fā)送的群組id
int userid = js["id"].get<int>();
int groupid = js["groupid"].get<int>();
// 2.查詢?cè)撊航M其他的用戶id
ConnPool* connPool = this->getConnPool();
vector<int> useridVec = m_groupModel.queryGroupUsers(connPool,userid, groupid);
// 3.進(jìn)行用戶查找
/*
* A向B說(shuō)話,在map表中未找到B,B可能不在本臺(tái)服務(wù)器上但通過(guò)數(shù)據(jù)庫(kù)查找
* 在線,要發(fā)送的消息直接發(fā)送以B用戶為id的通道上;也可能是離線狀態(tài),
* 發(fā)送離線消息
*/
lock_guard<mutex> lock(m_connMutex);
for(int id : useridVec) {
auto it = m_userConnMap.find(id);
// 用戶在線,轉(zhuǎn)發(fā)群消息
if(it != m_userConnMap.end()) {
// 轉(zhuǎn)發(fā)群消息
it->second->send(js.dump());
}
else { // 用戶不在線,存儲(chǔ)離線消息 或 在其它服務(wù)器上登錄的
// 查詢toid是否在線
User user = m_userModel.query(connPool,id);
if(user.getState() == "online") { // 在其他服務(wù)器上登錄的
m_redis.publish(id,js.dump());
}else{
// 存儲(chǔ)離線群消息
ConnPool* connPool = this->getConnPool();
m_offlineMsgModel.insert(connPool,id, js.dump());
}
}
}
}
//處理注銷業(yè)務(wù)
void ChatService::loginOut(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
//1、獲取要注銷用戶的id,刪除對(duì)應(yīng)連接
int userid = js["id"].get<int>();
// std::cout<<"獲取要注銷用戶的id,刪除對(duì)應(yīng)連接: userid: "<<userid<<std::endl;
{
lock_guard<mutex> lock(m_connMutex);
auto it = m_userConnMap.find(userid);
if (it != m_userConnMap.end())
{
m_userConnMap.erase(it);
}
}
// 用戶注銷,相當(dāng)于就是下線,在redis中取消訂閱通道
m_redis.unsubscribe(userid);
//2、更新用戶狀態(tài)信息
User user(userid, "", "", "offline");
ConnPool* connPool = this->getConnPool();
m_userModel.updateState(connPool,user);
}
// 從redis消息隊(duì)列中獲取訂閱的消息:通道號(hào) + 消息
void ChatService::handleRedisSubscribeMessage(int userid, string msg) {
lock_guard<mutex> lock(m_connMutex);
auto it = m_userConnMap.find(userid);
if (it != m_userConnMap.end()) {
it->second->send(msg);
return;
}
// 存儲(chǔ)該用戶的離線消息:在從通道取消息時(shí),用戶下線則發(fā)送離線消息
ConnPool* connPool = this->getConnPool();
m_offlineMsgModel.insert(connPool,userid, msg);
}
/*
服務(wù)器業(yè)務(wù)模塊ChatService
服務(wù)器業(yè)務(wù)模塊:客戶端發(fā)送的業(yè)務(wù)數(shù)據(jù),先到達(dá)服務(wù)器端網(wǎng)絡(luò)模塊,
網(wǎng)絡(luò)模塊進(jìn)行事件分發(fā)到業(yè)務(wù)模塊相應(yīng)的業(yè)務(wù)處理器,最終通過(guò)數(shù)據(jù)
層訪問(wèn)底層數(shù)據(jù)模塊
3.1 用戶注冊(cè)業(yè)務(wù)
用戶注冊(cè):服務(wù)器將客戶端收到的json反序列化后存儲(chǔ)到數(shù)據(jù)庫(kù)中,依據(jù)是否
注冊(cè)成功給客戶端返回響應(yīng)消息
*/
src/server/main.cpp
#include "chatserver.hpp"
#include "chatservice.hpp"
#include <iostream>
#include <signal.h>
using namespace std;
// 處理服務(wù)器ctrl+c結(jié)束后,重置user的狀態(tài)信息
void resetHandler(int) {
ChatService::getInstance()->reset();
exit(0);
}
int main(int argc, char** argv) {
signal(SIGINT,resetHandler);
// InetAddress addr("127.0.0.1", 6000);
char* ip = argv[1];
uint16_t port = atoi(argv[2]);
InetAddress addr(ip, port);
EventLoop loop;
ChatServer server(&loop, addr, "ChatServer");
server.start();
loop.loop(); // 啟動(dòng)事件循環(huán)
return 0;
}
src/client/main.cpp
#include "json.hpp"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <chrono>
#include <ctime>
#include <map>
#include <atomic>
using namespace std;
using json = nlohmann::json;
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include "group.hpp"
#include "user.hpp"
#include "public.hpp"
// 登錄成功客戶端記錄用戶相關(guān)信息,后續(xù)無(wú)需再?gòu)姆?wù)器獲取了
User g_currentUser; // 記錄當(dāng)前系統(tǒng)登錄的用戶信息
vector<User> g_currentUserFriendList; // 記錄當(dāng)前登錄用戶的好友列表信息
vector<Group> g_currentUserGroupList; // 記錄當(dāng)前登錄用戶的群組列表信息
bool isMainMenuRunning = false; // 控制主菜單頁(yè)面程序:主菜單頁(yè)面是否正在進(jìn)行
sem_t rwsem; // 用于讀寫線程之間的通信
atomic_bool g_isLoginSuccess{false}; // 記錄登錄狀態(tài)是否成功
void mainMenu(int clientfd);
// 顯示當(dāng)前登錄成功用戶的基本信息:打印用戶id、名稱,顯示其好友列表與群組列表
void showCurrentUserData()
{
cout << "======================login user======================" << endl;
cout << "current login user id:" << g_currentUser.getId() << " name:" << g_currentUser.getName() << endl;
cout << "----------------------friend list---------------------" << endl;
if (!g_currentUserFriendList.empty())
{
for (User &user : g_currentUserFriendList)
{
cout << user.getId() << " " << user.getName() << " " << user.getState() << endl;
}
}
cout << "----------------------group list----------------------" << endl;
if (!g_currentUserGroupList.empty())
{
for (Group &group : g_currentUserGroupList)
{
cout << group.getId() << " " << group.getName() << " " << group.getDesc() << endl;
for (GroupUser &user : group.getUsers())
{
cout << user.getId() << " " << user.getName() << " " << user.getState() << " " << user.getRole() << endl;
}
}
}
cout << "======================login user======================" << endl;
}
// 處理登錄響應(yīng)邏輯
void doLoginResponse(json &responsejs)
{
if (0 != responsejs["errno"].get<int>()) // 登錄失敗
{
cerr << responsejs["errmsg"] << endl;
g_isLoginSuccess = false;
}
else // 登錄成功
{
// 記錄當(dāng)前用戶的id和name
g_currentUser.setId(responsejs["id"].get<int>());
g_currentUser.setName(responsejs["name"]);
// 記錄當(dāng)前用戶的好友列表信息
if (responsejs.contains("friends"))
{
// 初始化
g_currentUserFriendList.clear();
vector<string> vec = responsejs["friends"];
for (string &str : vec)
{
json js = json::parse(str);
User user;
user.setId(js["id"].get<int>());
user.setName(js["name"]);
user.setState(js["state"]);
g_currentUserFriendList.push_back(user);
}
}
// 記錄當(dāng)前用戶的群組列表信息
if (responsejs.contains("groups"))
{
// 初始化
g_currentUserGroupList.clear();
vector<string> vec1 = responsejs["groups"];
// cout<<"vec1.size: "<<vec1.size()<<endl;
for (string &groupstr : vec1)
{
// cout<<"groupstr: "<<groupstr<<endl;
json grpjs = json::parse(groupstr);
Group group;
group.setId(grpjs["id"].get<int>());
group.setName(grpjs["groupname"]);
group.setDesc(grpjs["groupdesc"]);
vector<string> vec2 = grpjs["users"];
for (string &userstr : vec2)
{
GroupUser user;
json js = json::parse(userstr);
user.setId(js["id"].get<int>());
user.setName(js["name"]);
user.setState(js["state"]);
user.setRole(js["role"]);
group.getUsers().push_back(user);
}
g_currentUserGroupList.push_back(group);
}
}
// 顯示登錄用戶的基本信息
showCurrentUserData();
// 顯示當(dāng)前用戶的離線消息 個(gè)人聊天信息或者群組消息
if (responsejs.contains("offlinemsg"))
{
vector<string> vec = responsejs["offlinemsg"];
for (string &str : vec)
{
json js = json::parse(str);
// time + [id] + name + " said: " + xxx
if (ONE_CHAT_MSG == js["msgid"].get<int>())
{
cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
<< " said: " << js["msg"].get<string>() << endl;
}
else if(ADD_FRIEND_REQ_MSG == js["msgid"].get<int>()) {
cout << "offline msg: " << js["msg"].get<string>() << endl;
// cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
// << " said: " << js["msg"].get<string>() << endl;
}
else if(GROUP_CHAT_MSG == js["msgid"].get<int>())
{
cout << "groupmsg[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
<< " said: " << js["msg"].get<string>() << endl;
}
}
}
g_isLoginSuccess = true;
}
}
// 處理注冊(cè)響應(yīng)邏輯
void doRegResponse(json &responsejs)
{
if (0 != responsejs["errno"].get<int>()) // errno不為0,注冊(cè)失敗
{
cerr << "name is already exist, register error!" << endl;
}
else // errno為0,注冊(cè)成功,返回userid
{
cout << "name register success, userid is " << responsejs["id"] << ", do not forget it!" << endl;
}
}
// 子線程,接收線程:接收用戶的手動(dòng)輸入
void readTaskHandler(int clientfd)
{
for (;;)
{
char buffer[1024] = {0};
int len = recv(clientfd, buffer, 1024, 0);
if (-1 == len || 0 == len)
{
close(clientfd);
exit(-1);
}
// 接收數(shù)據(jù),將網(wǎng)絡(luò)發(fā)送過(guò)來(lái)的數(shù)據(jù)反序列化為json數(shù)據(jù)對(duì)象,如果是聊天信息則打印
json js = json::parse(buffer);
int msgtype = js["msgid"].get<int>();
if (ONE_CHAT_MSG == msgtype) // 點(diǎn)對(duì)點(diǎn)聊天消息
{
cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
<< " said: " << js["msg"].get<string>() << endl;
continue;
}
if (GROUP_CHAT_MSG == msgtype) // 群消息
{
cout << "groupmsg[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
<< " said: " << js["msg"].get<string>() << endl;
continue;
}
if (LOGIN_MSG_ACK == msgtype) // 處理登錄響應(yīng)消息
{
doLoginResponse(js);
sem_post(&rwsem); // 子線程給主線程通知信號(hào)量
continue;
}
if (REG_MSG_ACK == msgtype) // 處理注冊(cè)響應(yīng)消息
{
doRegResponse(js);
sem_post(&rwsem); // 子線程給主線程通知信號(hào)量
continue;
}
}
}
// 獲取系統(tǒng)時(shí)間(聊天信息需要顯示發(fā)送時(shí)間)
string getCurrentTime()
{
auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
struct tm *ptm = localtime(&tt);
char date[60] = {0};
sprintf(date, "%d-%02d-%02d %02d:%02d:%02d",
(int)ptm->tm_year + 1900, (int)ptm->tm_mon + 1, (int)ptm->tm_mday,
(int)ptm->tm_hour, (int)ptm->tm_min, (int)ptm->tm_sec);
return std::string(date);
}
// 聊天客戶端程序:main線程用作發(fā)送線程,子線程用作接收線程
int main(int argc, char **argv)
{
if (argc < 3)
{
cerr << "command invalid ! example:./ChatClient 127.0.0.1 6000" << endl;
exit(-1);
}
// 解析通過(guò)命令行參數(shù)傳遞的ip和port
char *ip = argv[1];
uint16_t port = atoi(argv[2]);
// 創(chuàng)建client端的socket
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == clientfd)
{
cerr << "socket create error" << endl;
exit(-1);
}
// 填寫client需要連接的server信息ip + port
sockaddr_in server;
memset(&server, 0, sizeof(sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip);
// client和server進(jìn)行連接
if (-1 == connect(clientfd, (sockaddr *)&server, sizeof(sockaddr_in)))
{
cerr << "connect server error" << endl;
close(clientfd);
exit(-1);
}
// 初始化讀寫線程通信用的信號(hào)量
sem_init(&rwsem, 0, 0);
// 連接服務(wù)器成功,啟動(dòng)接收子線程
std::thread readTask(readTaskHandler, clientfd); // 底層為pthread_create()
readTask.detach(); // 底層為pthread_detach
// main線程用于接收用戶輸入,負(fù)責(zé)發(fā)送數(shù)據(jù)
for (;;)
{
// 顯示首頁(yè)面菜單:登錄、注冊(cè)、退出
cout << "======================" << endl;
cout << "1. login " << endl;
cout << "2. register" << endl;
cout << "3. quit" << endl;
cout << "======================" << endl;
cout << "please choice:";
int choice = 0;
cin >> choice;
cin.get(); // 讀掉緩沖區(qū)殘留的回車
switch (choice)
{
case 1: // login業(yè)務(wù)
{
// 獲取用戶的用戶id和密碼
int id = 0;
char pwd[50] = {0};
cout << "userid:";
cin >> id;
cin.get(); // 讀掉緩沖區(qū)殘留的回車
cout << "userpassword:";
cin.getline(pwd, 50);
// 組裝json數(shù)據(jù),將json數(shù)據(jù)對(duì)象序列化為字符串后通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)器
json js;
js["msgid"] = LOGIN_MSG;
js["id"] = id;
js["password"] = pwd;
string request = js.dump();
g_isLoginSuccess = false;
int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0);
if (len == -1)
{
cerr << "send login msg error:" << request << endl;
}
sem_wait(&rwsem); // 等待信號(hào)量,由子線程處理完登錄的響應(yīng)消息后通知主線程
if (g_isLoginSuccess) // 登錄成功
{
// 進(jìn)入聊天主菜單頁(yè)面
isMainMenuRunning = true;
mainMenu(clientfd);
}
}
break;
case 2: // register業(yè)務(wù)
{
// 獲取用戶輸入的用戶名、密碼
char name[50] = {0};
char pwd[50] = {0};
cout << "username:";
cin.getline(name, 50);
cout << "userpassword:";
cin.getline(pwd, 50);
// 組裝json數(shù)據(jù),將json數(shù)據(jù)對(duì)象序列化為字符串后通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)器
json js;
js["msgid"] = REG_MSG;
js["name"] = name;
js["password"] = pwd;
string request = js.dump();
int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0);
if (len == -1) // 響應(yīng)失敗
{
cerr << "send res msg error:" << request << endl;
}
sem_wait(&rwsem); // 等待信號(hào)量,由子線程處理完登錄的響應(yīng)消息后通知主線程
}
break;
case 3: // quit業(yè)務(wù)
{
close(clientfd);
sem_destroy(&rwsem);
exit(0);
}
default:
cerr << "invalid input!" << endl;
break;
}
}
return 0;
}
//系統(tǒng)支持的客戶端命令列表
unordered_map<string, string> commandMap = {
{"help", "顯示所有支持的命令,格式help"},
{"chat", "一對(duì)一聊天,格式chat:friendid:message"},
{"addfriend", "添加好友,格式addfriend:friendid"},
{"ackaddfriend", "響應(yīng)添加好友請(qǐng)求,格式ackaddfriend:friendid:true/false"},
{"creategroup", "創(chuàng)建群組,格式creategroup:groupname:groupdesc"},
{"addgroup", "加入群組,格式addgroup:groupid"},
{"groupchat", "群聊,格式groupchat:groupid:message"},
{"loginout","注銷,格式loginout"}
};
//幫助信息:打印系統(tǒng)所支持的命令
void help(int fd = 0, string str = "")
{
cout << "show command list >>> " << endl;
for (auto &p : commandMap)
{
cout << p.first << " : " << p.second << endl;
}
cout << endl;
}
//一對(duì)一聊天:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void chat(int, string);
//添加好友請(qǐng)求:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void addfriend(int, string);
//響應(yīng)好友請(qǐng)求
void ackaddfriend(int, string);
//創(chuàng)建群組:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void creategroup(int, string);
//加入群組:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void addgroup(int, string);
//群聊:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void groupchat(int, string);
//注銷:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void loginout(int, string);
//注冊(cè)系統(tǒng)支持的客戶端命令處理
unordered_map<string, function<void(int,string)>> commandHandlerMap = {
{"help", help},
{"chat", chat},
{"addfriend", addfriend},
{"ackaddfriend",ackaddfriend},
{"creategroup", creategroup},
{"addgroup", addgroup},
{"groupchat", groupchat},
{"loginout", loginout}
};
// 登錄成功后主聊天頁(yè)面程序:設(shè)計(jì)符合開(kāi)閉原則
void mainMenu(int clientfd)
{
help();
// cout<<"====================分割線===================="<<endl;
// showCurrentUserData();
char buffer[1024] = {0};
for (;;)
{
cin.getline(buffer, 1024); //獲取用戶輸入:命令分為兩種,有冒號(hào)的業(yè)務(wù)與無(wú)冒號(hào)的業(yè)務(wù)
string commandbuf(buffer);
string command; //存儲(chǔ)命令
int idx = commandbuf.find(":");
if (-1 == idx) //無(wú)冒號(hào)
{
command = commandbuf;
}
else //有冒號(hào)
{
command = commandbuf.substr(0, idx);
}
auto it = commandHandlerMap.find(command);
if (it == commandHandlerMap.end()) //輸入錯(cuò)誤,未找到用戶輸入對(duì)應(yīng)的業(yè)務(wù)
{
cerr << "invalid input command!" << endl;
continue;
}
//調(diào)用響應(yīng)命令的事件處理回調(diào),mainMenu對(duì)修改封閉,添加新功能不需要修改該函數(shù)
it->second(clientfd, commandbuf.substr(idx + 1,commandbuf.size() - idx)); //調(diào)用命令處理方法
if(command == "loginout") {
break;
}
}
}
// //添加好友:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
// void addfriend(int clientfd, string str)
// {
// int friendid = atoi(str.c_str());
// json js;
// js["msgid"] = ADD_FRIEND_MSG;
// js["id"] = g_currentUser.getId();
// std::cout<<"ADD_FRIEND_MSG: "<<ADD_FRIEND_MSG<<std::endl;
// std::cout<<"g_currentUser.getId(): "<<g_currentUser.getId()<<std::endl;
// js["friendid"] = friendid;
// std::cout<<"friendid: "<<friendid<<std::endl;
// string buffer = js.dump();
// int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
// if (-1 == len)
// {
// cerr << "send addfriend msg error -> " << buffer << endl;
// }
// }
//添加好友:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void addfriend(int clientfd, string str)
{
int friendid = atoi(str.c_str());
json js;
js["msgid"] = ADD_FRIEND_REQ_MSG;
js["id"] = g_currentUser.getId();
// std::cout<<"ADD_FRIEND_REQ_MSG: "<<ADD_FRIEND_REQ_MSG<<std::endl;
// std::cout<<"g_currentUser.getId(): "<<g_currentUser.getId()<<std::endl;
js["friendid"] = friendid;
// std::cout<<"friendid: "<<friendid<<std::endl;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send addfriend msg error -> " << buffer << endl;
}
}
void ackaddfriend(int clientfd, string str) {
int idx = str.find(":");
if (-1 == idx)
{
cerr << "ackaddfriend command invalid!" << endl;
return;
}
int friendid = atoi(str.substr(0, idx).c_str());
bool flag = static_cast<bool>(str.substr(idx + 1, str.size() - idx).c_str());
json js;
js["msgid"] = ADD_FRIEND_MSG_ACK;
js["id"] = g_currentUser.getId();
js["friendid"] = friendid;
js["flag"] = flag;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send ackaddfriend msg error -> " << buffer << endl;
}
}
//一對(duì)一聊天:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void chat(int clientfd, string str)
{
//解析用戶輸入的命令
int idx = str.find(":"); //friendid:message
if (-1 == idx)
{
cerr << "chat command invalid!" << endl;
return;
}
int friendid = atoi(str.substr(0, idx).c_str());
string message = str.substr(idx + 1, str.size() - idx);
json js;
js["msgid"] = ONE_CHAT_MSG;
js["id"] = g_currentUser.getId();
js["name"] = g_currentUser.getName();
js["toid"] = friendid;
js["msg"] = message;
js["time"] = getCurrentTime();
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send chat msg error -> " << buffer << endl;
}
}
// 創(chuàng)建群組:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void creategroup(int clientfd, string str)
{
int idx = str.find(":");
if (-1 == idx)
{
cerr << "creategroup command invalid!" << endl;
return;
}
string groupname = str.substr(0, idx);
string groupdesc = str.substr(idx + 1, str.size() - idx);
json js;
js["msgid"] = CREATE_GROUP_MSG;
js["id"] = g_currentUser.getId();
js["groupname"] = groupname;
js["groupdesc"] = groupdesc;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send creategroup msg error -> " << buffer << endl;
}
}
// 加入群組:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void addgroup(int clientfd, string str)
{
int groupid = atoi(str.c_str());
json js;
js["msgid"] = ADD_GROUP_MSG;
js["id"] = g_currentUser.getId();
js["groupid"] = groupid;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send addgroup msg error -> " << buffer << endl;
}
}
// 群聊:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void groupchat(int clientfd, string str)
{
int idx = str.find(":");
if (-1 == idx)
{
cerr << "groupchat command invalid!" << endl;
return;
}
int groupid = atoi(str.substr(0, idx).c_str());
string message = str.substr(idx + 1, str.size() - idx);
json js;
js["msgid"] = GROUP_CHAT_MSG;
js["id"] = g_currentUser.getId();
js["name"] = g_currentUser.getName();
js["groupid"] = groupid;
js["msg"] = message;
js["time"] = getCurrentTime();
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send groupchat msg error -> " << buffer << endl;
}
}
// 注銷:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù)
void loginout(int clientfd, string str)
{
json js;
js["msgid"] = LOGIN_OUT_MSG;
js["id"] = g_currentUser.getId();
string buffer = js.dump();
std::cout<<"注銷:int接收sockfd,string接收用戶發(fā)送的數(shù)據(jù) buffer: "<<buffer<<std::endl;
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send loginout msg error -> " << buffer << endl;
}
else
{
isMainMenuRunning = false;
}
cout<<"isMainMenuRunning: "<<isMainMenuRunning<<endl;
}
autobash.sh
set -x
rm -rf `pwd`/build/*
cmake -B build
cmake --build build
?CMakeLists.txt
cmake_minimum_required(VERSION 3.28.0)
project(chat)
# 配置編譯選項(xiàng)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)
# 配置可執(zhí)行文件生成路徑
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 配置頭文件搜索路徑
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/include/server)
include_directories(${PROJECT_SOURCE_DIR}/include/server/db)
include_directories(${PROJECT_SOURCE_DIR}/include/server/model)
include_directories(${PROJECT_SOURCE_DIR}/include/server/redis)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty)
# 加載子目錄
add_subdirectory(src)
src/CMakeLists.txt
add_subdirectory(server)
add_subdirectory(client)
?src/server/CMakeLists.txt
# 定義了一個(gè)SRC_LIST變量 包含了該目錄下所有的源文件
aux_source_directory(. SRC_LIST)
aux_source_directory(./db DB_LIST)
aux_source_directory(./model MODEL_LIST)
aux_source_directory(./redis REDIS_LIST)
# 指定生成可執(zhí)行文件
add_executable(ChatServer ${SRC_LIST} ${DB_LIST} ${MODEL_LIST} ${REDIS_LIST})
# 指定可執(zhí)行文件鏈接時(shí)需要依賴的庫(kù)文件
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient hiredis pthread)
?src/client/CMakeLists.txt
# 定義了一個(gè)SRC_LIST變量,包含了該目錄下所有的源文件
aux_source_directory(. SRC_LIST)
# 指定生成可執(zhí)行文件
add_executable(ChatClient ${SRC_LIST})
# 指定可執(zhí)行文件鏈接時(shí)需要依賴的庫(kù)文件
target_link_libraries(ChatClient pthread)
完整項(xiàng)目:
heheda102410/ChatServer: C++集群聊天服務(wù)器 nginx+redis+muduo+mysql數(shù)據(jù)庫(kù)連接池 (github.com)https://github.com/heheda102410/ChatServer
heheda@linux:~/Linux/Chat/bin$ ./ChatClient 127.0.0.1 8888
======================
1. login
2. register
3. quit
======================
please choice:1
userid:1
userpassword:1024
======================login user======================
current login user id:1 name:heheda
----------------------friend list---------------------
8 coco offline
9 daoji offline
2 Tom offline
3 Jerry offline
----------------------group list----------------------
3 1 C++ Chat Group
1 heheda online creator
2 Tom offline normal
3 Jerry offline normal
======================login user======================
groupmsg[3]:2024-02-15 19:19:29 [3]Jerry said: wwoahis
show command list >>>
addgroup : 加入群組,格式addgroup:groupid
creategroup : 創(chuàng)建群組,格式creategroup:groupname:groupdesc
ackaddfriend : 響應(yīng)添加好友請(qǐng)求,格式ackaddfriend:friendid:true/false
loginout : 注銷,格式loginout
addfriend : 添加好友,格式addfriend:friendid
groupchat : 群聊,格式groupchat:groupid:message
chat : 一對(duì)一聊天,格式chat:friendid:message
help : 顯示所有支持的命令,格式help
heheda@linux:~/Linux/Chat/bin$ ./ChatClient 127.0.0.1 8888
======================
1. login
2. register
3. quit
======================
please choice:1
userid:2
userpassword:520
======================login user======================
current login user id:2 name:Tom
----------------------friend list---------------------
1 heheda online
8 coco offline
----------------------group list----------------------
3 1 C++ Chat Group
1 heheda online creator
2 Tom online normal
3 Jerry offline normal
======================login user======================
show command list >>>
addgroup : 加入群組,格式addgroup:groupid
creategroup : 創(chuàng)建群組,格式creategroup:groupname:groupdesc
ackaddfriend : 響應(yīng)添加好友請(qǐng)求,格式ackaddfriend:friendid:true/false
loginout : 注銷,格式loginout
addfriend : 添加好友,格式addfriend:friendid
groupchat : 群聊,格式groupchat:groupid:message
chat : 一對(duì)一聊天,格式chat:friendid:message
help : 顯示所有支持的命令,格式help
推薦文章:?
jsoncpp庫(kù)和nlohmann-json庫(kù)實(shí)現(xiàn)JSON與字符串類型轉(zhuǎn)換_jsoncpp string轉(zhuǎn)json-CSDN博客https://blog.csdn.net/gezongbo/article/details/132083993
ubuntu 垃圾清理的方式 - 簡(jiǎn)書(shū) (jianshu.com)https://www.jianshu.com/p/5cba6a541eb9文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-828016.html
linux機(jī)器報(bào)錯(cuò): 設(shè)備上沒(méi)有空間_fatal error: error closing /tmp/cczy5luu.s: 設(shè)備上沒(méi)有空-CSDN博客https://blog.csdn.net/qq_45003354/article/details/135698562本地代碼上傳至github的兩種方法_如何將本地代碼上傳到github-CSDN博客https://blog.csdn.net/weixin_62526435/article/details/128386541文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-828016.html
到了這里,關(guān)于C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!