国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下)

這篇具有很好參考價(jià)值的文章主要介紹了C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

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
}

C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下),數(shù)據(jù)庫(kù),服務(wù)器,nginx,redis,muduo,數(shù)據(jù)庫(kù)連接池

執(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);
}

C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下),數(shù)據(jù)庫(kù),服務(wù)器,nginx,redis,muduo,數(shù)據(jù)庫(kù)連接池

  • 執(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

C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下),數(shù)據(jù)庫(kù),服務(wù)器,nginx,redis,muduo,數(shù)據(jù)庫(kù)連接池

  • 執(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;
}

C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下),數(shù)據(jù)庫(kù),服務(wù)器,nginx,redis,muduo,數(shù)據(jù)庫(kù)連接池

  • ?執(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

C++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下),數(shù)據(jù)庫(kù),服務(wù)器,nginx,redis,muduo,數(shù)據(jù)庫(kù)連接池

  • 執(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/ChatServerC++集群聊天服務(wù)器 muduo+nginx+redis+mysql數(shù)據(jù)庫(kù)連接池 筆記 (下),數(shù)據(jù)庫(kù),服務(wù)器,nginx,redis,muduo,數(shù)據(jù)庫(kù)連接池

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

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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • C++集群聊天服務(wù)器 網(wǎng)絡(luò)模塊+業(yè)務(wù)模塊+CMake構(gòu)建項(xiàng)目 筆記 (上)

    C++集群聊天服務(wù)器 網(wǎng)絡(luò)模塊+業(yè)務(wù)模塊+CMake構(gòu)建項(xiàng)目 筆記 (上)

    跟著施磊老師做C++項(xiàng)目,施磊老師_騰訊課堂 (qq.com) 一、網(wǎng)絡(luò)模塊ChatServer chatserver.hpp chatserver.cpp json 里邊會(huì)包含一個(gè) msgid .由于客戶端和服務(wù)器通信收發(fā)消息,需要判斷這個(gè)消息是屬于哪種業(yè)務(wù)的,就需要一個(gè)業(yè)務(wù)的標(biāo)識(shí),所以就用 msgid 來(lái)表示業(yè)務(wù)的標(biāo)識(shí).在 onMessage 函數(shù)中,并不想

    2024年02月20日
    瀏覽(57)
  • 基于ssm+shiro+redis+nginx tomcat服務(wù)器集群管理項(xiàng)目

    畢業(yè)設(shè)計(jì)——基于ssm+shiro+redis+nginx tomcat服務(wù)器集群管理項(xiàng)目 完整項(xiàng)目地址:https://download.csdn.net/download/lijunhcn/88430549 1.搭建一個(gè)最簡(jiǎn)潔,模塊劃分最明確的ssm+swargger+shiro+redis+nginx整合項(xiàng)目,采用maven作為構(gòu)建工具,在有新項(xiàng)目開(kāi)發(fā)時(shí)可以借助此demo快速構(gòu)建項(xiàng)目 2.實(shí)現(xiàn)shiro的授

    2024年02月03日
    瀏覽(21)
  • 畢業(yè)設(shè)計(jì)——基于ssm+shiro+redis+nginx tomcat服務(wù)器集群管理項(xiàng)目

    畢業(yè)設(shè)計(jì)——基于ssm+shiro+redis+nginx tomcat服務(wù)器集群管理項(xiàng)目 完整項(xiàng)目地址:https://download.csdn.net/download/lijunhcn/88430549 1.搭建一個(gè)最簡(jiǎn)潔,模塊劃分最明確的ssm+swargger+shiro+redis+nginx整合項(xiàng)目,采用maven作為構(gòu)建工具,在有新項(xiàng)目開(kāi)發(fā)時(shí)可以借助此demo快速構(gòu)建項(xiàng)目 2.實(shí)現(xiàn)shiro的授

    2024年02月04日
    瀏覽(24)
  • 一、C++項(xiàng)目:仿muduo庫(kù)實(shí)現(xiàn)高性能高并發(fā)服務(wù)器

    一、C++項(xiàng)目:仿muduo庫(kù)實(shí)現(xiàn)高性能高并發(fā)服務(wù)器

    仿mudou庫(kù)one thread oneloop式并發(fā)服務(wù)器實(shí)現(xiàn) 仿muduo庫(kù)One Thread One Loop式主從Reactor模型實(shí)現(xiàn)高并發(fā)服務(wù)器: 通過(guò)實(shí)現(xiàn)的高并發(fā)服務(wù)器組件,可以簡(jiǎn)潔快速的完成一個(gè)高性能的服務(wù)器搭建。并且,通過(guò)組件內(nèi)提供的不同應(yīng)用層協(xié)議支持,也可以快速完成一個(gè)高性能應(yīng)用服務(wù)器的搭建

    2024年02月07日
    瀏覽(34)
  • Nginx(7)Nginx實(shí)現(xiàn)服務(wù)器端集群搭建

    Nginx(7)Nginx實(shí)現(xiàn)服務(wù)器端集群搭建

    前面課程已經(jīng)將Nginx的大部分內(nèi)容進(jìn)行了講解,我們都知道了Nginx在高并發(fā)場(chǎng)景和處理靜態(tài)資源是非常高性能的,但是在實(shí)際項(xiàng)目中除了靜態(tài)資源還有就是后臺(tái)業(yè)務(wù)代碼模塊,一般后臺(tái)業(yè)務(wù)都會(huì)被部署在Tomcat,weblogic或者是websphere等web服務(wù)器上。那么如何使用Nginx接收用戶的請(qǐng)

    2024年02月09日
    瀏覽(20)
  • 概述、搭建Redis服務(wù)器、部署LNP+Redis、創(chuàng)建Redis集群、連接集群、集群工作原理

    概述、搭建Redis服務(wù)器、部署LNP+Redis、創(chuàng)建Redis集群、連接集群、集群工作原理

    Top 案例1:搭建redis服務(wù)器 案例2:常用命令限 案例3:部署LNP+Redis 案例4:創(chuàng)建redis集群 1.1 具體要求如下 在主機(jī)redis64運(yùn)行redis服務(wù) 修改服務(wù)運(yùn)行參數(shù) ip 地址192.168.88.64 服務(wù)監(jiān)聽(tīng)的端口6364 redis服務(wù)的連接密碼為 tarenaplj 1.2 方案 準(zhǔn)備1臺(tái)新虛擬機(jī),要求如表-1所示。 ? 1.3 步驟 實(shí)

    2024年02月12日
    瀏覽(114)
  • 一臺(tái)服務(wù)器上部署 Redis 偽集群

    一臺(tái)服務(wù)器上部署 Redis 偽集群

    哈嘍大家好,我是咸魚(yú) 今天這篇文章介紹如何在一臺(tái)服務(wù)器(以 CentOS 7.9 為例)上通過(guò) redis-trib.rb 工具搭建 Redis cluster (三主三從) redis-trib.rb 是一個(gè)基于 Ruby 編寫的腳本,其功能涵蓋了創(chuàng)建、管理以及維護(hù) Redis 集群的各個(gè)方面 值得注意的是,隨著時(shí)間的推移,一些較新版

    2024年02月11日
    瀏覽(33)
  • minio集群部署,4臺(tái)服務(wù)器+1臺(tái)nginx

    分布式Minio里所有的節(jié)點(diǎn)需要有同樣的access秘鑰和secret秘鑰,即:用戶名和密碼 分布式Minio存放數(shù)據(jù)的磁盤目錄必須是空目錄 分布式Minio官方建議生產(chǎn)環(huán)境最少4個(gè)節(jié)點(diǎn),因?yàn)橛蠳個(gè)節(jié)點(diǎn),得至少保證有N/2的節(jié)點(diǎn)才能可讀,保證至少N/2+1的節(jié)點(diǎn)才能可寫。這里只是作演示搭建,只

    2024年02月15日
    瀏覽(31)
  • docker服務(wù)器中redis-cluster集群配置(redis-5.0.7)

    因?yàn)樾枰褂玫絛ocker服務(wù)器下的redis-cluster集群環(huán)境,而以前redis3.2.8版本的redis搭配起來(lái)費(fèi)事費(fèi)力還沒(méi)有成功,所以使用了較新一些的redis版本----redis-5.0.7。 默認(rèn):dockers已經(jīng)安裝成功 1.1下載tar包 1.2把進(jìn)行安裝 2.1 編寫配置文件 #編寫目錄 mkdir -p /usr/local/docker-redis/redis-cluster #切

    2024年02月21日
    瀏覽(26)
  • Redis持久化說(shuō)明及其單臺(tái)Linux服務(wù)器搭建Redis集群架構(gòu)

    Redis持久化說(shuō)明及其單臺(tái)Linux服務(wù)器搭建Redis集群架構(gòu)

    說(shuō)明:RDB快照主要以二進(jìn)制文件的形式進(jìn)行存儲(chǔ)數(shù)據(jù),主要以文件名dump.rdb進(jìn)行存儲(chǔ),主要設(shè)置redis.conf里面設(shè)置’save 60 1000’命令可以開(kāi)啟, 表示在60秒內(nèi)操作1000次進(jìn)行一次備份數(shù)據(jù)。在客戶端執(zhí)行save(同步)和bgsave(異步操作)。 redis.conf 啟動(dòng)redis相關(guān)命令 說(shuō)明:主要把文件生

    2024年02月10日
    瀏覽(32)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包