[集群聊天服务器]----(九)客户端开发

接着上文的[集群聊天服务器]----(八)群组类、群组操作接口以及业务模块之创建群组,加入群组以及群组聊天开发,项目真正操作,还需要客户端进行相关操作的,接下来我们剖析客户端的实现。

main函数

客户端主要对用户输入的消息进行序列化,一定要与之前服务端设置的键值相对应

int main(int argc, char **argv)
{if (argc < 3){cerr << "command invalid! example: ./ChatClient 127.0.0.1 6000" << endl;exit(-1);}// 解析通过命令行参数传递的ip和portchar *ip = argv[1];uint16_t port = atoi(argv[2]);// 创建client端的socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == clientfd){cerr << "socket create error" << endl;exit(-1);}// 填写client需要连接的server信息ip+port 绑定ip和端口号sockaddr_in server;memset(&server, 0, sizeof(sockaddr_in));server.sin_family = AF_INET;server.sin_port = htons(port);server.sin_addr.s_addr = inet_addr(ip);// client和server进行连接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();// main线程用于接收用户输入,负责发送数据for (;;){// 显示首页面菜单 登录、注册、退出cout << "========================" << endl;cout << "1. login" << endl;cout << "2. register" << endl;cout << "3. quit" << endl;cout << "========================" << endl;cout << "choice:";int choice = 0;cin >> choice;cin.get(); // 读掉缓冲区残留的回车 如果不回收到回车,后面在输入就会读取回车 不读取输入的switch (choice){case 1: // login业务{int id = 0;char pwd[50] = {0};cout << "userid:";cin >> id;cin.get(); // 读掉缓冲区残留的回车cout << "userpassword:";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);if (len == -1){cerr << "send login msg error:" << request << endl;}sem_wait(&rwsem); // 等待信号量,由子线程处理完登录的响应消息后,通知这if (g_isLoginSuccess){isMainMenuRunning = true;// 进入聊天主菜单页面mainMenu(clientfd);}}break;case 2: // register业务{char name[50] = {0};char pwd[50] = {0};cout << "username:";cin.getline(name, 50); // 遇见回车结束cout << "userpassword:";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);if (len == -1){cerr << "send reg msg error:" << request << endl;}sem_wait(&rwsem); // 等待信号量,由子线程处理完注册的响应消息后,通知这}break;case 3: // quit业务close(clientfd);sem_destroy(&rwsem);exit(0);default:cerr << "invalid input!" << endl;break;}}return 0;
}
  • 解析通过命令行参数获取传递的ip和port
  • 调用socket函数,创建client端的socket
  • 填写client需要连接的server信息ip+port 绑定ip和端口号
  • 调用connect()函数将client和server进行连接
  • 初始化读写线程通信用的信号量
  • 连接服务器成功,启动接收子线程,main线程用于接收用户输入,负责发送数据,子线程用作接收线程
  • 显示首页面菜单 登录、注册、退出操作,注意读掉缓冲区残留的回车 如果不回收到回车,后面在输入就会读取回车 不读取输入的
  • 根据用户的选择登录LOGIN_MSG、注册REG_MSG、退出进行相关操作

登录响应

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);int msgtype = js["msgid"].get<int>();if (ONE_CHAT_MSG == msgtype){cout << js["time"].get<string>() << "[" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;}else if (GROUP_CHAT_MSG == msgtype){cout << "群消息[" << js["groupid"] << "]: " << js["time"].get<string>() << "["<< js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;}}}g_isLoginSuccess = true;}
}
  • 登录成功以后记录当前用户的id和name
  • 记录当前用户的好友列表信息
  • 记录当前用户的群组列表信息
  • 显示登录用户的基本信息
  • 显示当前用户的离线消息 个人聊天或者群组消息

注册相应

void doRegResponse(json &responsejs)
{if (0 != responsejs["errno"].get<int>()){cerr << "name is already exist, register error!" << endl;}else{cerr << "name  register success, userid is" << responsejs["id"]<< ", do not forget it!" << endl;}
}

子线程做接收线程

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);}// 接受chatserver转发的数据,反序列化生成json数据对象json js = json::parse(buffer);int msgtype = js["msgid"].get<int>();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;}}
}
  • 根据msgid获取对应的消息类别,并作出相关的回应
  • ** 通知主线程 登录以及注册结果**

显示当前登录成功用户的基本信息

void showCurrentUserData()
{cout << "======================login user======================" << endl;cout << "current login user => id:" << g_currentUser.getId() << " name:" << g_currentUser.getName() << endl;cout << "----------------------friend list---------------------" << endl;if (!g_currentUserFriendList.empty()){for (User &user : g_currentUserFriendList){cout << user.getId() << " " << user.getName() << " " << user.getState() << endl;}}cout << "----------------------group list----------------------" << endl;if (!g_currentUserGroupList.empty()){for (Group &group : g_currentUserGroupList){cout << group.getId() << " " << group.getName() << " " << group.getDesc() << endl;for (GroupUser &user : group.getUsers()){cout << user.getId() << " " << user.getName() << " " << user.getState()<< " " << user.getRole() << endl;}}}cout << "======================================================" << 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);
}

系统支持的客户端命令列表commandMap

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

注册系统支持的客户端命令处理

unordered_map<string, function<void(int, string)>> commandHandlerMap = {{"help", help},{"chat", chat},{"addfriend", addfriend},{"creategroup", creategroup},{"addgroup", addgroup},{"groupchat", groupchat},{"loginout", loginout}};

help,chat,addfriend,creategroup,addgroup,groupchat,loginout都对应着相应的处理函数

主聊天页面程序

void mainMenu(int clientfd)
{help();char buffer[1024] = {0};while (isMainMenuRunning){cin.getline(buffer, 1024);string commandbuf(buffer);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 << "invaild input command!" << endl;continue;}// 调用相应命令的事件处理回调 mainMenu对修改封闭 添加新功能不需要修改该函数it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - idx));}
}
  • 通过find以及substr函数对命令command进行了分离,命令存在的话,调用相应命令的事件处理回调

help命令

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

显示系统支持的客户端命令列表

addfriend命令

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

根据客户输入的命令,调用send()函数,发向服务端

chat命令

void chat(int clientfd, string str)
{int idx = str.find(":"); // friendid:messageif (-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;}
}
  • 获取好友id,以及要发送的消息

creategroup命令

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命令

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命令

void groupchat(int clientfd, string str)
{int idx = str.find(":"); // friendid:messageif (-1 == idx){cerr << "groupchat command invalid!" << endl;return;}int groupid = atoi(str.substr(0, idx).c_str());         // atoi处理的char*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命令

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

好了~ 客户端主要是实现json数据的输入,切记要和服务端的json值一一对应,否则会失败的。

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

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

相关文章

5.21数据库mySQL

服务器存储信息的能力是有限的&#xff0c;需要将信息存储在磁盘上。 存在主要是两个问题&#xff0c;就是将数据从磁盘中读出数据来&#xff0c;将数据从服务器中存储到磁盘上。 那么接下来的问题就是如何对于数据进行存储方便于进行读取&#xff0c;数据库就是起这样的作用…

C++_C++11的学习

1. 统一的列表初始化 1.1&#xff5b;&#xff5d;初始化 在C98 中&#xff0c;标准就已经允许使用花括号 {} 对数组或者结构体元素进行统一的列表初始值设定。而到了C11&#xff0c;标准扩大了用大括号括起的列表 ( 初始化列表 )的使用范围&#xff0c;使其能适用于所有的内…

Python 获取当前IP地址(爬虫代理)

Python 获取当前IP地址&#xff08;爬虫代理&#xff09; 在Python中&#xff0c;获取当前的公网IP地址通常涉及到发送一个请求到外部服务&#xff0c;因为本地IP地址通常只在你的私有网络内部是可见的&#xff0c;而公网IP地址是由你的ISP&#xff08;互联网服务提供商&#x…

J2SE+swing客户端开发进阶总结

Hello &#xff0c; 我是恒。分享一个练手项目一本糊涂账&#xff0c;顺便帮站长宣传一下站点https://how2j.cn 本项目是基于Swing和JDBC开发的图形界面桌面应用&#xff0c;通过这个项目能运用锻炼J2SE知识和技能 结构 ├───src/ │ ├───HutuMainFrame.java │ ├…

Java进阶学习笔记28——StringJoiner

Java中&#xff0c;有没有即能高效&#xff0c;又能实现更方便的拼接呢&#xff1f; StringJoiner&#xff1a; JDK8才开始的&#xff0c;跟StringBuilder一样&#xff0c;也是用来操作字符串的&#xff0c;也可以看成是一个容器&#xff0c;创建之后里面的内容是可变的。 好…

C++发票四要素真伪查验、数电票查验

在数字化转型的浪潮下&#xff0c;财务管理作为企业运营的核心环节之一&#xff0c;正经历着前所未有的变革。近期&#xff0c;随着发票查验接口技术的不断成熟与创新&#xff0c;翔云发票查验接口平台为企业提供了便捷、高效的发票查验服务&#xff0c;极大地提升了企业的财税…

SpringBoot学习小结之RocketMQ

文章目录 前言一、架构设计1.1 架构图1.2 消息1.3 工作流程 二、部署2.1 单机2.2 集群 三、Springboot Producter3.1 准备3.2 pom依赖、yml 配置3.3 普通消息3.4 顺序、批量、延迟消息3.5 事务消息 四、Springboot Consumer4.1 配置4.2 普通Push消费4.3 回复4.4 集群和广播4.5 …

CompletableFuture的使用(详细)

引入 功能和灵活性&#xff1a; Future&#xff1a;是Java 5引入的接口&#xff0c;用于表示一个异步操作的未来结果。它提供了基本的异步操作支持&#xff0c;如检查是否完成、等待结果以及获取结果&#xff0c;但在处理结果、异常和组合等方面功能有限。 CompletableFuture&a…

Linux文件操作命令

马哥教育 Linux SRE 学习笔记 文件操作命令 显示当前工作目录 每个shell和系统进程都有一个当前的工作目录 CWD&#xff1a;current work directory 显示当前shell CWD的绝对路径 pwd命令: printing working directory -P 显示真实物理路径-L 显示链接路径&#xff08;默认&…

Light_Future的C++框架的套利代码读取arbitrage_strategy.cpp

1. 套利策略的初始化函数,订阅两个数据 void arbitrage_strategy::on_init(subscriber& suber) {// 向订阅者注册两个交易代码的tick接收器。suber.regist_tick_receiver(_code1, this);suber.regist_tick_receiver(_code2, this);// 获取当前交易日。uint32_t trading_d…

SpringCloud配置文件bootrap

解决方案&#xff1a; 情况一、SpringBoot 版本 小于 2.4.0 版本&#xff0c;添加以下依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-context</artifactId> </dependency> 情况二、SpringBoot…

[集群聊天服务器]----(八)群组类、群组操作接口以及业务模块之创建群组,加入群组以及群组聊天

接着上文关于[集群聊天服务器]----(七)业务模块之一对一聊天、添加好友函数、好友类以及离线消息类的剖析。本章将对创建群组&#xff0c;加入群组以及群组聊天业务进行剖析。 群类 类似于User类&#xff0c;构建了Group类 #ifndef GROUP_H #define GROUP_H#include "g…

在Windows中安装Redis

一、下载Redis github链接&#xff1a;https://github.com/redis-windows/redis-windows/releases 二、安装 解压后点击start.bat文件即可启动服务 新开一个cmd窗口进入安装了Redis的文件夹输入redis-cli.exe -h 127.0.0.1 -p 6379连接Redis&#xff0c;见如下结果便是成功&…

sql-labs靶场环境搭建(手把手保姆级教学)

文章目录 一、sql-labs靶场简介&#xff1a;二、搭建过程1、资源下载2、配置文件&#xff1b;3、访问网站4、创建数据库 三、使用PhpStudy2018原因 一、sql-labs靶场简介&#xff1a; SQL-Labs 是一个实践环境&#xff0c;旨在用于数据库和 SQL&#xff08;结构化查询语言&…

某大型制造集团企业信息化建设总体规划设计方案(67页PPT)

方案介绍&#xff1a; 随着信息技术的飞速发展&#xff0c;企业信息化建设已成为提高管理效率、增强企业竞争力的重要手段。某大型制造集团为应对市场变化、提升管理水平、优化资源配置&#xff0c;决定进行全面深入的信息化建设。本方案旨在构建一个集生产、管理、销售、物流…

Java中的IO和NIO(New IO)有什么区别?

在Java编程中&#xff0c;IO&#xff08;Input/Output&#xff09;和NIO&#xff08;New IO&#xff09;是两个重要的概念&#xff0c;它们分别代表了Java中的传统IO和新的IO库。为了全面解释这两者之间的区别&#xff0c;我们将从技术难点、面试官关注点、回答吸引力和代码举例…

【DevOps】Jenkins + Dockerfile自动部署Maven(SpringBoot)项目

环境 docker_host192.168.0.1jenkins_host192.168.0.2 jenkins_host构建完成后把jar发布到docker_host&#xff0c;再通过dockerfile自动构建镜像&#xff0c;运行镜像 1 Jenkins安装 AWS EC2安装Jenkins&#xff1a;AWS EC2 JDK11 Jenkins-CSDN博客 AWS EC2上Docker安装…

T113调试7寸RGB屏

文章目录 软硬件介绍软件板卡屏幕 调试修改内核设备树修改U-Boot设备树 测试添加启动logo其它问题总结 软硬件介绍 软件 基于Tina5.0 SDK。 板卡 韦东山的T113工业板&#xff1a; 屏幕 韦东山的7寸RGB电容触摸屏&#xff1a; 调试 修改内核设备树 打开内核设备树<…

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点 、 面试题 02.07. 链表相交、142.环形链表II

24. 两两交换链表中的节点 题目链接&#xff1a; 24. 两两交换链表中的节点 文档讲解&#xff1a;代码随想录 状态&#xff1a;没做出来&#xff0c;没有正确更新头节点&#xff0c;因为head和cur共享引用&#xff0c;会随着cur的移动&#xff0c;丢失之前存放的节点 错误代码&…

efuse xinpian

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 xx项目需要进行efuse烧录&#xff0c;之前都是单板环境&#xff0c;现补充裸板烧录教…