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和portchar *ip = argv[1];uint16_t port = atoi(argv[2]);// 创建client端的socketint 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+portsockaddr_in server;memset(&server, 0, sizeof(sockaddr_in)); // memset清0server.sin_family = AF_INET;            // IPv4server.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_createreadTask.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和nameg_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>(); // 获取处理业务msgidif (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.rhkb.cn/news/310281.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于velero和minio实现k8s数据的备份

1.30部署minio rootk8s-harbor:/etc/kubeasz/clusters/k8s-cluster1# docker run \ -d --restartalways -p 9000:9000 -p 9090:9090 –name minio -v /data/minio/data:/data -e “MINIO_ROOT_USERadmin” -e “MINIO_ROOT_PASSWORD12345678” quay.io/minio/minio server…

什么是工业级物联网智能网关?如何远程控制PLC?

在数字化浪潮席卷全球的今天&#xff0c;工业物联网&#xff08;IIoT&#xff09;已经成为推动工业转型升级的关键力量。而在工业物联网的大家庭中&#xff0c;工业级物联网智能网关扮演着举足轻重的角色。那么&#xff0c;究竟什么是工业级物联网智能网关&#xff1f;又该如何…

五、流程控制之循环

目录 5.1 步进循环语句for 5.1.1 带列表的for循环语句 5.1.2 不带列表的for循环语句 5.1.3 类C风格的for循环语句 5.2 while循环语句 5.2.1 while循环读取文件 5.2.2 while循环语句示例 5.3 until循环语句 5.4 select循环语句 5.5 嵌套循环 5.1 步进循环语句for for…

linux安装和使用-第一天

一. 安装linux系统 安装过程:略注意事项: 安装时一定一定一定不要选择有中文的目录包括镜像文件所在的目录,否则会发生各种问题,比如VMware Tools是灰色的.1. 安装ssh工具 (1) 安装命令 # 第一次安装系统需要更新一下apt的源,他维护了软件依赖关系,否则安装不了软件,每次安装…

Python数据容器(一)

一.数据容器入门 1.Python中的数据容器&#xff1a;一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为1个元素&#xff0c;每一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等。 2.数据容器根据特点的不同&#xff0c;如&#…

CountDownLatch

CountDownLatch 翻译&#xff1a; 倒计时锁存器&#xff0c;&#xff0c;&#xff0c;&#xff0c;count计数&#xff0c;down停止&#xff0c;Latch锁 解释&#xff1a; 允许一个或多个线程等待&#xff0c;直到在其他线程中执行的一组操作完成的同步辅助不懂&#xff1f;…

github上的软件许可证是什么?如何合并本地的分支德语难学还是俄语更加难学?站在一个中国人的立场上,德语难学还是俄语更加难学?俄语跟德语有什么样的显著差别?

目录 github上的软件许可证是什么&#xff1f; 如何合并本地的分支 德语难学还是俄语更加难学&#xff1f; 站在一个中国人的立场上&#xff0c;德语难学还是俄语更加难学&#xff1f; 俄语跟德语有什么样的显著差别&#xff1f; github上的软件许可证是什么&#xff1f; …

46.HarmonyOS鸿蒙系统 App(ArkUI)网格布局

Grid(){GridItem(){Button(按钮1).fontSize(28)}.backgroundColor(Color.Blue)GridItem(){Text(数学).fontSize(28)}.backgroundColor(Color.Yellow)GridItem(){Text(语文).fontSize(28)}.backgroundColor(Color.Green)GridItem(){Text(英语).fontSize(28)}.backgroundColor(Co…

分布式锁-redission可重入锁原理

5.3 分布式锁-redission可重入锁原理 在Lock锁中&#xff0c;他是借助于底层的一个voaltile的一个state变量来记录重入的状态的&#xff0c;比如当前没有人持有这把锁&#xff0c;那么state0&#xff0c;假如有人持有这把锁&#xff0c;那么state1&#xff0c;如果持有这把锁的…

RabbbitMQ基本使用及其五种工作模型

初识MQ 同步通讯和异步通讯 什么是同步通讯呢&#xff1f;举个例子&#xff0c;你认识了一个小姐姐&#xff0c;聊的很火热&#xff0c;于是你们慢慢开始打电话&#xff0c;视频聊天&#xff0c;这种方式就成为同步通讯&#xff0c;那什么是一部通讯呢&#xff0c;同样的&…

uni-app如何生成骨架屏

为什么需要骨架屏&#xff1a;为了缓解用户打开程序时等待接口的焦虑情绪 1.打开微信开发者工具&#xff0c;找到模拟器中的页面信息&#xff0c;选择生成骨架屏 2.将生成的wxml代码复制到vscode&#xff0c;在index的components中新建一个vue文件&#xff0c;只需保留请求接口…

Day16_学点儿JavaEE_实践_基于IDEA2023的简易JavaWeb项目、Tomcat输出乱码解决

0 JavaWeb项目目录 └──JavaWeb├──resources│ └──db.properties├──src│ └──com.sdust.web│ ├──servlet│ │ └──StudentServlet│ ├──pojo│ │ └──Student│ └──util│ └──JDBCUtil├──web│ ├──st…

趣话最大割问题:花果山之群猴博弈

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 编辑丨浪味仙 排版丨 沛贤 深度好文&#xff1a;3000字丨15分钟阅读 趋利避害&#xff0c;是所有生物遵循的自然法则&#xff0c;人类也不例外。 举个例子&#xff0c;假如你是某生鲜平台的配…

吴恩达llama课程笔记:第六课code llama编程

羊驼Llama是当前最流行的开源大模型&#xff0c;其卓越的性能和广泛的应用领域使其成为业界瞩目的焦点。作为一款由Meta AI发布的开放且高效的大型基础语言模型&#xff0c;Llama拥有7B、13B和70B&#xff08;700亿&#xff09;三种版本&#xff0c;满足不同场景和需求。 吴恩…

itext7 pdf转图片

https://github.com/thombrink/itext7.pdfimage 新建asp.net core8项目&#xff0c;安装itext7和system.drawing.common 引入itext.pdfimage核心代码 imageListener下有一段不安全的代码 unsafe{for (int y 0; y < image.Height; y){byte* ptrMask (byte*)bitsMask.Scan…

2022年蓝桥杯省赛软件类C/C++B组----积木画

想借着这一个题回顾一下动态规划问题的基本解法&#xff0c;让解题方法清晰有条理&#xff0c;希望更多的人可以更轻松的理解动态规划&#xff01; 目录 【题目】 【本题解题思路】 【DP模版】 总体方针&#xff1a; 具体解题时的套路&#xff1a; 【题目】 【本题解题思…

状态模式(行为型)

目录 一、前言 二、状态模式 三、总结 一、前言 状态模式(State Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类&#xff0c;但实际上&#xff0c;由于状态模式的引入&#xff0c;行为的变…

macOS制作C/C++ app

C/C制作macOS .app 一、 .app APP其实是一个文件夹结构&#xff0c;只不过mac的界面中让它看起来像一个单独的文件。 在shell终端或者右键查看包结构即可看到APP的目录结构。 通常的app目录结构如下&#xff1a; _CodeSignature, CodeResources 一般为Mac APP Store上架程序…

华三交换机知道ip怎么查找主机ip在接入交换机哪个端口下

环境&#xff1a; 华三S5120V3-52S-SI H3C Comware Software, Version 7.1.070, Release 6329 问题描述&#xff1a; 华三交换机知道ip怎么查找主机ip在接入交换机哪个端口下 已知主机ip192.168.1.111 解决方案&#xff1a; 在H3C&#xff08;新华三&#xff09;交换机上…

K8S:常用资源对象操作

文章目录 一、使用Replication Controller(RC)、Replica Set(RS) 管理Pod1 Replication Controller&#xff08;RC&#xff09;2 Replication Set&#xff08;RS&#xff09; 二、Deployment的使用1 创建2 滚动升级3 回滚Deployment三、 Pod 自动扩缩容HPA1 使用kubectl autosc…