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.rhkb.cn/news/304257.html

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

相关文章

【软件测试】个人博客系统测试

个人博客系统测试 一、项目背景1.1 技术背景1.2 功能背景 二、自动化测试2.1 什么是自动化测试2.2 通过使用selenium进行自动化测试的编写&#xff08;Java实现&#xff09;2.3 编写测试用例&#xff0c;执行自动化测试2.3.1 输入用户名:test,密码:123&#xff0c;登录成功2.3.…

云服务器上Docker启动的MySQL会自动删除数据库的问题

一、问题说明 除了常见的情况&#xff0c;例如没有实现数据挂载&#xff0c;导致数据丢失外&#xff0c;还需要考虑数据库是否被攻击&#xff0c;下图 REVOVER_YOUR_DATA 就代表被勒索了&#xff0c;这种情况通常是数据库端口使用了默认端口&#xff08;3306&#xff09;且密码…

jmeter实验 模拟:从CSV数据到加密请求到解密返回数据再到跨越线程组访问解密后的数据

注意,本实验所说的加密只是模拟加密解密,您需要届时写自己的加解密算法或者引用含有加密算法的相关jar包才行. 思路: 线程组1: 1.从CSV文件读取原始数据 2.将读取到的数据用BeanShell预习处理器进行加密 3.HTTP提取器使用加密后的数据发起请求 4.使用BeanShell后置处理器…

vite+react+ts+scss 创建项目

npm create vitelatest输入项目名称选择react选择typescript swc WC 通过利用 Rust 编写的编译器&#xff0c;使用了更先进的优化技术&#xff0c;使得它在处理 TypeScript 代码时能够更快地进行转换和编译。特别是在大型项目中&#xff0c;SWC 相对于传统的 TypeScript 编译器…

使用QT 开发不规则窗体

使用QT 开发不规则窗体 不规则窗体贴图法的不规则窗体创建UI模板创建一个父类创建业务窗体main函数直接调用user_dialog创建QSS文件 完整的QT工程 不规则窗体 QT中开发不规则窗体有两种方法&#xff1a;&#xff08;1&#xff09;第一种方法&#xff0c;使用QWidget::setMask函…

跨域问题一文解决

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳中求进&#xff0c;晒太阳 一、为什么会出现跨域的问题&#xff1f; 是浏览器的同源策略&#xff0c;跨域也是因为浏览器这个机制引起的&#xff0c;这个机制的存在还是在于安全…

FFmpeg: 简易ijkplayer播放器实现--01项目简介

文章目录 项目介绍流程图播放器实现过程界面展示 项目介绍 此项目基于FFmeg中 ffplay.c进行二次开发&#xff0c;实现基本的功能&#xff0c;开发软件为Qt 项目优势&#xff1a; 参考ijkplayer播放器&#xff0c;实现UI界面和播放器核心进行解耦&#xff0c;容易添加其他功能…

机器学习和深度学习 -- 李宏毅(笔记与个人理解1-6)

机器学习和深度学习教程 – 李宏毅&#xff08;笔记与个人理解&#xff09; day1 课程内容 什么是机器学习 找函数关键技术&#xff08;深度学习&#xff09; 函数 – 类神经网络来表示 &#xff1b;输入输出可以是 向量或者矩阵等如何找到函数&#xff1a; supervised Lear…

SSRF靶场

SSRF概述 ​ 强制服务器发送一个攻击者的请求 ​ 互联网上的很多web应用提供了从其他服务器&#xff08;也可以是本地)获取数据的功能。使用用户指定的URL&#xff0c;web应用可以获取图片&#xff08;载入图片&#xff09;、文件资源&#xff08;下载或读取)。如下图所示&…

C语言面试题之返回倒数第 k 个节点

返回倒数第 k 个节点 实例要求 1、实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点&#xff1b;2、返回该节点的值&#xff1b; 示例&#xff1a;输入&#xff1a; 1->2->3->4->5 和 k 2 输出&#xff1a; 4 说明&#xff1a;给定的 k 保证是有效的。实…

uniapp开发h5端使用video播放mp4格式视频黑屏,但有音频播放解决方案

mp4格式视频有一些谷歌播放视频黑屏&#xff0c;搜狗浏览器可以正常播放 可能和视频的编码格式有关&#xff0c;谷歌只支持h.264编码格式的视频播放 将mp4编码格式修改为h.264即可 转换方法&#xff1a; 如果是自己手动上传文件可以手动转换 如果是后端接口调取的地址就需…

效果炸裂!文生图再升级,支持多对象个性化图片生成!开源!

大家好&#xff0c;今天和大家分享一篇最新的文生图工作&#xff0c;代码已开源 标题&#xff1a;Identity Decoupling for Multi-Subject Personalization of Text-to-Image Models 单位&#xff1a;KAIST 论文&#xff1a;https://arxiv.org/pdf/2404.04243.pdf 代码&#xf…

【linux】yum 和 vim

yum 和 vim 1. Linux 软件包管理器 yum1.1 什么是软件包1.2 查看软件包1.3 如何安装软件1.4 如何卸载软件1.5 关于 rzsz 2. Linux编辑器-vim使用2.1 vim的基本概念2.2 vim的基本操作2.3 vim命令模式命令集2.4 vim底行模式命令集2.5 vim操作总结补充&#xff1a;vim下批量化注释…

贪心算法简介

目录 一、什么是贪心算法&#xff1f; 二、贪心算法的特点 三、贪心算法解决找零问题、最短路径问题、背包问题 1.找零问题 2.最短路径问题 3.背包问题 一、什么是贪心算法&#xff1f; 贪心算法就是希望通过局部最优来解决全局最优 基本步骤&#xff1a;1.将问题分为若…

SAM功能改进VRP-SAM论文解读VRP-SAM: SAM with Visual Reference Prompt

现已总结SAM多方面相关的论文解读&#xff0c;具体请参考该专栏的置顶目录篇 一、总结 1. 简介 发表时间&#xff1a;2024年3月30日 论文&#xff1a; 2402.17726.pdf (arxiv.org)https://arxiv.org/pdf/2402.17726.pdf代码&#xff1a; syp2ysy/VRP-SAM (github.com)htt…

acwing总结-线性质数筛

质数筛 题目链接:质数筛线性筛法 ac代码&#xff1a; #include<iostream> #include<algorithm> //https://www.bilibili.com/video/BV1LR4y1Z7pm/?spm_id_from333.337.search-card.all.click&vd_source436ccbb3a8f50110aa75654f38e35672 //链接到b站视频 us…

【Vuforia+Unity】AR判断当前平台获取点击/触摸坐标点选中识别的二维码跳转网页

实现了&#xff1a;【VuforiaUnity】判断当前平台获取点击/触摸坐标点选中识别的二维码跳转网页 using UnityEngine; using Vuforia; public class BarcodeScanner : MonoBehaviour { public TMPro.TextMeshProUGUI barcodeAsText; string platformNum""; privat…

Java零基础入门-Scanner类

一、概述 对于上几期&#xff0c;我们有学完java的反射机制及java正则&#xff0c;有些小伙伴私下给我说&#xff0c;教的有点太难理解了&#xff0c;不是很能消化&#xff0c;说教学内容学习压力有点大&#xff0c;我仔细去看了看&#xff0c;确实是我教学先后顺序的问题&…

使用自己的数据基于SWIFT微调Qwen-Audio-Chat模型

目录 使用自己的数据训练参数设置自己的数据准备语音转写任务语音分类任务 开始训练不同训练方法mpddpmp ddpdeepspeed 训练实例训练详情Qwen-Audio-Chat模型 模型数据实例官方可用的数据由内部函数处理为指定格式 训练好的模型测试 使用自己的数据 官方参考文档&#xff1a;…

微服务-网关

在微服务架构中&#xff0c;每个服务都是一个可以独立开发和运行的组件&#xff0c;而一个完整的微服务架构由一系列独立运行的微服务组成。其中每个服务都只会完成特定领域的功能&#xff0c;比如订单服务提供与订单业务场景有关的功能、商品服务提供商品展示功能等。各个微服…