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

news2024/12/24 9:16:39

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

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

一、业务功能介绍

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

// 客户端命令列表
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博客

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Promise简单概述

一. Promise是什么&#xff1f; 理解 1.抽象表达&#xff1a; Promise是一门新的技术(ES6规范) Promise是JS中进行异步编程的新解决方案(旧方案是单纯使用回调函数) 异步编程&#xff1a;包括fs文件操作&#xff0c;数据库操作(Mysql)&#xff0c;AJAX&#xff0c;定时器 2.具…

【opencv】示例-imagelist_creator.cpp 从命令行参数中创建一个图像文件列表(yaml格式)...

/* 这个程序可以创建一个命令行参数列表的yaml或xml文件列表 */ // 包含必要的OpenCV头文件 #include "opencv2/core.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include <string> #include <iostream>…

24、链表-回文链表

思路&#xff1a; 回文链表就是两个指针各从首 尾 开始遍历&#xff0c;实时相等&#xff0c;那么就是回文链表&#xff0c;或者关于中线对称。 第一种方式 集合方式实现很简单不再赘述&#xff0c;代码如下 //直接使用一个栈来校验&#xff0c;回文正过来 逆过来 都一样&am…

Go——Goroutine介绍

一. 并发介绍 进程和线程 进程是程序在操作系统中一次执行过程&#xff0c;系统进程资源分配和调度的一个独立单位。线程是进程执行的实体&#xff0c;是CPU调度和分派的基本单位&#xff0c;它是比进程更小的能独立运行的基本单位。一个进程可以创建和撤销多个线程&#xff0c…

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

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

【二分算法】

17. 二分查找&#xff08;easy&#xff09; 算法流程&#xff1a; 算法代码&#xff1a; int search(int* nums, int numsSize, int target) {// 初始化 left 与 right 指针int left 0, right numsSize - 1;// 由于两个指针相交时&#xff0c;当前元素还未判断&#xff0c;因…

在Ubuntu服务器上快速安装一个redis并提供远程服务

一、快速安装一个Redis 第一步&#xff1a;更新apt源 sudo apt update第二步&#xff1a;下载Redis sudo apt install redis第三步&#xff1a;查看Redis是否已自启动 systemctl status redis二、配置Redis提供远程服务 第一步&#xff1a;先确保6379端口正常开放 如果是…

客户端传日期格式字段(String),服务端接口使用java.util.Date类型接收报错问题

客户端传日期格式字段&#xff08;string&#xff09;,服务端接口使用java.util.Date类型接收报错问题 问题演示第1种&#xff1a;客户端以URL拼接的方式传值第2种&#xff1a;客户端以body中的form-data方式提交第3种 客户端以Body中的json方式提交 问题解决&#xff08;全局解…

【SpringBoot XSS存储漏洞 拦截器】Java纯后端对于前台输入值的拦截校验实现 一个类加一个注解结束

先看效果&#xff1a; 1.js注入拦截&#xff1a; 2.sql注入拦截 生效只需要两步&#xff1a; 1.创建Filter类&#xff0c;粘贴如下代码&#xff1a; package cn.你的包命.filter; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IO…

Qt5 编译oracle数据库

库文件 1、Qt源码目录&#xff1a;D:\Qt5\5.15.2\Src\qtbase\src\plugins\sqldrivers\oci 2、oracle客户端SDK: https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html 下载各版本中的如下压缩包&#xff0c;一定要版本相同的 将两个压缩包…

jenkins+gitlab配置

汉化 1、安装Localization: Chinese (Simplified)插件 &#xff08;此处我已安装&#xff09; &#xff08;安装完成后重启jenkins服务即可实现汉化&#xff09; 新增用户权限配置 1、安装插件 Role-based Authorization Strategy 2、全局安全配置 3、配置角色权限 4、新建…

运用单例模式思想解决RuntimeException超时问题

今天&#xff0c;排查了一个RuntimeException超时问题&#xff0c;简单记录分享下。 分析关键日志排查如下 查看关键代码 private static Client createClient(String wsdlUrl) {JaxWsDynamicClientFactory jaxWsDynamicClientFactory JaxWsDynamicClientFactory.newInstance…

ElasticView一款ElasticSearch的web可视化工具

ElasticView 是一款用来监控ElasticSearch状态和操作ElasticSearch索引的web可视化工具。它由golang开发而成&#xff0c;具有部署方便&#xff0c;占用内存小等优点 ElasticSearch连接树管理&#xff08;更方便的切换测试/生产环境&#xff09;支持权限管理支持sql转换成dsl语…

Go语言图像处理实战:深入image/color库的应用技巧

Go语言图像处理实战&#xff1a;深入image/color库的应用技巧 引言image/color库基础颜色模型简介颜色类型和接口 image/color库实际应用基本颜色操作创建颜色颜色值转换颜色比较 颜色转换与处理与image库结合使用 性能优化和高级技巧性能考量避免频繁的颜色类型转换使用并发处…

在vue3中实现pptx、word、excel预览

插件推荐 PPTXjs vue-office 代码 <script setup lang"ts" name"home"> import { computed, nextTick, ref, onMounted } from vue; //引入VueOfficeDocx组件 import VueOfficeDocx from vue-office/docx; //引入VueOfficeExcel组件 import VueOf…

探索Web3的奇迹:数字时代的新前景

在数字化时代的潮流中&#xff0c;我们不可避免地迎来了一个全新的篇章——Web3时代的到来。在这个时代中&#xff0c;区块链技术作为数字化世界的核心&#xff0c;正在重塑着我们的生活方式、经济模式以及社会结构。在Web3时代&#xff0c;我们将目睹着一个以去中心化、透明化…

Kubernetes(k8s):深入理解k8s中的亲和性(Affinity)及其在集群调度中的应用

Kubernetes&#xff08;k8s&#xff09;&#xff1a;深入理解k8s中的亲和性&#xff08;Affinity&#xff09;及其在集群调度中的应用 1、什么是亲和性&#xff1f;2、节点亲和性&#xff08;Node Affinity&#xff09;2.1 硬性节点亲和性规则&#xff08;required&#xff09;…

TCP/IP协议—UDP

TCP/IP协议—UDP UDP协议UDP通信特点 UDP头部报文UDP检验 UDP协议 用户数据传输协议 (UDP&#xff0c;User Datagram Protocol) 是一种无连接的协议&#xff0c;提供了简单的数据传输服务&#xff0c;不保证数据的顺序以及完整性。应用层很多通信协议都基于UDP进行传输&#x…

「51媒体网」汽车类媒体有哪些?车展媒体宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 汽车类媒体有很多&#xff0c;具体如下&#xff1a; 汽车之家&#xff1a;提供全面的汽车新闻、评测、导购等内容。 爱卡汽车&#xff1a;同样是一个综合性的汽车信息平台&#xff0c;涵…

iPad 无法解锁?修复 iPad 滑动解锁不起作用的 9 个解决方案

“我的 iPad Pro 一整天都工作正常&#xff0c;直到 20 分钟前。当我解锁它时&#xff0c;它不让我向上滑动。屏幕有响应&#xff0c;但我的 iPad 无法解锁。是否有其他人遇到过这种情况并找到了解决方法&#xff1f;解决方案&#xff1f;” ——来自 Apple 支持社区 iPad 屏幕…