【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)
{//首先用户要上线就需要先给服务器发送onlineif (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.hqwc.cn/news/28027.html

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

相关文章

2023最新版本Activiti7系列-网关服务

网关篇 网关可控制流程的执行流向&#xff0c;常用于拆分或合并复杂的流程场景。在Activiti7中&#xff0c;有以下几种类型的网关&#xff1a; 排他网关&#xff08;Exclusive Gateway&#xff09;&#xff1a;用于在流程中进行条件判断&#xff0c;根据不同的条件选择不同的分…

使用wxPython和pillow开发拼图小游戏(四)

上一篇介绍了使用本地图片来初始化游戏的方法&#xff0c;通过前边三篇&#xff0c;该小游戏的主要内容差不多介绍完了&#xff0c;最后这一篇来介绍下游戏用时的计算、重置游戏和关闭窗口事件处理 游戏用时的计算 对于游戏用时的记录&#xff0c;看过前几篇的小伙伴可能也发现…

linux之Ubuntu系列(-)常见指令

Ubuntu 中文 版本 注意点 通过修改语言改成英文 在终端录入&#xff1a;export LANGen_US 在终端录入&#xff1a;xdg-user-dirs-gtk-update 单用户和多用户 命令格式 command [-选项] [参数] –查看命令的帮助 命令 --help man 命令 |操作键| 功能| |空格键|-显示手册的下…

15 - 堆栈 - 大顶堆

前面我们学习了小顶堆,相信大家都已经有点概念了,今天来了解一下大顶堆。 大顶堆示意图 堆数组存放的公式 我们用简单的公式来描述一下堆的定义就是: 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 小顶堆:arr[i] <= arr[2i+1] && a…

2023 最新版网络安全保姆级指南,手把手带你从零基础进阶渗透攻防工程师

前言 一份网络攻防渗透测试的学习路线&#xff0c;不藏私了&#xff01; 1、学习编程语言(phpmysqljshtml) 原因&#xff1a;phpmysql 可以帮助你快速的理解 B/S 架构是怎样运行的&#xff0c;只有理解了他的运行原理才能够真正的找到问题/漏洞所在。所以对于国内那些上来就…

SRT对比TCP协议的优缺点

主流的流媒体协议&#xff0c;如HTTP&#xff0c;HLS&#xff0c;RTMP是TCP协议&#xff0c;而RTSP既可以基于TCP也可基于UDP协议进行数据传输。 从趋势来看&#xff0c;新的流媒体协议大都选择UDP作为底层传输协议&#xff0c;其主要原因和流媒体业务本身的特性及TCP特性有关。…

SpringCloud Gateway网关

文章目录 SpringCloud Gateway1.1 网关架构1.2微服务网关介绍1.3Spring Cloud Gateway(技术选型)1.4依赖1.5yaml配置(包含gateway相关配置,实现转发的功能)1.6断言案例:1.7断言详细介绍1.8 整合nacos1.9 nacos整合网关案例1.10动态路由 SpringCloud Gateway 1.1 网关架构 (dub…

HashSet 、LinkedHashSet 源码级详解

Set 集合类体系如下&#xff1a; HashSet -- 无序、不重复、无索引 LinkedHashSet -- 有序、不重复、无索引 TreeSet -- 可排序、不重复、无索引 HashSet HashSet 底层采用 哈希表 存储数据 哈希表组成 JDK8 之前 -- 数组 链表 JDK8 之后 -- 数组 链表 红黑树 一开始…

OpenVas扫描器更新扫描引擎

OPenvas扫描器安装时step1 是交换指导升级&#xff08;nvt&#xff0c;cert&#xff0c;scap&#xff09;&#xff0c;这次升级后是自动升级24h升级一次&#xff0c;但第一次升级时选择默认的rsync升级时会出现同步失败的问题&#xff0c;导致openvas安装完后有很大模块和规则不…

字符设备驱动开发(最初方式)

目录&#xff1a; 1.字符设备驱动简介2.字符设备驱动开发步骤2.1. 驱动模块的加载与卸载2.2. Makefile的编写2.3.字符设备的注册与注销2.3.1.设备号的组成2.3.2.设备号的分配 2.4.具体操作函数的实现2.4.1.进行打开和关闭操作2.4.2.对chrdev进行读写操作 3.具体程序的实现3.1.驱…

MySql如何卸载干净经验分享

第一步&#xff1a;首先打开注册表&#xff1a;点击电脑的开始按钮&#xff0c;打开找到运行&#xff0c;输入regedit&#xff0c;进入注册表&#xff1b; 第二步&#xff1a;删除mysql再注册表中的信息&#xff0c;以下三个目录&#xff1a; 1.HKEY_LOCAL_MACHINE\SYSTEM\Cont…

计讯物联5G千兆网关TG463在电力智能巡检机器人的应用功能解析

项目背景 随着国家智能电网建设加速推进&#xff0c;投资规模持续扩大&#xff0c;我国电网智能化、信息化不断提高&#xff0c;传统的电力运维与管理模式早已不能满足智能电网快速发展的需求。因此&#xff0c;在5G无线通信、人工智能、物联网、云计算、大数据、电力等前沿技术…