UDP实现Mini版在线聊天室

实现原理

只有当客户端先对服务器发送online消息的时候,服务器才会把客户端加入到在线列表。当在线列表的用户发消息的时候,服务器会把消息广播给在线列表中的所有用户。而当用户输入offline时,表明自己要下线了,此时服务器把该用户踢出在线列表。此时的用户看不到公屏的信息也无法在发送信息。

上线步骤:

在这里插入图片描述

通信步骤:

在这里插入图片描述

下线步骤

只要把用户踢出在线列表,那么它就是离线了,因为服务器只关心在线列表中的客户。

在这里插入图片描述

服务器要做的事

1. 判断收到的消息是否是online或者offline

2. 收到online则把用户添加进在线列表,offline则移除在线列表。

3. 如果发送的消息不是offline,切用户在线,则对发送的消息进行广播,广播给在线列表的所有用户

客户端要做的事

1. 向服务器发送online申请上线

2. 主线程负责发送消息,不发也可以

3. 创建一个线程时时刻刻接收消息,收到消息即显示到自己的公屏上

server服务端代码实现

服务端需要有一个UserManage类,来管理在线用户,这也是我们的在线列表。这个类只有一个哈希表成员,用来管理在线的用户。还要提供四个成员函数,分别有上线,下线,判断是否在线,以及广播功能。

server.cc代码:

#include "server.hpp"
#include <memory>
#include <unistd.h>
#include <fcntl.h>
#include <vector>
#include <sys/wait.h>
#include <cstring>
#include "User.hpp"UserManage um;void ChatRoomMessage(int _sock,std::string ip,uint16_t port,std::string message)
{//如果用户输入online,那么就把用户添加到在线列表if(message == "online") um.online(port,ip); //如果用户输入offline,那么把用户移除在线列表if(message == "offline") um.offline(port,ip);//用户在线才能广播消息if(um.isonline(port,ip))    um.broadcastMessage(message,_sock,ip,port); //广播消息
}int main(int argc , char* argv[])
{if(argc != 2) //命令行参数不为2就退出{std::cout << "Usage : " << argv[0] << "   bindport" << std::endl;  //打印使用手册exit(1);}uint16_t port = atoi(argv[1]); //命令行传的端口转换成16位整形std::unique_ptr<UdpServer> s(new UdpServer(port,ChatRoomMessage)); //创建UDP服务器s->init(); //初始化服务器,创建 + 绑定s->start(); //运行服务器
}

server.hpp代码:

这个类主要是对服务器的封装,在收到消息后通过用户传入的callback函数进行回调处理。


#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <functional>typedef std::function<void(int,std::string,uint16_t,std::string)> func_t;class UdpServer
{
private:int _sock; uint16_t _port;func_t _callback;
public:UdpServer(uint16_t port,func_t callback): _port(port) ,_callback(callback){ }~UdpServer() { close(_sock); }void init(){_sock = socket(AF_INET,SOCK_DGRAM,0);  //创建套接字if(_sock < 0){//创建失败std::cout << "create socket failed...." << std::endl;abort();}//绑定 struct sockaddr_in ser; ser.sin_port = htons(_port);  //填入端口ser.sin_family = AF_INET; // 填入域ser.sin_addr.s_addr = INADDR_ANY; //填入IP地址if(bind(_sock,(sockaddr*)&ser,sizeof ser) != 0) //绑定{//绑定失败std::cout << "bind socket failed...." << std::endl;abort();}}void start(){struct sockaddr_in peer; //对端socklen_t peer_len = sizeof peer;char buff[1024] = {0};   while(1){int n = recvfrom(_sock,buff,1023,0,(struct sockaddr*)&peer,&peer_len); buff[n] = 0;if(read == 0){std::cout << "one client quit..." << std::endl;continue;}else if(read < 0){std::cout << "read error..." << std::endl;break;}//获取客户端的端口和IPstd::string clientip = inet_ntoa(peer.sin_addr);uint16_t clientport = ntohs(peer.sin_port);std::cout << buff << std::endl; //回显客户端信息//调用回调函数处理数据_callback(_sock,clientip,clientport,buff);}} 
};

User.hpp代码:

这个头文件有2个类,User类是对用户的一层抽象,如果你用户还有其他的信息也可以加入到User类中。UserManage是对在线用户的管理,提供了增删查操作,以及消息广播。

#pragma once 
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class User
{
public:User(uint16_t port , const std::string& ip) :_port(port),_ip(ip){}std::string ip(){return _ip;}uint16_t port(){return _port;}
private:uint16_t _port;std::string _ip; 
};class UserManage
{
public://上线bool online(uint16_t port,const std::string& ip){std::string id = ip + "-" + std::to_string(port); auto it = _users.find(id); User u(port,ip);//如果不在上线列表中,加入到上线列表if(it == _users.end()) {_users.insert(std::make_pair(id,u));std::cout <<"[" <<  id << "  is online]" << std::endl;}else return false;return true;}//下线bool offline(uint16_t port,const std::string& ip){std::string id = ip + "-" + std::to_string(port);auto it = _users.find(id);if(it != _users.end()){_users.erase(id); //移除在线列表std::cout <<"[" <<  id << "  is offline]" << std::endl;return true;}//没找到该用户,下线错误return false;}//用户是否在线bool isonline(uint16_t port,const std::string& ip){std::string id = ip + "-" + std::to_string(port);auto it = _users.find(id);return it != _users.end();}//消息转发,把消息转发给用户列表的所有人void broadcastMessage(const std::string& message, int _sock,std::string ip,uint16_t port){for(auto& u : _users){//构建客户端sockaddr_inuint16_t u_port = u.second.port(); //要广播的客户端端口std::string u_ip = u.second.ip();  //要广播的客户端ipstruct sockaddr_in client; client.sin_family = AF_INET; client.sin_port = htons(u_port); client.sin_addr.s_addr = inet_addr(u_ip.c_str()); //这里的ip和port是发送消息人的端口和portstd::string response = ip + "-" + std::to_string(port) + " :" + message;//发送消息sendto(_sock,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof client);}}private:std::unordered_map<std::string,User> _users;  //记录在线用户
};

client客户端实现

客户端必须要保证至少2个线程,因为读消息和发送消息在一个线程里进行的话,会发送IO阻塞。除非你用多路转接=,=这里暂时不使用这种方法。

client.cc代码:

#include "client.hpp"
#include <memory>int main(int argc , char* argv[])
{if(argc != 3){std::cout << "Usage : " << argv[0] << "   serverip  serverport" << std::endl; exit(1);}uint16_t port = atoi(argv[2]); std::string ip = argv[1];std::unique_ptr<UdpClient> cli(new UdpClient(port,ip)); cli->init();cli->start();
}

client.hpp代码:

start函数负责处理用户发送的消息,RecvMessageThread函数是线程的执行函数,负责收服务器广播回来的消息,并把消息打印在公屏上,注意回显消息要用cerr打印!因为我们测试的时候会把cout重定向到一个命名管道中。

#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <arpa/inet.h>
#include <cstdio>
#include <cstring>
#include <pthread.h>class UdpClient
{
public:UdpClient(uint16_t port, const std::string &ip) : _port(port), _svr_ip(ip) {}~UdpClient() { close(_sock); }void init(){// 套接字创捷_sock = socket(AF_INET, SOCK_DGRAM, 0);if (_sock < 0){std::cout << "create socket failed...." << std::endl;abort();}}// 线程执行函数,负责接收消息static void *RecvMessageThread(void *args){while (1){int *sock = (int *)args; //提取sock套接字// 收服务器广播来的消息char recvbuff[1024 * 4] = {0};recvfrom(*sock, recvbuff, sizeof recvbuff - 1, 0, nullptr, nullptr);// 打印回收到的消息std::cout << recvbuff << std::endl;}return nullptr;}void start(){// 创建服务器的 sockaddr结构struct sockaddr_in svr;svr.sin_port = htons(_port);svr.sin_addr.s_addr = inet_addr(_svr_ip.c_str());svr.sin_family = AF_INET;// 发送消息的缓冲区char sendbuff[1024] = {0};// 创建一个线程来接收别人发送的信息pthread_t tid;pthread_create(&tid, nullptr, RecvMessageThread, (void *)&_sock);// 该线程负责发送消息while (1){// 输入消息std::cerr << "Enrty # ";fgets(sendbuff, sizeof sendbuff - 1, stdin);sendbuff[strlen(sendbuff) - 1] = 0;std::string message = sendbuff;// 发送消息sendto(_sock, message.c_str(), message.size(), 0, (struct sockaddr *)&svr, sizeof svr);}}private:int _sock;uint16_t _port;std::string _svr_ip;
};

测试代码:

首先我们启动服务器,绑定端口8080(这个绑定其他的也可以)。

在这里插入图片描述

随后启动一个客户端,创建一个管道,这里的管道就相当于聊天室中的公屏,而自己在命令行里输入的是自己的输入窗口。而不是输入栏和接收栏都用一个窗口,这样显得十分怪异,因为自己的消息会回显2次。

在这里插入图片描述

随后一个窗口启动客户端并把内容重定向到管道,一个窗口监视管道。

在这里插入图片描述

此时的客户端是没有在线的,我们输入online即可上线。

在这里插入图片描述

此时我们再创建一个客户端,进行同样的操作,但是暂时不要上线,看看没上线的客户端是否可以和上线的客户端通信。我们会发现没上线的客户端发消息,上线的客户端是看不见的。

在这里插入图片描述

我们让上线的客户端也发送消息,我们发现客户端2是无法收到的。

在这里插入图片描述

我们让客户端2输入online登录,随即两个客户端进行通信,而一旦客户端2下线后,客户端2的消息将无法被送达客户端1。

在这里插入图片描述

因为命令行,只有使用ctrl+退格键才能退格,而退格之后会产生乱码…这些都是小事情,和程序本身无关。

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

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

相关文章

服务器docker应用一览

文章目录 一、需求概况二、业务流程三、运行效果四、实现过程1. 基础前提2. 源码放送3.核心代码4. 项目打包5.部署步骤 一、需求概况 现有某云主机服务器&#xff0c;用来做项目演示用&#xff0c;上面运行了docker应用&#xff0c;现希望有一总览页面&#xff0c;用来展示部署…

HC-SR04(超声波模块)

工具 1.Proteus 8 仿真器 2.keil 5 编辑器 原理图 讲解 简介 HC-SR04超声波模块是一种常用的测距模块&#xff0c;通过不断检测超声波发射后遇到障碍物所反射的回波&#xff0c;从而测出发射和接收回波的时间差&#xff0c;并据此求出距离。它主要由两个压电陶瓷超声传感器…

Centos7查看内存使用情况

Centos7查看内存使用情况 free -b&#xff1a;以字节为单位显示内存使用情况。-k&#xff1a;以KB为单位显示内存使用情况&#xff08;默认选项&#xff09;。-m&#xff1a;以MB为单位显示内存使用情况。-g&#xff1a;以GB为单位显示内存使用情况。-t&#xff1a;在输出的最…

C++知识点总结(29):递归练习

一、满足条件的值 1. 审题 已知&#xff1a; S 1 2 4 7 11 16 … S12471116… S12471116… 递归求解刚好大于等于 5000 5000 5000 时 S S S 的值。 2. 参考答案 #include <iostream> using namespace std;// 定义递归函数&#xff0c;计算第x个数的值 int f(…

【Python】使用OPC UA创建数据服务器

目录 准备工作服务器设置创建或获取节点设置节点值启动服务器查看服务器客户端总结 在工业自动化和物联网&#xff08;IoT&#xff09;领域&#xff0c;OPC UA&#xff08;开放平台通信统一架构&#xff09;已经成为一种广泛采用的数据交换标准。它提供了一种安全、可靠且独立于…

Mixed-Query Transformer:统一的图像分割架构

Mixed-Query Transformer:统一的图像分割架构 摘要IntroductionRelated WorkMethodMQ-Former ArchitectureObject Query Strategies Mixed-Query Transformer: A Unified Image Segmentation Architectur 摘要 在现有的一体化图像分割模型中&#xff0c;要么在多个任务上采用统…

学浪已购买视频怎么下载到本地?

许多学习者在学浪购买了丰富的课程&#xff0c;然而&#xff0c;一些课程存在时间限制&#xff0c;使得学习者希望将其下载并永久保存。在这里&#xff0c;我们将介绍一款名为小浪助手的工具&#xff0c;它能够帮助你轻松将学浪已购买的视频下载到本地&#xff0c;让学习变得更…

Django处理枚举(枚举模型)以及source的使用

Django处理枚举-枚举模型 1、定义模型类、序列化器类2、对上面这些场景使用source参数3、支持连表查询4、自定义序列化输出方法5、案例5 1、定义模型类、序列化器类 定义模型类models.py&#xff1b;项目模型类、接口模型类、用例模型类 from django.db import modelsclass T…

图书管理系统!牛逼!

今天给大家分享一套基于SpringbootVue的图书管理系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文末自取) ​一、系统运行图&#xff08;设计报告和接口文档&#xff09; 1、登陆页面 2、后台页面 3、设计报告包含接口文档 二、系统搭建视频教程 源码…

12-LINUX--进程间的通信

进程间通信&#xff1a;采用IPC机制&#xff08;进程间的用户空间相互独立&#xff0c;内核空间共享&#xff09;&#xff0c;有管道&#xff0c;信号量&#xff0c;共享内存&#xff0c;消息队列&#xff0c;套接字。 一.管道 管道可以用来在两个进程之间传递数据&#xff0c…

终端工具命令行颜色配置(解决终端工具连上服务器之后,无颜色问题)

本期主题&#xff1a; 讲解使用mobaxterm等终端工具连上服务器&#xff0c;但是命令行没有颜色的问题 目录 1. 问题描述2. 原因解释3.测试 1. 问题描述 使用终端工具&#xff08;Mobaxterm等&#xff09;连上服务器之后&#xff0c;发现终端工具没有颜色&#xff0c;如下图&am…

Python学习从0到1 day25 第二阶段 SQL ② Python操作数据库

少年有梦&#xff0c;不应至于心动&#xff0c;更要付诸行动 —— 24.4.12 pymysql 除了使用图形化工具以外&#xff0c;我们也可以使用编程语言来执行SQL从而操作数据库 在Python中&#xff0c;使用第三方库&#xff1a;pymysql来完成对MySQl数据库的操作 安装 pip install py…

GPT4.5发布了?OpenAI终于发布正式版Turbo,重回AI王座第一

令人惊讶的是&#xff0c;短短三个月内&#xff0c;全球最强AI的称号又一次易主了&#xff01;几个月前&#xff0c;Claude3 Opus的性能全面超过了GPT-4&#xff0c;全球网友纷纷转向Claude3&#xff0c;并分享了他们对Claude3的惊艳体验。然而&#xff0c;OpenAI最近再次展示了…

探索GlusterFS:开源分布式文件系统

目录 引言 一、GlusterFS简介 &#xff08;一&#xff09;基本介绍 &#xff08;二&#xff09;GlusterFS特点 &#xff08;三&#xff09;GlusterFS术语 &#xff08;四&#xff09;GlusterFS工作流程 二、GlusterFs的卷类型 &#xff08;一&#xff09;卷类型 &…

【免安装的MATLAB--MATLAB online】

目录&#xff1a; 前言账号的注册图片处理的示例准备图片脚本函数 总结 前言 在计算机、数学等相关专业中&#xff0c;或多或少都会与MATLAB产生藕断丝连的联系&#xff0c;如果你需要使用MATLAB&#xff0c;但是又不想要安装到自己的电脑上&#xff08;它实在是太大了啊&#…

Linux 系统问题排查常用命令

立刻关机 haltcentos安装yum apt-get install yum查找文件夹 find / - name 需要查找文件名称vi里面 查找字符串 “/”&#xff0c;后面跟要查找的字符串&#xff0c;再按回车。vi将光标定位在该串下一次出现的地方上。键入n跳到该串的下一个出现处&#xff0c;键入N跳到该…

正则表达式 速成

正则表达式的作用 正则表达式&#xff0c;又称规则表达式,&#xff08;Regular Expression&#xff0c;在代码中常简写为regex、regexp或RE&#xff09;&#xff0c;是一种文本模式&#xff0c;包括普通字符&#xff08;例如&#xff0c;a 到 z 之间的字母&#xff09;和特殊字…

c语言中<string.h>的strstr与strtok函数

c语言中string.h的strstr与strtok函数 代码运行结果 代码 #include <stdio.h> #include <string.h>///1.在字符串str1里面,查找第一次出现str2的位置 //char * strstr(const char * str1,const char * str2)///2.sep为分割符,根据分割符来对str进行分割 //char * …

解决Django中的UnicodeDecodeError问题

在使用Django进行Web开发时&#xff0c;有时会遇到一些由于编码不一致引起的问题&#xff0c;特别是在处理文件读写操作时。一个常见的错误是UnicodeDecodeError&#xff0c;其表现为gbk codec cant decode byte 0xa6 in position 9737: illegal multibyte sequence。这个问题通…

【设计模式学习】单例模式和工厂模式

꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转…