C++项目——集群聊天服务器项目(十四)客户端业务

大家好~前段时间有些事情需要处理,没来得及更新,实在不好意思。

今天来继续更新集群聊天服务器项目的客户端功能,主要实现客户端业务,包括添加好友、点对点聊天、创建群组、添加群组、群组聊天业务,接下来我们一起来敲代码吧!

一、业务功能介绍

登录成功后,客户端界面将显示您想进行的功能,我们定义一个哈希表,存储服务器功能和功能对应的,命令格式。

// 客户端命令列表
unordered_map<string, string> CommandMap{
    {"help", "显示所有支持的命令 格式 help"},
    {"chat", "一对一聊天 格式 chat:friendid:message"},
    {"addfriend", "添加好友 格式 addfriend:friendid"},
    {"creategroup", "创建群组 格式 creategroup:groupname:groupdesc"},
    {"addgroup", "加入群组 格式 addgroup:groupid"},
    {"groupchat", "群聊 格式 groupchat:groupid:message"},
    {"loginout", "注销 格式 loginout"},
};

在用户成功登录后,显示至界面上,帮助用户完成操作

相应的,我们来分别定义上述七个函数完成功能实现,在src/client/main.cpp中进行实现

1.1 帮助命令

help(int,string)负责显示服务器所有支持的命令

// help command Handler
void help(int, string)
{
    cout << "--------------show command list-------------" << endl;
    for (auto &p : CommandMap)
    {
        cout << p.first << " : " << p.second << endl;
    }
    cout << endl;
}

输入:int为clientfd

           string:为服务器经序列化操作后传输过来的response

1.2 添加好友

help中规定,添加好友业务规则:addfriend:friendid,仅需知道想要添加的好友id即可

// addfriend command Handler         "addfriend:friendid"
void addfriend(int clientfd, string str)
{
    int friendid = atoi(str.c_str());
    json js;
    js["msgid"] = ADD_FRIEND_MSG;
    js["id"] = g_currentUser.getId();
    js["friendid"] = friendid;

    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;
    }
}

步骤:

(1)获取要添加的好友id

(2)创建json对象,设置当前业务为添加好友业务,将用户msgid、用户id、好友id添加至json对象中,经序列化操作发送给服务器端完成添加好友操作。

注:send仅表示将用户空间的buffer拷贝到内核空间的TCP发送缓冲区中,就返回了,TCP发送缓冲区的内容由内核TCP协议栈发送出去,send没有失败不代表数据到达对端。

1.3 点对点聊天业务

help中规定,点对点聊天业务发送规则为:chat:friendid:message

// chat command Handler              "chat:friendid:message"
void chat(int clientfd, string str)
{
    int idx = str.find(":");
    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;
    }
}

步骤:

(1)除了获取聊天好友id,还要获取聊天信息,我们使用stl库string的查找功能,定位到“:”,分别截取聊天好友id和聊天信息。

(2)构造json对象,设置当前业务为点对点聊天业务,将聊天对象、信息、用户id和name存入json,参考某聊天软件,聊天时会携带时间信息,这里将聊天时间也存储进入

(3)经json序列化发送给服务器,处理点对点聊天业务

1.4 创建群组业务

help中规定,创建群组业务规则为:creategroup:groupname:groupdesc

// creategroup command Handler       “creategroup:groupname:groupdesc
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;
    }
}

步骤:

(1)使用string的查找功能,定位到“:”,分别截取要创建的群组名和群组描述信息。

(2)构造json对象,设置当前业务为创建群组业务,将群组名、群组描述信息和创建群组的人,即用户id存入json

(3)经json序列化发送给服务器,处理创建群组业务

1.5 添加群组业务

help中规定,添加群组业务规则为:addgroup:groupid

// addgroup command Handler              "addgroup:groupid"
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;
    }
}

步骤:

(1)获取想添加的群组id

(2)构造json对象,设置当前业务为添加群组业务,将用户id、群组id存入json

(3)经json序列化发送给服务器,处理添加群组业务

1.6 群组聊天业务

help中规定,群组聊天业务规则为:groupchat:groupid:message

// groupchat command Handler         "groupchat:groupid:message"
void groupchat(int clientfd, string str)
{
    int idx = str.find(":");
    if (-1 == idx)
    {
        cerr << "groupchat command invalid1" << 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;
    }
}

步骤:

(1)使用string的查找功能,定位到“:”,分别截取群组id和群组聊天信息。

(2)构造json对象,设置当前业务为群组聊天业务,将群组id、用户id、用户名、群聊信息、当前时间存入json

(3)经json序列化发送给服务器,处理群组聊天业务

二、用户退出登录

用户可以选择退出账号,因此我们实现用户退出登录功能

在公共的public.hpp中,添加退出登录MSGID

#ifndef PUBLIC_H
#define PUBLIC_H

/*
server和client的公共文件    MSGID值
*/
enum EnMsgType
{
    LOGIN_MSG = 1, // 1:登录消息
    LOGIN_MSG_ACK, // 2:登录响应消息
    REG_MSG,     // 3:注册消息
    REG_MSG_ACK, // 4:注册响应消息
    ONE_CHAT_MSG,   // 5:聊天消息
    ADD_FRIEND_MSG, // 6:添加好友消息
    ADD_FRIEND_MSG_ACK, // 7:添加好友响应消息
    CREATE_GROUP_MSG, // 8:创建群组
    CREATE_GROUP_MSG_ACK, // 9:创建群组响应消息
    ADD_GROUP_MSG, // 10:加入群组
    ADD_GROUP_MSG_ACK, // 11:加入群组响应消息
    GROUP_CHAT_MSG, // 12:群聊天
    LOGINOUT_MSG, // 13:注销消息
};

#endif

在src/client/main.cpp中,定义用户退出登录函数

void loginout(int clientfd, string str){
    json js;
    js["msgid"] =LOGINOUT_MSG ;
    js["id"] = g_currentUser.getId();
    string buffer = js.dump();
    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;
    }
}

步骤:(1)定义json对象,设置当前处理业务为退出登录的MSGID,携带当前用户id存储于json对象中

(2)序列化发送至服务器,让服务器来处理退出登录业务,如果退出登录,设置不再显示主菜单

在include/server/chatservice.hpp中的ChatService业务类,添加退出登录函数

    // 处理客户端异常退出
    void clientCloseException(const TcpConnectionPtr &conn);

在src/server/chatservice.cpp中进行实现

// 处理注销业务
void ChatService::Loginout(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    int userid = js["id"].get<int>();
    {
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(userid);
        if (it != _userConnMap.end())
        {
            _userConnMap.erase(it);
        }
    }

    // 更新用户状态信息
    User user(userid, "", "", "offline");
    _userModel.updateState(user);
}

只需将在线用户连接中所属用户的连接删掉即可,并将数据库中存储的用户在线信息修改为offline离线即可

添加至构造函数中进行业务绑定

// 注销业务
    _msgHandlerMap.insert({LOGINOUT_MSG, std::bind(&ChatService::Loginout, this, _1, _2, _3)});

三、业务处理

我们来定义一个客户端命令处理的哈希,目的是从序列化获取的字符串中获取业务,定位到不同的功能处理函数

// 客户端命令处理
unordered_map<string, function<void(int, string)>> CommandHandlerMap = {
    {"help", help},
    {"chat", chat},
    {"addfriend", addfriend},
    {"creategroup", creategroup},
    {"addgroup", addgroup},
    {"groupchat", groupchat},
    {"loginout", loginout},
};

登录成功后,显示主聊天程序

// 主聊天页面程序
void mainMenu(int clientfd)
{
    help();
    char buffer[1024] = {0};
    while (isMainMenuRunning)
    {
        cin.getline(buffer, 1024);      // 捕获客户端命令
        string commandbuf(buffer);      // 由char * 转为string类型
        string command;                 // 存储命令
        int idx = commandbuf.find(":"); // 判断是否为help命令或loginout命令
        if (-1 == idx)
        {
            command = commandbuf;
        }
        else
        {
            command = commandbuf.substr(0, idx); // 截取命令类型
        }
        auto it = CommandHandlerMap.find(command);
        if (it == CommandHandlerMap.end())
        {
            cerr << "invalid input command!" << endl;
            continue;
        }

        // 调用相应命令的事件处理回调,mainMenu对修改封闭,添加新功能无需更改该函数
        it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - idx)); // 调用命令处理方法
    }
}

步骤:

(1)定义缓存buffer,捕获客户端的命令,并转为string类型方便查找

(2)从字符串中获取“:”,截取业务功能字段,如果存在相应指令,定位到哈希表存储的功能函数中,如不存在,退出即可

至此,所有客户端代码实现完毕,整体代码如下:

src/client/main.cpp中

// 自定义头文件
#include "json.hpp"
#include "group.hpp"
#include "user.hpp"
#include "public.hpp"
using json = nlohmann::json;

// 标准库头文件
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <chrono>
#include <ctime>
#include <unordered_map>
#include <functional>
using namespace std;

#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include <atomic>

// 全局变量、对象、函数
User g_currentUser;                   // 记录当前系统登录的用户信息
vector<User> g_currentUserFriendList; // 记录当前登录用户的好友列表信息
vector<Group> g_currentUserGroupList; // 记录当前登录用户的群组列表信息
bool isMainMenuRunning = false;       // 控制主菜单页面程序

// 信号量
sem_t rwsem; // 用于读写线程之间的通信

/*atomic原子变量是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰
原子变量它具有类似于普通变量的操作,但是这些操作都是原子级别的,即要么全部完成,要么全部未完成。*/

atomic_bool g_isLoginSuccess{false}; // 记录登录状态           atomic防止线程安全引发的静态条件问题

// 获取系统时间(聊天信息需要添加时间信息)
string getCurrentTime();

// 显示当前登录成功用户的基本信息
void showCurrentUserData();

// 主聊天页面程序
void mainMenu(int);

// 接收线程
void readTaskHandler(int clientfd);

// 聊天客户端程序实现,main线程用作发送线程,子线程用作接收线程
int main(int argc, char **argv) // argc 参数个数    argv 参数序列或指针
{
    if (argc < 3)
    {
        cerr << "command invalid! example: ./ChatClient 127.0.0.1 6000" << endl; // cerr:程序错误信息
        exit(-1);
    }

    // 解析通过命令行参数传递的ip和port
    char *ip = argv[1];
    uint16_t port = atoi(argv[2]);

    // 创建client端的socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IPv4  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)); // memset清0

    server.sin_family = AF_INET;            // IPv4
    server.sin_port = htons(port);          // 端口
    server.sin_addr.s_addr = inet_addr(ip); // ip地址

    // client和server进行连接
    if (-1 == connect(clientfd, (sockaddr *)&server, sizeof(sockaddr_in)))
    {
        cerr << "connect server error" << endl;
        close(clientfd);
        exit(-1);
    }

    // 初始化读写线程通信用的信号量
    sem_init(&rwsem, 0, 0);

    // 连接服务器成功,启动接收子线程
    std::thread readTask(readTaskHandler, clientfd); // pthread_create
    readTask.detach();                               // pthread_detach

    // main线程用于接收用户输入,负责发送数据
    for (;;)
    {
        // 显示首页面菜单 登录、注册、退出
        cout << "========================" << endl;
        cout << "======  1. login  ======" << endl;
        cout << "======  2. register  ===" << endl;
        cout << "======  3. quit  =======" << endl;
        cout << "========================" << endl;
        cout << "Please input your choice:";
        int choice = 0;
        cin >> choice; // 读取功能选项
        cin.get();     // 读掉缓冲区残留的回车

        switch (choice)
        {
        case 1: // 登录业务
        {
            int id = 0;
            char pwd[50] = {0};
            cout << "user id:";
            cin >> id;
            cin.get(); // 读掉缓冲区残留的回车
            cout << "user password:";
            cin.getline(pwd, 50);

            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); // 通过网络send给服务器端
            if (len == -1)
            {
                cerr << "send login msg error:" << request << endl;
            }

            sem_wait(&rwsem); // 等待信号量,由子线程处理完登录的响应消息后,通知这里

            if (g_isLoginSuccess)
            {
                // 进入聊天主菜单页面
                isMainMenuRunning = true;
                mainMenu(clientfd);
            }
        }
        break;
        case 2: // 注册业务
        {
            char name[50] = {0};
            char pwd[50] = {0};
            cout << "user name:";
            cin.getline(name, 50);
            cout << "user password:";
            cin.getline(pwd, 50);

            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); // 通过网络send给服务器端
            if (len == -1)
            {
                cerr << "send reg msg error:" << request << endl;
            }

            sem_wait(&rwsem); // 等待信号量,子线程处理完注册消息会通知
        }
        break;
        case 3: // 退出业务
            close(clientfd);
            sem_destroy(&rwsem);
            exit(0);
        default:
            cerr << "invalid input!" << endl;
            break;
        }
    }

    return 0;
}

// 显示当前登录成功用户的基本信息
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 << 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 << endl;
        }
    }
    cout << "======================================================" << endl;
}

// 获取系统时间(聊天信息需要添加时间信息)
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);
}

// 处理注册的响应逻辑
void doRegResponse(json &responsejs)
{
    if (0 != responsejs["errno"].get<int>()) // 注册失败
    {
        cerr << "name is already exist, register error!" << endl;
    }
    else // 注册成功
    {
        cout << "name register success, userid is " << responsejs["id"]
             << ", do not forget it!" << endl;
    }
}

// 处理登录的响应逻辑
void doLoginResponse(json &responsejs)
{
    if (0 != responsejs["errno"].get<int>()) // 登录失败
    {
        cerr << responsejs["errmsg"] << endl;
        g_isLoginSuccess = false;
    }
    else // 登录成功
    {
        // 记录当前用户的id和name
        g_currentUser.setId(responsejs["id"].get<int>());
        g_currentUser.setName(responsejs["name"]);

        // 记录当前用户的好友列表信息
        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);
            }
        }

        // 记录当前用户的群组列表信息
        if (responsejs.contains("groups"))
        {
            // 初始化
            g_currentUserGroupList.clear();

            vector<string> vec1 = responsejs["groups"];
            for (string &groupstr : vec1)
            {
                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();

        // 显示当前用户的离线消息  个人聊天信息或者群组消息
        if (responsejs.contains("offlinemsg"))
        {
            vector<string> vec = responsejs["offlinemsg"];
            for (string &str : vec)
            {
                json js = json::parse(str);                 // 反序列化
                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 // 群聊
                {
                    cout << "群消息[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
                         << " said: " << js["msg"].get<string>() << endl;
                }
            }
        }

        g_isLoginSuccess = true;        //登录状态
    }
}

// 子线程 - 接收线程
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);
        }

        // 接收服务器转发的数据,反序列化生成json数据对象
        json js = json::parse(buffer);
        int msgtype = js["msgid"].get<int>(); // 获取处理业务msgid
        if (ONE_CHAT_MSG == msgtype)          // 点对点聊天
        {
            cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
                 << " said: " << js["msg"].get<string>() << endl;
            continue;
        }

        if (GROUP_CHAT_MSG == msgtype) // 群聊
        {
            cout << "群消息[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()
                 << " said: " << js["msg"].get<string>() << endl;
            continue;
        }

        if (LOGIN_MSG_ACK == msgtype) // 登录业务
        {
            doLoginResponse(js); // 处理登录响应的业务逻辑
            sem_post(&rwsem);    // 通知主线程,登录结果处理完成
            continue;
        }

        if (REG_MSG_ACK == msgtype) // 注册业务
        {
            doRegResponse(js); // 处理注册响应的业务逻辑
            sem_post(&rwsem);  // 通知主线程,注册结果处理完成
            continue;
        }
    }
}

// 客户端命令列表
unordered_map<string, string> CommandMap{
    {"help", "显示所有支持的命令 格式 help"},
    {"chat", "一对一聊天 格式 chat:friendid:message"},
    {"addfriend", "添加好友 格式 addfriend:friendid"},
    {"creategroup", "创建群组 格式 creategroup:groupname:groupdesc"},
    {"addgroup", "加入群组 格式 addgroup:groupid"},
    {"groupchat", "群聊 格式 groupchat:groupid:message"},
    {"loginout", "注销 格式 loginout"},
};

// help command Handler
void help(int fd = 0, string = "");
// chat command Handler
void chat(int, string);
// addfriend command Handler
void addfriend(int, string);
// creategroup command Handler
void creategroup(int, string);
// addgroup command Handler
void addgroup(int, string);
// groupchat command Handler
void groupchat(int, string);
// oginout command Handler
void loginout(int, string);

// 客户端命令处理
unordered_map<string, function<void(int, string)>> CommandHandlerMap = {
    {"help", help},
    {"chat", chat},
    {"addfriend", addfriend},
    {"creategroup", creategroup},
    {"addgroup", addgroup},
    {"groupchat", groupchat},
    {"loginout", loginout},
};

// 主聊天页面程序
void mainMenu(int clientfd)
{
    help();
    char buffer[1024] = {0};
    while (isMainMenuRunning)
    {
        cin.getline(buffer, 1024);      // 捕获客户端命令
        string commandbuf(buffer);      // 由char * 转为string类型
        string command;                 // 存储命令
        int idx = commandbuf.find(":"); // 判断是否为help命令或loginout命令
        if (-1 == idx)
        {
            command = commandbuf;
        }
        else
        {
            command = commandbuf.substr(0, idx); // 截取命令类型
        }
        auto it = CommandHandlerMap.find(command);
        if (it == CommandHandlerMap.end())
        {
            cerr << "invalid input command!" << endl;
            continue;
        }

        // 调用相应命令的事件处理回调,mainMenu对修改封闭,添加新功能无需更改该函数
        it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - idx)); // 调用命令处理方法
    }
}

// help command Handler
void help(int, string)
{
    cout << "--------------show command list-------------" << endl;
    for (auto &p : CommandMap)
    {
        cout << p.first << " : " << p.second << endl;
    }
    cout << endl;
}

// chat command Handler              "chat:friendid:message"
void chat(int clientfd, string str)
{
    int idx = str.find(":");
    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;
    }
}

// addfriend command Handler         "addfriend:friendid"
void addfriend(int clientfd, string str)
{
    int friendid = atoi(str.c_str());
    json js;
    js["msgid"] = ADD_FRIEND_MSG;
    js["id"] = g_currentUser.getId();
    js["friendid"] = friendid;

    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;
    }
}

// creategroup command Handler       “creategroup:groupname:groupdesc
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;
    }
}

// addgroup command Handler              "addgroup:groupid"
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;
    }
}
// groupchat command Handler         "groupchat:groupid:message"
void groupchat(int clientfd, string str)
{
    int idx = str.find(":");
    if (-1 == idx)
    {
        cerr << "groupchat command invalid1" << 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;
    }
}
// loginout command Handler
void loginout(int clientfd, string str){
    json js;
    js["msgid"] =LOGINOUT_MSG ;
    js["id"] = g_currentUser.getId();
    string buffer = js.dump();
    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;
    }
}

服务器端只要绑定注销函数即可,这里不再显示代码

四、功能验证

4.1 添加好友功能

让23号与24号分别添加好友,退出登录后再次登录可以发现,好友已经添加成功

4.2 点对点聊天业务(离线消息)

23登录,给24发送Hello?此时24不在线

24上线后,收到23发来的离线消息,成功显示

23与24开始聊天

4.3 群组业务

先来查看一下底层群组表,发现23 13 15均在2号群中

4.3.1 添加群组功能

登录23号用户,添加2号群

4.3.2 群组聊天业务

13 23 22在聊天,15不在线,收到的是离线消息

让15上线,可以发现收到离线消息了

左图为23,为添加群组和群组聊天业务验证

右图为15,为群组聊天和离线消息业务验证

那就一起聊天吧!可以看到15号发送的消息其他人也收到了

4.3.3 创建群组功能

创建成功了!

至此,所有服务器和客户端功能验证完成!

下一节,我们来解决一下高并发情况带来的服务器负载均衡问题,感谢大家!

五、项目流程

 1、项目环境搭建 

C++项目——集群聊天服务器项目(一)项目介绍、环境搭建、Boost库安装、Muduo库安装、Linux与vscode配置_c++集群聊天服务器-CSDN博客

2、Json第三方库介绍

C++项目——集群聊天服务器项目(二)Json第三方库-CSDN博客

3、muduo网络库介绍

C++项目——集群聊天服务器项目(三)muduo网络库-CSDN博客

4、MySQL数据库创建

C++项目——集群聊天服务器项目(四)MySQL数据库-CSDN博客

5、网络模块与业务模块代码编写

C++项目——集群聊天服务器项目(五)网络模块与业务模块-CSDN博客

6、MySQL模块编写

C++项目——集群聊天服务器项目(六)MySQL模块-CSDN博客

7、Model层设计、注册业务实现

C++项目——集群聊天服务器项目(七)Model层设计、注册业务实现-CSDN博客

8、用户登录业务

C++项目——集群聊天服务器项目(八)用户登录业务-CSDN博客

9、客户端异常退出业务

C++项目——集群聊天服务器项目(九)客户端异常退出业务-CSDN博客

10、点对点聊天业务

C++项目——集群聊天服务器项目(十)点对点聊天业务_c++ 公共集群聊天 csdn-CSDN博客

11、服务器异常退出与添加好友业务

C++项目——集群聊天服务器项目(十一)服务器异常退出与添加好友业务-CSDN博客

12、群组业务

C++项目——集群聊天服务器项目(十二)群组业务-CSDN博客

13、客户端登录、注册、退出业务

C++项目——集群聊天服务器项目(十三)客户端登录、注册、退出业务-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/539099.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity中支持泰语--没有版权限制

在Unity中支持泰语主要涉及以下几个方面&#xff1a; 选择合适的字体&#xff1a;在Unity中&#xff0c;确保使用支持泰文字符的字体是至关重要的。例如&#xff0c;可以选择使用Noto Serif Thai字体&#xff0c;这是一个支持泰语的字体2。 处理Unity版本问题&#xff1a;某些…

物联网实战--驱动篇之(八)磁编码器(AS5600)

目录 一、AS5600磁编码简介 二、AS5600使用 一、AS5600磁编码简介 AS5600是一款性价比极高的磁编码传感器&#xff0c;一般用于电机转动位置的记录&#xff0c;一般采用IIC通讯&#xff0c;也可以用模拟信号获取转动角度&#xff0c;具体资料在这里。AS5600-ASOM_&#xff08…

ubuntu 20.04 设置国内镜像源(阿里源、清华源)

在网上搜了好多设置国内镜像源&#xff0c;都写的乱七八糟的&#xff0c;都是随便换&#xff0c;最后还是换得一堆问题。 镜像源也是跟版本一一对应的&#xff0c;不能随便一个国内源就还过去用&#xff0c;否则会出现各种各样的问题&#xff0c;我也是吃过亏之后才发现的。 国…

Harmony鸿蒙南向驱动开发-SPI接口使用

功能简介 SPI指串行外设接口&#xff08;Serial Peripheral Interface&#xff09;&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线。SPI是由Motorola公司开发&#xff0c;用于在主设备和从设备之间进行通信。 SPI接口定义了操作SPI设备的通用方法集合…

MacOS13搭建安卓逆向环境

MacOS中用apktool解包 这里是所有链接&#xff1a;123云盘下载 https://www.123pan.com/s/9QRqVv-JE7Y.html安装apktool https://apktool.org/docs/install/ 或者下载单独的jar包 brew install wgethttps://apktool.org/blog/apktool-2.9.3下载直链&#xff1a;https://co…

【Tomcat 文件读取/文件包含(CVE-2020-1938)漏洞复现】

文章目录 前言 一、漏洞名称 二、漏洞描述 三、受影响端口 四、受影响版本 五、漏洞验证 六、修复建议 前言 近日在做漏扫时发现提示服务器存在CVE-2020-1938漏洞&#xff0c;故文章记录一下相关内容。 一、漏洞名称 Tomcat 文件读取/文件包含漏洞(CVE-2020-1938) 二、漏洞描…

大数据之ClickHouse

大数据之ClickHouse 简介 ClickHouse是一种列式数据库管理系统&#xff0c;专门用于高性能数据分析和数据仓库应用。它是一个开源的数据库系统&#xff0c;最初由俄罗斯搜索引擎公司Yandex开发&#xff0c;用于满足大规模数据分析和报告的需求。 特点 开源的列式存储数据库…

苹果全力升级:用专注AI的M4芯片彻底改造Mac系列

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

FPGA基于VCU的H265视频解压缩,解码后HDMI2.0输出,支持4K60帧,提供工程源码+开发板+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的视频图像编解码方案4K60帧HDMI2.0输入&#xff0c;H265视频压缩方案 3、详细设计方案设计框图FPGA开发板解压视频源Zynq UltraScale VCUVideo Frame Buffer ReadVideo MixerHDMI 1.4/2.0 Transmitter SubsystemVideo PHY Cont…

微服务学习 Eureka注册中心

服务调用时候出现问题&#xff0c;当服务者很多时候&#xff0c;比如不同的端口。消费者如何找到服务者的地址&#xff1f;又如何判断服务者是否健康。 Eureka基本原理&#xff1a; 总结:如果有多个服务提供者&#xff0c;消费者该如何选择&#xff1f; 搭建Eureka注册中心: 1.…

oracle创建整个数据库的只读账户

在源用户readonly 下创建只读用户 reader readonly 的表空间为AA 一、创建只读用户 create user reader identified by 密码 default tablespace AA; 二、授权 grant connect to reader ; 三、获取原账号readonly 的查询权限 select grant select on ||owner||.||object…

Linux的学习之路:7、yum与git

摘要 本章主要是说一下yum和git的操作 目录 摘要 一、什么是yum 二、yum三板斧 1、list 2、install 3、remove 三、怎么创建仓库 四、git三板斧 1、add 2、commit 3、push 4、pull 五、思维导图 一、什么是yum YUM是Yellowdog Updater Modified的简称&#xf…

出海企业如何从海外云手机中受益?

随着全球化的推进&#xff0c;越来越多的企业开始将目光投向海外市场。然而&#xff0c;不同国家和地区的网络环境、政策限制&#xff0c;以及语言文化的差异&#xff0c;给出海企业的市场拓展带来了诸多挑战。在这一背景下&#xff0c;海外云手机作为一种新兴解决方案&#xf…

Jenkins+AWS CodeCommit(git)

问题 需要使用Jenkins搭建一套CI流&#xff0c;即通过git代码托管拉取代码&#xff0c;构建自定分支的代码&#xff0c;构建出jar&#xff0c;并进一步构建出docker镜像&#xff0c;并推送到docker私有库中。 准备 AWS云准备 这里假设已经在CodeCommit已经存在私有git代码仓…

spring boot整合Redis监听数据变化

一、前言 Redis提供了数据变化的通知事件&#xff0c;可以实时监测key和value的变化&#xff0c;客户端可以通过订阅相关的channel来接收这些通知事件&#xff0c;然后做相应的自定义处理&#xff0c;详细的介绍可以参考官方文档Redis keyspace notifications | Docs 使用Red…

Spring Boot | SpringBoot 对 SpringMVC的 “整合支持“

目录: SpringMVC 的 “整合支持” ( 引入"Web依赖启动器"&#xff0c;几乎可以在无任何额外的配置的情况下进行"Web开发")1.SpringMVC "自动配置" 介绍 ( 引入Web依赖启动器"后&#xff0c;SpringBoot会自动进行一些“自动配置”&#xff0…

【Hadoop】下载安装及伪分布式集群搭建教程

目录 1.概述 2.环境准备 3.hadoop安装 3.1.下载安装配置 3.2.伪分布式集群 3.3.注意事项 4.Hadoop集群的组成 1.概述 hadoop有三种安装模式 单机模式&#xff0c;只在一台机器上运行&#xff0c;存储用的本地文件系统而不是HDFS。 伪分布式模式&#xff0c;存储采用HD…

221 基于matlab编制的直齿圆柱齿轮应力计算程序

基于matlab编制的直齿圆柱齿轮应力计算程序&#xff0c;输入设计参数&#xff1a;模数、齿顶高、齿宽、啮合齿数、转速、扭矩、安全系数、压力角、齿轮类型&#xff08;开式、闭式&#xff09;等&#xff0c;输出弯曲应力和许用应力&#xff0c;并对比是否满足要求。并把程序成…

【算法刷题 | 回溯思想 02】4.12(电话号码的字母组合)

文章目录 4.电话号码的字母组合4.1问题4.2解法&#xff1a;回溯4.2.1回溯思路&#xff08;1&#xff09;函数返回值以及参数&#xff08;2&#xff09;终止条件&#xff08;3&#xff09;遍历过程 4.2.2代码实现 4.电话号码的字母组合 4.1问题 给定一个仅包含数字 2-9 的字符…

B站基于Apache Ranger的大数据权限服务的技术演进

01 背景 随着云计算、大数据技术的日趋成熟&#xff0c;复杂多元、规模庞大的数据所蕴含的经济价值和社会价值逐步凸显&#xff0c;数据安全也是企业面临的巨大挑战&#xff0c;B站一直致力于对用户隐私数据的保护。 02 Ranger概述 2.1 用户认证 提到安全&#xff0c;就不得不…