【Linux】udp服务器实现大型网络聊天室

udp终结篇~

文章目录

  • 前言
  • 一、udp服务器实现大型网络聊天室
  • 总结


前言

根据上一篇文章中对于英汉互译和远程操作的两个小功能大家应该已经学会了,我们的目的是让大家可以深刻的理解udp服务器所需要的接口已经实现的简单步骤,下面我们开始实现网络聊天室。


一、udp服务器实现大型网络聊天室

首先我们的实现思想是,当客户端发消息给服务器,这个时候服务器会将这条消息发给所有在线的用户,这样的话每个用户就能看到别人的消息了,所以我们首先创建一个文件来写用户管理的代码,由于我们的实现只是为了增加对udp服务器的理解,所以我们不会特别详细,就比如应该先注册再用账号和密码登录然后图形化界面什么的我们就不搞了,有兴趣的可以自己下去手动添加,我们只是写出最主要的功能。

#include <iostream>
using namespace std;

class User
{
public:
    User(const string& ip,const uint16_t &port)
       :_ip(ip)
       ,_port(port)
    {

    }
    ~User()
    {

    }
private:
    string _ip;
    uint16_t _port;
};

首先每个用户都要有自己的端口号和IP,然后我们就暂时写一个构造和析构就好了,在这里大家就可以设计每个用户的昵称等信息了。然后我们还需要有一个管理用户的类:

class UserManager
{
public:
    UserManager()
    {
    }
    ~UserManager()
    {
    }
    void addUser(const string &ip, const uint16_t port)
    {
    }
    void delUser(const string &ip, const uint16_t &port)
    {
    }

private:
    unordered_map<string,User> users;
};

我们的目的是直接通过用户的IP和端口号组成一个ID来实现映射,也就是让map去实现用户的增加删除操作。下面我们完善每个接口的代码:

    void addUser(const string &ip, const uint16_t port)
    {
        string id = ip + "-" + to_string(port);
        users.insert(make_pair(id,User(ip,port)));
    }
    void delUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        users.erase(id);
    }

首先每个用户都有自己的IP和端口号,所以我们用一个字符串去拼接他们的ID和端口号,这样就形成了唯一的id,然后用map的插入,插入的用户对象我们直接用匿名对象构造即可。删除一个用户也是同样的套路,下面我们再写一个判断是否在线的接口:

    bool isOnline(const string& ip,const uint16_t& port)
    {
        string id = ip + "-" + to_string(port);
        if (users.find(id)==users.end())
        {
            return false;
        }
        else 
        {
            return true;
        }
    }

我们就通过这个用户是否在map中来判断这个用户是否在线即可。管理用户的部分我们搞定后,下面我们再重新写一个回调方法:

 然后我们开始设计消息路由的回调方法:

void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //首先用户要上线就需要先给服务器发送online
    if (message=="online")
    {
        onlineuser.addUser(clientip,clientport);
    }
    if (onlineuser.isOnline(clientip,clientport))
    {
       //进行消息的路由
    }
    else
    {
        //不在线就提示用户需要先上线
        string response;
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        bzero(&client,sizeof(client));
        client.sin_family = AF_INET;
        client.sin_port = htons(clientport);
        client.sin_addr.s_addr = inet_addr(clientip.c_str());
        response = "你还没有上线,先输入online上线";
        sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,len);
    }
}

我们首先判断用户是否输入online,如果输入就添加到map中,如果不在线就告诉用户需要先在线,那么我们该如何实现消息的路由呢?因为我们的map中存放的都是已经在线的用户,所以我们直接在用户管理中写一个函数,这个函数的目的是遍历map然后给每个用户发消息,因为我们是知道用户的端口号和ip的,所以发消息的内容就非常简单了:

首先包含网络相关的头文件,然后进行消息广播接口的编写:

 void broadcastMessage(int sockfd,const string& message)
    {
        for (auto& ur:users)
        {
            struct sockaddr_in client;
            bzero(&client,sizeof(client));
            client.sin_family = AF_INET;
            client.sin_port = htons(ur.second.returnport());
            client.sin_addr.s_addr = inet_addr(ur.second.returnip().c_str());
            sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&client,sizeof(client));
        }
    }

 首先我们发送消息必须用到sendto接口,这个接口会用到文件描述符,所以我们除了需要一个广播的消息,还需要文件描述符,然后给每个在线的用户发消息,发消息要用到用户的ip和port,这两个参数是用户的私有成员变量,所以我们写两个接口拿到这两个参数:

 当然我们考虑到广播消息的时候其他用户还要看到这条消息是谁发的,所以我们应该加入发消息这个人的ip和port:

 void broadcastMessage(int sockfd,const string& clientip,const uint16_t& clientport, const string& message)
    {
        for (auto& ur:users)
        {
            struct sockaddr_in client;
            bzero(&client,sizeof(client));
            client.sin_family = AF_INET;
            client.sin_port = htons(ur.second.returnport());
            client.sin_addr.s_addr = inet_addr(ur.second.returnip().c_str());
            string s = clientip + "-" + to_string(clientport) + "# ";
            s+=message;
            sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));
        }
    }

 我们在回调方法中还应该加入用户下线的信息,一旦下线我们就将这个用户从map中移除,上面服务端的代码我们就写完了,下面开始修改客户端的代码:

 客户端的代码中我们只需要让每次客户输入前有#的标记,最后消息打印完换行即可(注意:上面代码中我们用recvfrom这个函数的时候将结构体进行了填充,实际上recvfrom这个函数并不需要填充结构体)。下面我们引入多线程的知识,让我们的消息变成有一个线程专门读消息,另一个线程专门发送消息,这样的话即使有人不想说话也依旧能看到其他用户的聊天内容,如果我们不加入多进程或多线程的概念,那么就会出现用户不说话就无法看到其他用户的消息:

 首先加入一个线程的变量,然后我们让这个读线程去执行客户端中读取服务端发送消息的过程,主线程去给服务端发消息:

static void *readMessage(void* args)
       {
           int sockfd = *(static_cast<int*>(args));
           pthread_detach(pthread_self());
           while (true)
           {
               char buffer[1024];
               struct sockaddr_in temp;
               socklen_t len = sizeof(temp);
               int n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &len);
               if (n >= 0)
               {
                   buffer[n] = 0;
               }
               cout << buffer << endl;
           }
           return nullptr;
       }
       void run()
       {
           pthread_create(&reader,nullptr,readMessage,(void*)&_sockfd);
           struct sockaddr_in server;
           memset(&server,0,sizeof(server));
           server.sin_family = AF_INET;
           server.sin_addr.s_addr = inet_addr(_serverip.c_str());
           server.sin_port = htons(_serverport);
           string message;
           while (!_quit)
           {
              cout<<"# ";
              char commandbuffer[1024];
              fgets(commandbuffer,sizeof(commandbuffer)-1,stdin);
              message = commandbuffer;
              sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
           }

创建线程后让reader去执行read方法,将文件描述符传过去,由于主线程是死循环的发送消息,所以我们就不让主线程去等待reader线程了,直接将reader线程分离即可。分离后read线程就执行接收服务器消息的代码,主线程执行向服务器发送消息的代码。

下面我们演示一下:

首先我们可以搞一个小玩法,先创建一个管道文件,然后让服务器启动起来,将所有服务器发给我们客户端的消息我们都重定向到管道文件中,然后用另一个窗口使用cat命名查看管道文件中的内容,这样的话就实现了一个窗口我们只发送消息,一个窗口只查看服务器给我们发的消息,群聊消息可以在服务器看到。但是因为我们客户端run函数中是cout打印,cout会向一号描述符去打印,这就导致打印的消息也被重定向到管道了,所以我们将客户端中的消息用cerr打印,cerr会打印到二号描述符,这样就不会被重定向到管道文件了:

 上面重定向有什么作用呢?,作用其实就是让我们单独接收服务器给我们发送的消息,这也是重定向的一种玩法。当我们运行起来后发现我们不能上线输入online不识别:

 原因是我们输入的时候会带有回车,所以我们直接将回车去掉就好了:

 去掉后我们重新运行:

我们发现这次可以正常的上线了,我们再试试下线:

 下线也没有问题。下面我们开启两个Xshell来测试一下:

先开启服务端:

 然后我们已经用另一个xshell发了消息,客户端收到了消息:

 现在我们让这个用户上线:

 这个时候我们自己还没有上线:

 然后让自己上线:

 可以看到是没有问题的,下面可以看一下多人一起聊天的成果:

以上就是我们udp服务器实现大型网络聊天室的全部内容了。

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

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

相关文章

3.SpringBoot 返回Html界面

1.添加依赖spring-boot-starter-web <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>2.创建Html界面 在Resources/static 文件夹下面建立对应的html&#xff0c…

快速排序的非递归实现、归并排序的递归和非递归实现、基数排序、排序算法的时间复杂度

文章目录 快速排序的非递归三数取中法选取key快速排序三路划分 归并排序的递归归并排序的非递归计数排序稳定性排序算法的时间复杂度 快速排序的非递归 我们使用一个栈来模拟函数的递归过程&#xff0c;这里就是在利用栈分区间。把一个区间分为 [left,keyi-1][key][keyi1,right…

用百度地图api获取当前定位,获取经纬度——前端笔记

问题&#xff1a; 做一个按钮&#xff0c;点击后可以获取到当前位置的经纬度&#xff0c;并渲染地图。 效果如下: 代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head><title>获取当前定位测试<…

【笔记MD】

https://editor.csdn.net/md/?not_checkout1&articleId131798584 这里写自定义目录标题 https://editor.csdn.net/md/?not_checkout1&articleId131798584欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入…

行业数据和报告到底应该如何去找?

信息时代&#xff0c;经常要对行业信息进行分析。这时首先就是要进行信息收集和筛选&#xff0c;如果我们懂得构建自己的工作工具和数据来源&#xff0c;效率会蹭蹭往上涨。 找行业报告、了解行业趋势&#xff0c;提高效率。 1. 国家权威 国家统计局&#xff1a;这个网站覆盖…

谋合作、创新境 | 百度参观图为科技生产全链路

当代科技的发展不断催生出新的变革和机遇&#xff0c;百度作为全球顶尖的高科技公司&#xff0c;凭借其强大的创新基因&#xff0c;一直处于人工智能领域的最前沿。   近日&#xff0c;百度公司派出了一支专业团队来到了图为科技&#xff0c;对图为的研发技术及生产线进行了全…

基于MSP432P401R跟随小车【2022年电赛C题】

文章目录 一、赛前准备1. 硬件清单2. 工程环境 二、赛题思考三、软件设计1. 路程、时间、速度计算2. 距离测量3. 双机通信4. 红外循迹 四、技术交流 一、赛前准备 1. 硬件清单 主控板&#xff1a; MSP432P401R测距模块&#xff1a; GY56数据显示&#xff1a; OLED电机&#x…

基于单片机智能台灯坐姿矫正器视力保护器的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;LCD1602液晶显示当前当前光线强度、台灯灯光强度、当前时间、坐姿距离等&#xff1b;按键设置当前时间&#xff0c;闹钟、提醒时间、坐姿最小距离&#xff1b;通过超声波检测坐姿&#xff0c;当坐姿不正容易对眼睛和身体腰部等造成…

Git 使用笔记

Git使用笔记 1 版本控制 1.1 什么是版本控制 ​ 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。简单说就是用…

大模型开发(六):OpenAI Completions模型详解并实现多轮对话机器人

全文共8500余字&#xff0c;预计阅读时间约17~30分钟 | 满满干货(附代码)&#xff0c;建议收藏&#xff01; 代码下载点这里 一、 Completions与Chat Completions基本概念 经过海量文本数据训练的大模型会在全量语义空间内学习语法关系和表达风格&#xff0c;并通过某些微调过…

postgresql部署及优化

目录 一、postgresql概念 二、PostgreSQL 特征 三、postgres安装与登录 3.1、postgres安装 3.2、postgres使用 3.3.1、postgres登录 3.3.2、修改postgres用户密码 四、PostgreSQL 命令 4.1、PostgreSQL 创建数据库 4.2、删除数据库 4.3、创建表格 4.4、删除表格 一、…

Python爬虫学习笔记(一)————网页基础

目录 1.网页的组成 2.HTML &#xff08;1&#xff09;标签 &#xff08;2&#xff09;比较重要且常用的标签&#xff1a; ①列表标签 ②超链接标签 &#xff08;a标签&#xff09; ③img标签&#xff1a;用于渲染&#xff0c;图片资源的标签 ④div标签和span标签 &…

【MySQL】MySQL在Centos7环境下安装

目录 一、卸载不要的环境 1.1、查看是否有安装mysql 1.2、关闭运行的程序 1.3、卸载安装 二、配置yum 源 2.1、下载yum 源 2.2 安装yum源 2.3 查看是否已经生效 三、安装mysql服务 四、启动服务 五、登录方法 方法一&#xff08;不行就下一个&#xff09; 方法二&#xff08;不…

【Tauri + React 实战】VCluster - 了解技术选型与开发环境配置

VCluster A React Tauri App as visualizer of apps cluster on windows. 背景介绍 VCluster是一个在开发环境下&#xff0c;用以对一系列应用集群&#xff08;如分布式、微服务&#xff09;进行可视化管理的桌面应用程序&#xff0c;目标是实现类似 docker-compose 那样的集…

怎么解决亚马逊跟卖?为何卖家总是举报不成功?

以前大家都是从跟卖的时代走向现在的品牌化运营之路&#xff0c;但是现在跟卖已经从大家都模仿的对象变成了大部分卖家厌恶的对象&#xff0c;那么怎么解决这个跟卖问题呢&#xff1f;目前最直接的方法就是进入亚马逊后台进行举报&#xff0c;但是大概率是失败的。 一、举报违…

Spring Cloud 之 Gateway 网关

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

elasticsearch基本操作

elasticsearch 下面参数详细解释 java 搜索查询看官方文档 https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.8/connecting.html#_your_first_request{"name" : "Tom Foster","cluster_name" : "elasticsearch&q…

Kafka 入门到起飞 - 核心概念(术语解释)

在kafka之旅&#xff0c;我们会大量讨论Kafka中的术语&#xff0c;那么就让我们先来了解一下这些核心概念 消息(Message)&#xff1a; kafka的数据单元称为消息&#xff0c;相当于DB里的一行数据或一条记录 消息由字节数组组成 批次&#xff1a; 生产者组一批数据再向kafka推送…

消息重试框架 Spring-Retry 和 Guava-Retry

一 重试框架之Spring-Retry 1.Spring-Retry的普通使用方式 2.Spring-Retry的注解使用方式 二 重试框架之Guava-Retry 总结 图片 一 重试框架之Spring-Retry Spring Retry 为 Spring 应用程序提供了声明性重试支持。它用于Spring批处理、Spring集成、Apache Hadoop(等等)。…

MySQL高阶语句

目录 一、常用查询 1、按关键字排序 2、区间判断及查询不重复记录 3、限制结果条目 4、设置别名&#xff08;alias ——》as&#xff09; 5、通配符 一、常用查询 &#xff08;增、删、改、查&#xff09; 对 MySQL 数据库的查询&#xff0c;除了基本的查询外&#xff0c;…