demo版多人聊天系统

news2024/9/21 7:59:55

目录

​编辑

一,引入

二,在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.coloradmin.cn/o/1536101.html

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

相关文章

Java-Java基础学习(4)-多线程(2)

3.7. Lambda表达式 为什么要使用lambda表达式 避免匿名内部类定义过多&#xff1b;可以让代码看起来更简洁&#xff1b;去掉一堆没有意义的代码&#xff0c;只留下核心逻辑 属于函数式编程的概念&#xff0c;格式 (params) -> expression [表达式](params) -> statement…

山东省大数据局副局长禹金涛一行莅临聚合数据走访调研

3月19日&#xff0c;山东省大数据局党组成员、副局长禹金涛莅临聚合数据展开考察调研。山东省大数据局数据应用管理与安全处处长杨峰&#xff0c;副处长都海明参加调研&#xff0c;苏州市大数据局副局长汤晶陪同。聚合数据董事长左磊等人接待来访。 调研组一行参观了聚合数据展…

安装OneNote for Win10 | Win10/Win11

前言 PC端的OneNote分为2个版本&#xff0c;分别是Microsoft Store版本和Office版本&#xff0c;Microsoft Store版本即为OneNote for Win10&#xff0c;此版的OneNote有最近笔记功能&#xff0c;但检索功能不如Office版本&#xff0c;个人认为2个版本各有优劣。 但OneNote f…

详细剖析多线程(更新中...)

文章目录 前言一、认识线程1.1线程概念1.2为什么要有线程1.3线程和进程的区别&#xff08;经典面试题&#xff09; 二、创建线程2.1继承 Thread 类,重写run2.2实现 Runnable 接口,重写run2.3继承 Thread 类,重写run&#xff0c;匿名内部类2.4实现 Runnable 接口,重写run&#x…

WEB 表单练习题

任务如图&#xff1a; <html><head><meta charest"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head><body><table width"…

解决nginx报错nginx: [emerg] unknown log format main in 的方法

目录 一、故障描述1&#xff1a; 重启nginx是出现了如下错误 解决办法 二、故障描述2&#xff1a; 解决办法&#xff1a; 三、nginx介绍​ 四、nginx原理 五、nginx.conf配置文件 六、nginx负载均衡 七、正向代理、反向代理 一、故障描述1&#xff1a; 在添加Nginx的…

【WEEK4】 【DAY3】整合SSM框架之功能实现—修改、删除数据【中文版】

2024.3.20 Wednesday 接上文【WEEK4】 【DAY2】整合SSM框架之功能实现—总览、添加数据【中文版】 目录 7.6.修改功能7.6.1.修改BookController.java7.6.2.修改allBook.jsp7.6.3.新建updateBook.jsp7.6.4.修改MyBatis-config.xml7.6.5.运行 7.7.删除功能7.7.1.修改BookContro…

网络原理(4)——TCP协议的特性

目录 一、滑动窗口 1、ack丢了 2、数据丢了 二、流量控制&#xff08;流控&#xff09; 三、拥塞控制 拥塞窗口动态变化的规则 四、延时应答 五、捎带应答 六、面向字节流 七、异常情况 &#xff08;1&#xff09;进程崩溃了 &#xff08;2&#xff09;其中一方关机…

Ubuntu系统提示“/dev/mmcblk0p1 分区满了‘以及磁盘空间不够的处理办法

查看boot分区使用空间&#xff1a; df 查看已安装的内核版本&#xff1a; dpkg --get-selections | grep linux &#xff08;其中带image的一般就是旧版本&#xff0c;deinstall代表已经删除的旧版本&#xff0c;install代表还未删除的旧版本内核&#xff09; 查看系统当前…

基于java+springboot+vue实现的健身房管理系统(文末源码+Lw+ppt)23-523

摘 要 健身房管理的以往工作流程繁杂、多样、管理复杂与设备维护繁琐。而如今计算机已完全能够胜任健身房管理工作&#xff0c;而且更加准确、方便、快捷、高效、清晰、透明&#xff0c;它完全可以克服以上所述的不足之处。这将给查询信息和管理带来很大的方便&#xff0c;从…

晶体管测试仪系统能测 IGBT. Mosfet. Diode. BJT......

晶体管测试仪系统能测试很多电子元器件的静态直流参数&#xff08;如击穿电压V(BR)CES/V(BR)DSs、漏电流ICEs/lGEs/IGSs/lDSs、阈值电压/VGE(th)、开启电压/VCE(on)、跨导/Gfe/Gfs、压降/Vf、导通内阻Rds(on)&#xff09;。 测试种类覆盖 7 大类别26分类&#xff0c;包括“二极…

Redis 更新开源许可证 - 不再支持云供应商提供商业化的 Redis

原文&#xff1a;Rowan Trollope - 2024.03.20 未来的 Redis 版本将继续在 RSALv2 和 SSPLv1 双许可证下提供源代码的免费和宽松使用&#xff1b;这些版本将整合先前仅在 Redis Stack 中可用的高级数据类型和处理引擎。 从今天开始&#xff0c;所有未来的 Redis 版本都将以开…

力扣454. 四数相加 II

思路&#xff1a;把四个数组拆成两对&#xff0c;两个分别相加&#xff0c;记录第一对的相加结果进map里&#xff0c;再把第二对数组 0-nums2-nums4 去map里面找出现了几次&#xff0c;这题不用对重复的四元组去重&#xff0c;所以出现多次都有效。 class Solution {public int…

Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证

昨日&#xff0c;Redis 官方宣布了一项重要变更&#xff1a;他们将修改开源协议&#xff0c;未来所有版本将采用 “源代码可用” 的许可证。 具体来说&#xff0c;Redis 不再使用 BSD 3-Clause 开源协议进行分发。从 Redis 7.4 版本开始&#xff0c;Redis 将采用 SSPLv1 和 RSA…

探索大广赛获奖作品:职场设计风云的精彩篇章

2024第16届全国大学生创意广告大赛正在火热进行中&#xff0c;今天就与大家分享一些平面类往届优秀获奖作品&#xff0c;仅供参考~灵感慢慢&#xff0c;不要错过哦&#xff01; 大广赛命题素材下载&#xff1a; 下载地址https://js.design/f/Jspbti?sourcecsdn&planbtts…

Go语言学习Day1:什么是Go?

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、走近Go①Go语言的Logo②Go语言的创始人③Go语…

excel文件可以转成word文件吗?汇帮PDF转换器帮你实现excel转word

将Excel文件转换为Word文档是一个相对简单的任务&#xff0c;但在执行过程中需要注意一些细节&#xff0c;以确保转换后的文档格式正确、内容清晰。下面将详细介绍用汇帮PDF转换器将Excel转Word的步骤和注意事项。 一、Excel文件准备 在进行转换之前&#xff0c;首先确保Excel…

软考高级:UML 图-状态图概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

科研学习|论文解读——通过调查实验了解公民对政府财政信息的认知(GIQ,2022)

原文题目 Understanding citizens perception of government fiscal information through a survey experiment 摘要 州和地方政府定期向公众披露财务信息&#xff0c;以便公开承认政府当前的财务状况。此类披露的目的是实现问责制并让公民了解政府的财务决策。(背景&#xff0…

MySQL之索引与事务

一 索引的概念 一种帮助系统查找信息的数据 数据库索引 是一个排序的列表&#xff0c;存储着索引值和这个值所对应的物理地址无须对整个表进行扫描&#xff0c;通过物理地 址就可以找到所需数据是表中一列或者若干列值排序的方法 需要额外的磁盘空间 索引的作用 1 数据库…