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.mfbz.cn/a/477827.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LLM+Embedding构建问答系统的局限性及优化方案

LangChain LLM 方案的局限性&#xff1a;LLM意图识别准确性较低&#xff0c;交互链路长导致时间开销大&#xff1b;Embedding 不适合多词条聚合匹配等。 背景 在探索如何利用大型语言模型&#xff08;LLM&#xff09;构建知识问答系统的过程中&#xff0c;我们确定了两个核心…

飞跃前端瓶颈:技术进阶指南精华篇

引言&#xff1a; 在互联网的快车道上&#xff0c;前端技术日新月异。对于前端工程师而言&#xff0c;技术水平达到一定高度后&#xff0c;往往会遭遇成长的天花板。本文将探讨如何识别并突破这些技术瓶颈&#xff0c;分享实用的进阶策略和实践案例。 一、技术等级概览&#xf…

python知识点总结(七)

python知识点总结七 1、堆和栈的区别2、如何在局部修改全局的变量a、计算结果b、计算结果 3、如何修改一个enclosing变量4、关于值传递还是地址传值5、布尔类型6、逻辑运算7、字符串切片操作8、取整、取余、除数9、变量赋值10、字符串与数字相乘11、整型、浮点型、字符型之间相…

【LVGL-特殊符号】

LVGL-特殊符号 ■ LVGL-特殊符号 ■ LVGL-特殊符号 /* 直接调用 */ lv_label_set_text(my_label, LV_SYMBOL_OK); /* 与字符一起用 */ lv_label_set_text(my_label, LV_SYMBOL_OK "Apply"); /* 多个符号一起用 */ lv_label_set_text(my_label, LV_SYMBOL_OK LV_SYMBO…

智过网:一级建造师必须两年考过吗?有效期多久?

在建筑行业&#xff0c;一级建造师的职业资格证书是众多从业者追求的目标。然而&#xff0c;获得这一证书并非易事&#xff0c;它要求考生不仅具备扎实的专业知识&#xff0c;还需要在限定的时间内完成所有科目的考试。那么&#xff0c;一级建造师是否必须在两年内考完所有科目…

LeetCode - 存在重复元素

219. 存在重复元素 II 这道题可以用两个方法解决。 哈希表 从左到右遍历数组&#xff0c;并将数组的下标存到hash中&#xff0c;在遍历数字的过程中&#xff0c;如果hash中不存在nums[i]&#xff0c;将nums[i]加入到hash当中&#xff0c;若存在&#xff0c;则判断下标之间的关…

九泰智库 | 医械周刊- Vol.16

⚖️ 法规动态 28类耗材联盟集采结果出炉&#xff0c;中选率仅27% 3月19日&#xff0c;河北省药械集采中心发布了《关于公示京津冀“3N”联盟28种集中带量采购医用耗材拟中选结果的通知》。共有202个产品被列为拟中选&#xff0c;产品中选率约为27%。本次集采未设置保底中标条…

buuctf_Reverse_wp_2

文章目录 [WUSTCTF2020]level3[base64变表]Youngter-drive[upx、多线程][FlareOn4]IgniteMe[算法分析]相册[APK、so文件、Native方法][WUSTCTF2020]Cr0ssfun[套娃、patience][GWCTF 2019]xxor[z3、算法分析][UTCTF2020]basic-re[FlareOn6]Overlong脚本输出动态调试 [FlareOn3]C…

科技强国:国产桌面操作系统正式上线,中国七位院士发表支持

操作系统是电子产品的核心&#xff0c;无论是手机还是电脑&#xff0c;都离不开它的支持。它是科技发展中至关重要的领域之一。备受瞩目的鸿蒙系统&#xff0c;作为国内颇具影响力的手机桌面操作系统&#xff0c;以其桌面组件自由整合和自定义风格的便捷性&#xff0c;引领了一…

运用YOLOv5实时监测并预警行人社交距离违规情况

YOLO&#xff08;You Only Look Once&#xff09;作为一种先进的实时物体检测算法&#xff0c;在全球范围内因其高效的实时性能和较高的检测精度受到广泛关注。近年来&#xff0c;随着新冠疫情对社交距离管控的重要性日益凸显&#xff0c;研究人员开始将YOLO算法应用于社交距离…

5G安全技术新突破!亚信安全5G安全迅龙引擎正式发布

5G专网应用飞速增长&#xff1a;2020年5G专网数量800个&#xff0c;2021年2300个&#xff0c;2022年5325个&#xff0c;2023年已经超过16000个&#xff0c;5G与垂直行业的融合快速加深&#xff0c;5G带来的变革正加速渗透至各行各业。 5G网络出现安全问题&#xff0c;将是异常严…

QT tableWidget横向众向设置

横向控件 要设置QTabWidget选项卡的字体方向&#xff0c;可以使用QTabWidget的setTabPosition()方法。通过传递Qt枚举值QTabWidget.east或QTabWidget.west作为参数&#xff0c;可以设置选项卡的字体方向为从左到右或从右到左。 myTabWidget QTabWidget() myTabWidget.setTabP…

【论文精读】DALLE3:Improving Image Generation with Better Captions 通过更好的文本标注改进图像生成

文章目录 一、文章概览二、数据重标注&#xff08;一&#xff09;现在训练数据的文本标注主要存在的问题&#xff08;二&#xff09;创建图像标注器&#xff08;三&#xff09;微调图像标注器 三、评估重新标注的数据集&#xff08;一&#xff09;混合合成标注和真实标注&#…

【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

PCD8000D开关调光/调色线性恒流LED控制芯片 无需变压器及电阻电容 只需极少元器件

概述 PCD8000D 是一款开关调节亮度/色温的LED恒流驱动IC。适用于AC 180V-240V 或AC 90V- 130V 输入电压&#xff0c;恒流精度小于 5% 。PCD8000D在3 段调节亮度应用中&#xff0c;可根据开启/关闭电源&#xff0c;依次改变输出电流的大小&#xff0c;从而改变LED 灯的亮度, …

RK3568驱动指南|第二篇 字符设备基础-第13章 杂项设备驱动实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

SpringMVC | SpringMVC中的 “JSON数据交互“ 和 “RESTful设计风格“

目录: 一、JSON 数据交互1.1 JSON概述1.2 JSON的“数据结构”对象结构数组结构 1.3 JSON的“数据转换”用 \<mvc:annotation-driven/>的方式 来“自动配置” MappingJackson2HttpMessageConverter转换器 (来完成JSON数据转换)用\<bean>标签方式的来“自行配置” JS…

Midjourney发布新特性风格参考

1. 引言 最近&#xff0c;Midjourney 推出了Style Reference V2.0 即功能更加强大的风格参考工具&#xff0c;该工具可以让大家参考其他图像的风格&#xff0c;生成与参考图像风格保持一致&#xff0c;与文本提示词语义内容保持一致的图像。它与图像提示类似&#xff0c;但是只…

关于Count,FPKM,TPM,RPKM等表达量的计算

原文链接&#xff1a;关于Count&#xff0c;FPKM&#xff0c;TPM&#xff0c;RPKM等表达量的计算及转换 | 干货 写在前面 今天使用count值转化TPM&#xff0c;或是使用FPKM转换成TPM。这样的教程&#xff0c;我们在前面已经出国一起相对比较详细的教程了&#xff0c;一文了解…

(一)基于IDEA的JAVA基础1

Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实现了面向对象理论&#xff0…
最新文章