demo版多人聊天系统

目录

​编辑

一,引入

二,在Server端修改的代码

 1,保存用户信息功能实现

2,拼接消息

 3,广播消息

三, Client端要修改的代码

 四,效果演示


一,引入

在上一篇文章udp网络服务器中,我实现了一个简易版的网络服务器。而在今天的这篇文章中,我要实现的便是基于这个udp网络服务器而实现的多人聊天室。这个聊天室的新增功能如下:

1,添加用户。

2,广播消息。

3,使用不同的线程来接收消息和和发送消息。

二,在Server端修改的代码

 1,保存用户信息功能实现

这个功能的实现其实就像我们日常生活中的通讯录的作用一样。我们可以用线性表等方式实现。但是为了提高查找效率,我采用的方式是使用哈希表的方式实现。

  std::unordered_map<int,sockaddr_in> online_user_;//建立一个用户上线表,用哈希表的方式存储

哈希表参数类型解释:

int:因为要存放的是接收方的ip地址。

sockaddr_in:套接字类型,可以通过这个参数来获取接收方的端口号。

有了表以后,就要来想想一个常识性问题了。我们的表里面需要有重复信息吗?答案当然是不需要。所以在将数据插入到表里时我们要检查这个表里面的数据是否和当前要插入的数据重复,暂时通过ip的方式识别。

bool Check_user(int clientId)//检查是否为新用户{if(online_user_.find(clientId) == online_user_.end()){return true;}return false;}

 为什么是在Run函数里面修改?

因为在这个函数内部实现了接收消息的功能所以发送方的端口号和ip等消息便可以在这个函数内获得。

2,拼接消息

在得到发送方的端口号和ip以后,为了标识显示发送方。那我们便要将发送方的ip和port以及发送的消息拼接在一起

//拼接消息
inbuf[r1] = 0;
std::string ip = inet_ntoa(si.sin_addr);
std::string port = std::to_string(si.sin_port);
std::string message = inbuf;
std::string tostring = "["+ip+":"+port+"]" + message;

 3,广播消息

在做完用户表的添加和消息的拼接以后,我们便知道了消息是什么,消息要发给谁。所以我们便可以开始广播消息,让所有人看到消息了。

void Broad_cast(std::string& message)//广播函数{for(auto e:online_user_)//广播{socklen_t len = sizeof(e.second);int r = sendto(socketfd_, message.c_str(), message.size(), 0, (sockaddr*)&e.second, len);if(r<0){std::cout << "broad cast error!" << std::endl;continue;}}}

采用循环的方式将消息发送给用户表里的所有人。

所以我们在run函数里面要修改的全部代码便是:

 void Run(const func&fun)//加入远程操作{char inbuf[inbufSize] = {0};sockaddr_in si;socklen_t len = sizeof(si);//一定要初始化while (true){int r1 = recvfrom(socketfd_, inbuf, sizeof inbuf-1, 0, (sockaddr *)&si, &len);//收消息if(r1<0)//读取消息失败{perror("recvfrom error");exit(10);}if (Check_user(si.sin_addr.s_addr)) // 检查是否是新用户{std::cout << "welcome......." << std::endl;online_user_.insert({si.sin_addr.s_addr, si});//加入到用户表里}//将消息发送给用户//拼接消息inbuf[r1] = 0;std::string ip = inet_ntoa(si.sin_addr);std::string port = std::to_string(si.sin_port);std::string message = inbuf;std::string tostring = "["+ip+":"+port+"]" + message;//将消息广播Broad_cast(tostring);//std::cout << tostring << std::endl;// std::cout <<"收到消息,正在处理"<<std::endl;// std::string command = inbuf;// std::string cip = inet_ntoa(si.sin_addr);// int cport = si.sin_port;// std::string message = fun(command,cip,cport );// //  std::cout << message << std::endl;// int r2 =  sendto(socketfd_, message.c_str(),  message.size(), 0, (sockaddr *)&si, sizeof si);//将处理结果返回给发送方// std::cout << std::endl<<"处理完成";// if (r2 < 0)// {//     perror("server send message error");//     continue;// }}}

 对于server端的代码我们只需要修改Run函数里面的代码即可。

三, Client端要修改的代码

在平时的生活中,我们很容易的便可以知道。收发消息是可以同时的运行的。所以,在实现Client端的代码时,我们最好创建两个线程实现收发消息的并发执行。

void Run(){// 客户端接收函数,分两个线程执行// 定义线程变量pthread_t Send_thread;pthread_t Receve_thread;pthread_create(&Send_thread, nullptr, Send, this);pthread_create(&Receve_thread, nullptr, Receve, this);pthread_join(Send_thread, nullptr);pthread_join(Receve_thread, nullptr);// char outbuf[outbufSize];// while (true)// {//     std::cout << "请输入内容>> ";//     std::getline(std::cin, requestes); // client输入内容//     if (sendto(socketfd_, requestes.c_str(), sizeof requestes, 0, (sockaddr *)&si, sizeof si) < 0) // 发送消息//     {//         continue;//     }//     int r3 = recvfrom(socketfd_, outbuf, sizeof outbuf, 0, (sockaddr *)&si, &len);//     if(r3<0)//     {//         continue;//     }//     outbuf[r3] = 0;//     std::cout << outbuf << std::endl;//     memset(outbuf, 0, sizeof outbuf);// }}

在这段代码中,我将Client端的Run函数修改如上。Run函数的作用便只是创建两个线程,然后再执行对应的方法。这两个方法便是接收消息和发消息。

接收消息:

 static void *Receve(void *args) // 收消息的线程{Client *C = static_cast<Client *>(args);char outbuf[outbufSize];sockaddr_in si;socklen_t len = sizeof(si);C->Dup(); // 重定向到别的窗口while (true){int r3 = recvfrom(C->socketfd_, outbuf, sizeof outbuf-1, 0, (sockaddr *)&si, &len);if (r3 < 0){continue;}outbuf[r3] = 0;std::cerr << outbuf << std::endl;memset(outbuf, 0, sizeof outbuf);}}

 发消息:

static void *Send(void *args) // 发消息的线程{Client *C = static_cast<Client *>(args);std::string requestes;sockaddr_in si;socklen_t len;bzero(&si, sizeof si);si.sin_family = AF_INET;si.sin_port = htons(C->port_);si.sin_addr.s_addr = inet_addr(C->ip_.c_str());while (true){std::cout << "请输入内容>> "; // client输入内容std::string requestes;std::getline(std::cin, requestes);if (sendto(C->socketfd_, requestes.c_str(), requestes.size(), 0, (sockaddr *)&si, sizeof si) < 0) // 发送消息{continue;}}}

解释:

1,为什么要使用static函数来修饰这两个方法?

因为pthread_create函数里面的方法类型是void*(void*),但是类里面的成员方法的参数里面有一个隐藏的this指针。所以只能使用static修饰让成员方法变成静态成员方法进而去掉前面隐藏的this指针。

2,pthread_create函数中为什么要传入this指针?

因为在这两个函数的内部要使用类的私有成员。但是没有this指针不能直接调用。所以便传入this指针来进行调用私有成员。

3,为什么*Receve方法中的Dup()函数是什么?

其实这是一个重定向的函数。主要是为了实现输入和输出的分离。让输入和输出打印在不同的终端。

代码实现如下:
 

void Dup()
{int fd = open("/dev/pts/17", O_WRONLY|O_CREAT);//这是一个终端文件路径//可以使用w命令查看自己打开的终端号if(fd<0)                                       //终端号是数字,前面部分的路径是一样的{perror("open error");exit(30);}dup2(fd, 2);//重定向,重定向的是2号标准错误。因为我的消息是用cerr输出的。
}

补充:

如果不想实现重定向的功能,也可以在启动可执行程序时直接重定向到不同的终端

首先要使用w来查看自己的终端号是什么:

然后使用重定向操作:

./Server 8080 >/dev/pts/17    ./Client 111.230.60.61  8080 >/dev/pts/18

 四,效果演示

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

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

相关文章

MySQL索引优化

示例 CREATE TABLE employees (id int(11) NOT NULL AUTO_INCREMENT,name varchar(24) NOT NULL DEFAULT COMMENT 姓名,age int(11) NOT NULL DEFAULT 0 COMMENT 年龄,position varchar(20) NOT NULL DEFAULT COMMENT 职位,hire_time timestamp NOT NULL DEFAULT CURRENT_TI…

PyTorch深度学习:如何提升遥感影像的地物分类精度?

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

【算法杂货铺】分治

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 快速排序 &#x1f4c2;75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09; &#x1f4c2; 912. 排序数组 - 力扣&#xff08;LeetCode&#xff09; &#x1f4c2; 215. 数组中的第K个最大元素 - 力扣&#xff08;Lee…

突然发现!原来微信批量自动加好友这么简单!

你知道如何更好地管理和利用微信资源&#xff0c;实现客户拓展和沟通吗&#xff1f;下面就教大家一招&#xff0c;帮助大家实现统一管理多个微信号以及批量自动加好友。 想要统一管理多个微信号&#xff0c;不妨试试微信管理系统&#xff0c;不仅可以多个微信号同时登录&#…

数据分析概述、Conda环境搭建及JupyterLab的搭建

1. 数据分析职责概述 当今世界对信息技术的依赖程度在不断加深&#xff0c;每天都会有大量的数据产生&#xff0c;我们经常会感到数据越来越多&#xff0c;但是要从中发现有价值的信息却越来越难。这里所说的信息&#xff0c;可以理解为对数据集处理之后的结果&#xff0c;是从…

【Selenium(五)】

一、鼠标事件 from selenium import webdriver # 导入ActionChains类进行鼠标悬停操作 from selenium.webdriver.common.action_chains import ActionChains import time# 打开一个浏览器 # 法一、添加环境变量重启电脑 # 法二、填写浏览器驱动的绝对路径 driver webdriver.E…

vmare17 安装不可启动的iso镜像系统

由于要测试一个软件&#xff0c;要安装一个Windows11_InsiderPreview_Client_x64_zh-cn_26058.iso 于是在虚拟机里捣鼓一下。但是这个iso好像不能直接启动 这样就无法直接安装了&#xff0c;怎么办呢&#xff0c;可以先用个pe系统引导进去&#xff0c;再在PE系统里安装这个iso…

【免费】如何考取《鲸鸿动能广告初级优化师》认证(详细教程)

鲸鸿动能广告初级优化师认证考试PC网址 初级&#xff1a;鲸鸿动能广告初级优化师认证-华为开发者学堂 (huawei.com) 注&#xff1a;免费认证&#xff0c;里面包含免费的课程&#xff0c;浏览器用Edge。 文章目录 鲸鸿动能广告初级优化师认证考试网址 前言 一、备考流程 二…

软考 网络工程师 每日学习打卡 2024/3/21

学习内容 第8章 网络安全 本章主要讲解网络安全方面的基础知识和应用技术。针对考试应该掌握诸如数据加密、报文认 证、数字签名等基本理论&#xff0c;在此基础上深入理解网络安全协议的工作原理&#xff0c;并能够针对具体的 网络系统设计和实现简单的安全解决方案。 本章共有…

python云上水果超市的设计与实现flask-django-php-nodejs

伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对云上水果超市进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套云上水果超市&#xff0c;帮助商家进行商品信…

如何为您的网站压缩图像

今天碰到一个客户反馈&#xff0c;他在hostease购买了虚拟主机&#xff0c;创建的WordPress站点图片比较多&#xff0c;后来访问网站&#xff0c;页面上大量的图片加载时间较长&#xff0c;咨询网站图像如何压缩。我们为用户提供网站图像压缩&#xff0c;用户很快完成了设置。在…

【Pt】新建项目时的设置

新建项目时需要在如下界面做一些设置。 一、模板与文件 模板通常选择“PBR - Metallic Roughness Alpha-blend” 文件可以选择fbx&#xff0c;abc&#xff0c;obj等格式的三维模型文件 二、项目设置 2.1 文件分辨率 指的是在软件中的预览效果&#xff0c;分辨率越高预览效果…

使用npm创建一个全局的cli命令,就像vue-cli一样

我们用过vue-cli等工具包&#xff0c;全局安装之后&#xff0c;我们可以直接使用vue create等命令&#xff0c;实际上能够这样使用的原因&#xff0c;就是使用了package.json里面的bin字段注册命令。接下来就以一个脚本文件为例子为大家演示一下bin是如何发挥作用的。 创建项目…

跳过mysql权限验证来修改密码-GPT纯享版

1.打开 MySQL 的配置文件&#xff0c;通常是 my.ini 或 my.cnf。 2.找到 [mysqld] 部分&#xff0c;如果没有则添加。 3.在 [mysqld] 部分中添加一行&#xff1a;skip-grant-tables&#xff0c;这个选项告诉 MySQL 服务器跳过权限验证&#xff0c;允许任何用户连接而不需要密码…

java数据结构与算法基础-----排序------堆排序

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 堆排序是利用堆&#xff08;数据结构&#xff09;设计的排序算法…

基于SSM+Jsp+Mysql的KTV点歌系统

基于SSMJspMysql的KTV点歌系统 基于SSMJspMysql的KTV点歌系统的设计与实现 开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工…

SRC中的一些信息收集姿势

前言 前前后后挖了四个月的EDUSRC&#xff0c;顺利从路人甲升到了网络安全专家&#xff0c;从提交的内容来看大部分还是以中低危为主&#xff0c;主打的就是弱口令和未授权。 在这过程中还是比较浮躁的&#xff0c;因此接下来的时间还是要好好沉淀一下自身的技术&#xff0c;学…

undo log

从这篇「执行一条 SQL 查询语句&#xff0c;期间发生了什么&#xff1f; (opens new window)」中&#xff0c;我们知道了一条查询语句经历的过程&#xff0c;这属于「读」一条记录的过程&#xff0c;如下图&#xff1a; 那么&#xff0c;执行一条 update 语句&#xff0c;期间发…

vue3 报错 require is not defined

问题 require is not defined 原因 vite 不支持require的用法&#xff0c; webpack是支持的 解决 方法一&#xff1a; 更改vite使用语法 vite官网 方法二 安装转换插件vite-plugin-require-transform 仓库地址 参考 关于Vite不能使用require问题 方法二Vite 踩坑 —— …

鸿蒙开发-UI-动画-页面间动画

鸿蒙开发-UI-组件导航-Navigation 鸿蒙开发-UI-组件导航-Tabs 鸿蒙开发-UI-图形-图片 鸿蒙开发-UI-图形-绘制几何图形 鸿蒙开发-UI-图形-绘制自定义图形 鸿蒙开发-UI-图形-页面内动画 鸿蒙开发-UI-图形-组件内转场动画 鸿蒙开发-UI-图形-弹簧曲线动画 文章目录 前言 一、放大缩…