【Socket 编程】基于UDP协议建立多人聊天室

思路

对于服务端来说,除了要接收消息之外,还要实现一个路由转发模块,该路由转发模块可以将相应发送给所有连接的客户端。而对于客户端来说,除了要发送消息给聊天室,还要能实时看到其它所有客户端的消息。
下面来看看具体实现的代码,代码只给出核心模块,其余代码模块代码可以借鉴我之前的博客。具体来说,服务器类实现的功能只有收消息。

路由功能实现

Route.hpp头文件

该头文件实现了一个路由类,该路由类维护了一张用户表和一把锁。该类向外提供转发消息功能的接口。为了实现并发转发消息,这里使用到了之前博客写的线程池,将转发功能的函数提供给线程池里的线程去处理。

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <cstring>
#include "InetAddr.hpp"
#include "ThreadPool.hpp"#include "LockGuard.hpp"
using namespace std;
using task_t = function<void()>;class Route
{
private:
public:Route(){pthread_mutex_init(&_mutex, nullptr);}void CheckOnlineUser(InetAddr &who){// 用户表是临界资源,需要加锁LockGuard lockguard(&_mutex);for (auto &user : _online_user){if (user == who){LOG(DEBUG, "%s is exists!\n", who.AddrStr().c_str());return;}}LOG(DEBUG, "%s is not exists,add it!\n", who.AddrStr().c_str());_online_user.push_back(who);}// 下线void Offline(InetAddr &who){LockGuard lockguard(&_mutex);vector<InetAddr>::iterator st = _online_user.begin();while (st != _online_user.end()){if (*st == who){_online_user.erase(st);LOG(DEBUG, "%s is offline\n", who.AddrStr().c_str());break;}st++;}}void ForwardHelper(int sockfd, const string &message, InetAddr who){LockGuard lockguard(&_mutex);string send_message = "[" + who.AddrStr() + "]# " + message;// 遍历在线用户表,转发给他们for (auto &user : _online_user){struct sockaddr_in peer = user.Addr();LOG(DEBUG, "Forward message to %s\n", user.AddrStr().c_str());sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&peer, sizeof(peer));}}// 转发消息给在线用户表void Forward(int sockfd, const string &message, InetAddr &who){// 查看该用户是否在线,不在线就添加到在线用户中CheckOnlineUser(who);// 如果用户退出if (message == "QUIT" || message == "quit"){// 下线Offline(who);return;}// 创建单例线程池帮助转发task_t t = bind(&Route::ForwardHelper, this, sockfd, message, who);ThreadPool<task_t>::GetInstance()->Push(t);}~Route(){pthread_mutex_destroy(&_mutex);}private:vector<InetAddr> _online_user; // 在线用户表pthread_mutex_t _mutex;
};

服务端

UdpServerMain.cpp

服务器的处理逻辑

#include "UdpServer.hpp"
#include <iostream>
#include <string>
#include <memory>
#include "Route.hpp"
using namespace std;int main(int argc, char *argv[])
{if (argc != 2){cerr << "Usage:" << argv[0] << " local-port" << endl;exit(0);}uint16_t port = stoi(argv[1]);EnableScreen();Route messageRoute;server_t message_route = bind(&Route::Forward,&messageRoute, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(message_route, port); // C++14的标准usvr->InitServer();usvr->Start();return 0;
}

UdpServer.hpp

实现一个服务器类

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include "LockGuard.hpp"
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include <functional>
using namespace log_ns;
using namespace std;
static const int gsockfd = -1;
static const uint16_t glocalport = 8888;
using server_t = function<void(int, const string &, InetAddr &)>;
enum
{SOCKET_ERROR = 1,BIND_ERROR
};class UdpServer : public nocopy
{
private:
public:UdpServer(server_t func, uint16_t localport = glocalport): _localport(localport), _isrunning(false), _sockfd(gsockfd), _fun(func){}void InitServer(){// 创建socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // IPv4协议和UDP协议的套接字if (_sockfd < 0){LOG(FATAL, "socket create error!\n");exit(1);}LOG(DEBUG, "socket create success,sockfd is %d\n", _sockfd);// 绑定端口号和IP// 绑定之前创建一个sockaddr_in对象,存储本地地址信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定local.sin_port = htons(_localport);int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "sockfd bind error!\n");exit(1);}LOG(DEBUG, "sockfd bind success!\n");}void Start(){_isrunning = true;char inbuff[1024];while (_isrunning){// 获取数据struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, inbuff, sizeof(inbuff) - 1, 0, (struct sockaddr *)&peer, &len);if (n < 0){LOG(FATAL, "recvfrom error!\n");}else{LOG(DEBUG, "recvfrom success!\n");InetAddr addr(peer);inbuff[n] = '\0';std::string message = inbuff;_fun(_sockfd, message, addr);}}_isrunning = false;}~UdpServer(){if (_sockfd >= 0)close(_sockfd);}private:int _sockfd;uint16_t _localport;bool _isrunning;server_t _fun;
};

客户端

UdpClientMain.cpp

该文件实现了客户端发送消息和接收消息的逻辑,由于发送消息和接收消息需要异步进行,所以可以考虑使用两个线程,分别查看聊天室的消息和向聊天室发送消息

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"
#include "InetAddr.hpp"
#include <memory>
#include <functional>
using namespace std;
using namespace ThreadMoudle;int InitClient()
{int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cerr << "create client failed" << endl;exit(1);}return sockfd;
}// 发送消息
void SendMessage(int sockfd, string serverip, uint16_t serverport, const string &name)
{// 客户端一般不用绑定套接字,操作系统会在第一次发送消息时自动绑定本机ip和一个随机的portstruct 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);while (true){string line;cout << name << " #: ";getline(cin, line);ssize_t res = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)&server, sizeof(server));if (res <= 0){break;}}
}// 接收消息
void RecvMessage(int sockfd, const string &name)
{while (true){struct sockaddr_in peer;char buff[1024];socklen_t len = 0;int n = recvfrom(sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;cout << buff << endl;}else{cerr << "recvfrom error\n"<< endl;break;}}
}int main(int argc, char *argv[])
{if (argc != 3){cerr << "Usage:" << argv[0] << " local-port" << endl;exit(0);}string ip = argv[1];uint16_t port = stoi(argv[2]);int sockfd = InitClient();// auto ref = std::bind(&RecvMessage, sockfd, placeholders::_1);//  auto senf = std::bind(&SendMessage, sockfd, ip, port, placeholders::_1, placeholders::_2, placeholders::_3);Thread recver("recver-thread", std::bind(&RecvMessage, sockfd, std::placeholders::_1));Thread sender("sender-thread", std::bind(&SendMessage, sockfd, ip, port, std::placeholders::_1));recver.Start();sender.Start();recver.Join();sender.Join();close(sockfd);return 0;
}

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

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

相关文章

鸿蒙笔记--动画

这一节主要了解一下鸿蒙的动画&#xff0c;动画的引入主要是为了提升用户体验、增加用户反馈和互动感、引导用户操作以及缓解等待带来的不适感。 属性动画: Index.ets Entry Component struct Index {StatewidthSize: number 100StateheightSize: number 40build() {Column…

C++函数( Lambda、inline 、多载、指标)第二部

Lambda 函数 Lambda 函数是C11 新增的函数形式&#xff0c;这是种匿名函数&#xff0c;也就是不需要函数识别字&#xff0c;简单举例如下 #include <iostream>int main() {auto f [](int i) {return i * i;};std::cout << f(11) << std::endl;std::cout &l…

sql常见50道查询练习题

sql常见50道查询练习题 1. 表创建1.1 表创建1.2 数据插入 2. 简单查询例题(3题&#xff09;2.1 查询"李"姓老师的数量2.2 查询男生、女生人数2.3 查询名字中含有"风"字的学生信息 3. 日期相关例题(6题&#xff09;3.1 查询各学生的年龄3.2 查询本周过生日的…

redis的学习(二):常见数据结构及其方法

简介 redis常见的数据结构和他们的常用方法 redis的数据结构 redis是一个key-value的nosql&#xff0c;key一般是字符串&#xff0c;value有很多的类型。 j基本类型&#xff1a; stringhashlistsetsortedSet 特殊类型&#xff1a; GEOBitMapHyperLog key的结构 可以使用…

MacOS M1 安装item2 并配置Zsh

文章目录 1 下载item22 美化item22.1 配置主题2.2 设置黑色的主题&#xff1a;2.3 配置显示状态栏 status bar 3 安装 Oh my zsh3.1 设置主题3.2 设置插件3.3 安装第三方插件1 下载仓库解压2 使用 git clone 一些常用插件以及其作用 参考 1 下载item2 MacOS自带终端&#xff0…

ontap simulator配置过程

一、下载模拟器 参考《Simulate_ONTAP_9-14-1_Installation_and_Setup_Guide.pdf》P4的指导&#xff0c;登录网站进行下载。 二、传入pve&#xff0c;并解压转换 # 解压 tar -xvf vsim-netapp-DOT9.14.1-cm_nodar.ova# 解压后的文件列表&#xff0c;其中ovf文件里定义了虚拟机…

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换 背景&#xff1a;uniapp做的微信小程序&#xff0c;使用到了相机拍照并获取位置坐标信息&#xff1b;在腾讯地图上展示坐标点位置信息&#xff1b; 由于业务需要我们的PC端用的不是腾讯地图&#xff0c;需要使用WGS84坐标或…

《0基础》学习Python——第二十三讲__网络爬虫/<6>爬取哔哩哔哩视频

一、在B站上爬取一段视频&#xff08;B站视频有音频和视频两个部分&#xff09; 1、获取URL 注意&#xff1a;很多平台都有反爬取的机制&#xff0c;B站也不例外 首先按下F12找到第一条复制URL 2、UA伪装&#xff0c;下列图片中&#xff08;注意代码书写格式&#xff09; 3、Co…

ViT(Vision Transformer)网络结构详解

本文在transformer的基础上对ViT进行讲解&#xff0c;transformer相关部分可以看我另一篇博客&#xff08;transformer中对于QKV的个人理解-CSDN博客&#xff09;。 一、网络结构概览 上图展示了Vision Transformer (ViT) 的基本架构&#xff0c;我按照运行顺序分为三个板块进…

Rancher

文章目录 Rancher1. 安装和配置2. 服务部署和管理3. 容器自动化缩容和扩容 Rancher Rancher 是一个开源的企业级容器管理平台&#xff0c;旨在简化容器化应用的部署、管理和运维。它支持多种容器编排引擎&#xff0c;如 Kubernetes、Docker Swarm 等&#xff0c;并提供了统一的…

自动驾驶系统开发与调试:车路云一体化无人驾驶挑战赛参赛体验

点击蓝字 关注我们 在过去的几年里&#xff0c;自动驾驶技术在全球范围内吸引了大量关注。其潜力不仅在于提升行车安全&#xff0c;而且还可以改变我们的出行方式和城市规划&#xff0c;提高交通运输效率。国际汽车工程师学会&#xff08;SAE&#xff09;根据不同自动驾驶程度&…

MySQL学习之事务,锁机制

事务 什么是事务&#xff1f; 事务就是逻辑上的一组操作&#xff0c;要么全做&#xff0c;要么全不做 事务经典例子&#xff1a;转账&#xff0c;转账需要两个操作&#xff0c;从一个人账户上减钱&#xff0c;在另一个账户上加钱&#xff0c;比如说小红给小明转账100元&…

JAVA在线文档

1.存在码 JDK21中文API 2.全栈行动派 JDK17中文API 3.mklab.cn JDK11中文API JDK8中文API JDK7-21英文API 4.docs.oracle.com JDK7-22英文文档

项目笔记| 基于Arduino和IR2101的无刷直流电机控制器

本文介绍如何使用 Arduino UNO 板构建无传感器无刷直流 &#xff08;BLDC&#xff09; 电机控制器或简单的 ESC&#xff08;电子速度控制器&#xff09;。 无刷直流电机有两种类型&#xff1a;有传感器和无传感器。有感无刷直流电机内置3个霍尔效应传感器&#xff0c;这些传感…

MLIR的TOY教程学习笔记

MLIR TOY Language 文章目录 MLIR TOY Language如何编译该项目ch1: MLIR 前端IR解析ch2: 定义方言和算子 (ODS)1. 定义方言2. 定义OP3. OP相关操作4. 定义OP ODS (Operation Definition Specification)1. 基本定义2. 添加文档3. 验证OP4. 新增构造函数5. 定义打印OP的格式 ch3:…

【机器学习】超参数选择:解锁机器学习模型潜力的关键

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 超参数选择&#xff1a;解锁机器学习模型潜力的关键引言什么是超参数&#xff1…

计算机的错误计算(三十八)

摘要 计算机的错误计算&#xff08;十九&#xff09;指出&#xff1a;两个等价大数相减&#xff0c;差不是正确值0&#xff0c;而是一个大数。本节用 Python的 torch库中函数进行计算验证&#xff0c;进一步说明错误的一般性。 例1. 在Windows10&#xff0c;Python 3.12.4 下…

Android APP Camerax应用(02)预览流程

说明&#xff1a;camera子系统 系列文章针对Android12.0系统&#xff0c;主要针对 camerax API框架进行解读。 1 CameraX简介 1.1 CameraX 预览流程简要解读 CameraX 是 Android 上的一个 Jetpack 支持库&#xff0c;它提供了一套统一的 API 来处理相机功能&#xff0c;无论 …

【HarmonyOS NEXT】网络请求 - 分页加载

分页加载关键字&#xff1a;onReachEnd 一、申请网络权限 在 module.json5 文件中&#xff0c;添加网络权限&#xff1a; {"module": {..."requestPermissions": [{"name": "ohos.permission.INTERNET","usedScene": {&qu…

网络安全常用易混术语定义与解读(Top 20)

没有网络安全就没有国家安全&#xff0c;网络安全已成为每个人都重视的话题。随着技术的飞速发展&#xff0c;各种网络攻击手段层出不穷&#xff0c;保护个人和企业的信息安全显得尤为重要。然而&#xff0c;在这个复杂的领域中&#xff0c;许多专业术语往往让人感到困惑。为了…