大家好~前段时间有些事情需要处理,没来得及更新,实在不好意思。
今天来继续更新集群聊天服务器项目的客户端功能,主要实现客户端业务,包括添加好友、点对点聊天、创建群组、添加群组、群组聊天业务,接下来我们一起来敲代码吧!
一、业务功能介绍
登录成功后,客户端界面将显示您想进行的功能,我们定义一个哈希表,存储服务器功能和功能对应的,命令格式。
// 客户端命令列表 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、客户端登录、注册、退出业务