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

news2024/10/5 16:23:48

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服务器实现大型网络聊天室的全部内容了。

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

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

相关文章

Spring 6【DI的两种方式、自动注入】(四)-全面详解(学习总结---从入门到深化)

目录 八、DI的两种方式 九、自动注入 八、DI的两种方式 IoC是由IoC容器帮助实例化Bean&#xff0c;并且在IoC容器内部注入关联属性(对象)的过程。 在上面演示的都是如何实例化Bean&#xff0c;下面演示的是如果给Bean注入属性。也就是说在讲解IoC/DI的另 一半功能。 在前面…

实训笔记7.18

实训笔记7.18 7.18一、座右铭二、Hadoop大数据技术 大数据软件一般都要求7*24小时不宕机三、Hadoop的组成3.1 HDFS3.2 MapReduce3.3 YARN3.4 Hadoop Common 四、Hadoop生态圈五、Hadoop的安装问题5.1 Hadoop的本地安装模式-基本不用5.2 Hadoop的伪分布安装模式5.3 Hadoop的完全…

webpack插件compression-webpack-plugin

Vue配置compression-webpack-plugin实现Gzip压缩 1、为什么要压缩&#xff1f; 打包的时候开启gzip可以很大程度减少包的大小&#xff0c;页面大小可以变为原来的30%甚至更小&#xff0c;非常适合于上线部署。更小的体积对于用户体验来说就意味着更快的加载速度以及更好的用户…

SOPC之NiosⅡ系统(五)

NIOS Ⅱ系统实例 目录 2.创建BSP工程 2.1 创建BSP工程 2.2 BSP Editor 2.3 创建C代码文件 3.Nios Ⅱ实例 3.1 Hello NIOS Ⅱ 3.2 System ID与Timestamp 3.3 蜂鸣器定时鸣叫 3.4 拨码开关输入GIO控制 4.FPGA器件的代码固化 4.1 嵌入式软件HEX文件生成 4.2 程序存储…

RocketMQ环境搭建

环境搭建 环境准备 下载地址: https://downloads.apache.org/rocketmq/4.9.5/安装 上传至服务器 mkdir /usr/soft #上传至此目录/usr/softmkdir /usr/soft 解压 cd /usr/soft unzip rocketmq-all-4.9.5-bin-release.zip移动 mkdir /usr/local/rocketmq cd /usr/soft mv r…

Mysql 修改group_concat_max_len的默认值

1.前言 最近在进行递归查询组织及其下属组织时&#xff0c;发现数据查询不全&#xff0c;子组织数据查询不出来的问题。经排查发现是group_concat_max_len的长度不足引起的&#xff0c;默认情况下group_concat_max_len1024&#xff0c;所以我们需要修改这个默认参数。 2.SQL语…

.net core控制台应用程序在linux运行

1&#xff09;创建.net 6.0控制台应用程序 2&#xff09;在应用根目录执行cmd命令发布应用&#xff1a;dotnet publish -o .\deploy 3&#xff09;将发布文件上传到服务器 4&#xff09;运行控制台应用程序&#xff08;dotnet /home/app/ConsoleApp/ConsoleApp5.dll&#xff09…

卡尔曼与扩展卡尔曼的区别与推导

1.卡尔曼的推导&#xff1a; 1&#xff09;先看系统随机系统状态空间模型&#xff1a;&#xff08;线性&#xff09; 所谓线性是指递推或者状态转移方程是线性的 至于参数解释自己去看书&#xff0c;本文旨在捋顺推导思路。 2&#xff09;k-1时刻值减去k-1的状态最优估计k-1时…

Vscode设置忽略文件,忽略node-modules、dist

####看图 files.exclude 设置排除和显示的文件夹 search.exclude 设置搜索时忽略的文件夹

WEB:Cat

背景知识 命令执行漏洞 Django框架 题目 先ping一下&#xff0c;输入127.0.0.1 这个输入可能存在命令执行的操作&#xff0c;但是经过尝试之后并不能正常执行 127.0.0.1&&dir、127.0.0.1&&ls、127.0.0.1|ls均被屏蔽&#xff0c;但经过尝试可知&#xff0c;网…

matplotlib笔记:qbstyle设置matplotlib 主题

0 原始matplotlib import numpy as np import matplotlib.pyplot as pltxnp.linspace(0,100) ynp.sin(100*x)plt.plot(x,y); 1 light from qbstyles import mpl_style mpl_style(darkFalse) #开启light主题 plt.plot(x,y); 2 dark from qbstyles import mpl_style mpl_styl…

实现大文件传输的几种方法,并实现不同电脑间大文件传输

随着网络技术的快速发展&#xff0c;大文件的传输需求越来越多&#xff0c;如何在不同的电脑之间实现大文件的快速传输&#xff0c;是一个挑战&#xff0c;下面介绍几种常用的方法可以解决这个问题。 1、利用局域网传输&#xff1a;把两台电脑接入同一个网络环境&#xff0c;通…

关于硬件加速器FPGA的异构加速流程龙蜥CI框架及实践介绍 | 第 87-88 期

本周「龙蜥大讲堂」预告来啦&#xff01;我们邀请了浪潮信息异构加速软件工程师刘科分享《基于 FPGA 的数据库硬件加速研究》、 CICD SIG Maintainer 李晔做《龙蜥社区 CI 框架及实践》主题演讲&#xff0c;精彩多多&#xff0c;快入群&#xff0c;预定前排小板凳观看直播&…

项目名称:无源在线词典项目

一&#xff0c;概述 基于C语言的网络电子词典项目&#xff0c;使用到了tcp协议的并发服务器设计、网络编程、文件I/O、数据库等多方面的知识。可以满足多用户同时登陆&#xff0c;用户登陆后可以查询单词及历史记录&#xff0c;具有查找快速&#xff0c;保密性好等优点。 开…

19.删除链表的倒数第N个节点

19.删除链表的倒数第N个节点 这道题是链表问题中双指针的一个经典应用 如果要删除倒数第n个节点&#xff0c;那么我们让fast快指针移动n步&#xff0c;然后让fast和慢指针slow同时开始移动&#xff0c;当fast指针指向链表末尾的时候&#xff0c;删掉slow指针指向的节点即可。 …

ai绘画工具哪个好用?这几款好用的ai绘画生成器安利给你

嘿&#xff0c;小伙伴们&#xff01;你是否曾经想过创作一幅酷炫的人物插画&#xff0c;但由于缺乏绘画技巧而放弃这个想法&#xff1f;别担心&#xff0c;现在有了ai绘画工具&#xff0c;让你轻松成为艺术家的潜力无限&#xff01;今天我就来给大家介绍几个用ai绘画工具生成好…

【iOS】ARC实现

ARC由以下工具来实现&#xff1a; clang&#xff08;LLVM编译器&#xff09;3.0以上objc4 Objective-C运行时库493.9以上 下面我们&#xff0c;我们将围绕clang汇编输出和objc4库的源代码探究ARC实现 1. __strong修饰符 1.1 赋值给附有__strong修饰符的变量 看下面代码 {…

Android TextView 在最后一行末尾加图标

当前有个需求.显示一段文本&#xff0c;文本最多显示两行&#xff0c;点击展开后才显示完全。当没有显示完全的时候&#xff0c;需要在文本的第二行末尾显示图标&#xff0c;点击图标和文本&#xff0c;文本展开。难点在于图标需要和第二行文本显示在同一行&#xff0c;高度和文…

JavaScript的WebAPI

这里写目录标题 DOM 基本概念获取元素事件概念事件的三要素操作元素获取/修改表单元素属性行内样式操作类名样式操作操作节点 DOM 基本概念 DOM 全称为 Document Object Model. W3C 标准给我们提供了一系列的函数, 让我们可以操作: 网页内容 ,网页结构, 网页样式 DOM数的结构如…

【PDFBox】PDFBox操作PDF文档之读取指定页面文本内容、读取所有页面文本内容、根据模板文件生成PDF文档

这篇文章&#xff0c;主要介绍PDFBox操作PDF文档之读取指定页面文本内容、读取所有页面文本内容、根据模板文件生成PDF文档。 目录 一、PDFBox操作文本 1.1、读取所有页面文本内容 1.2、读取指定页面文本内容 1.3、写入文本内容 1.4、替换文本内容 &#xff08;1&#xf…