【计算机网络】UDP服务器实现网络聊天室

news2024/12/23 3:56:47

前言

上一篇文章我们简单了解了一下什么是套接字编程,这篇文章我们利用UDP套接字来实现一个简单的网络聊天室。

编写UDP套接字服务器

成员变量

// 1. socket的id,相当于文件id
int _sock;
// 2. port
uint16_t _port;

// 3 一个线程负责收放消息;另一个线程负责发消息
Thread *c; // 收消息
Thread *p; // 发消息

// 4 环形队列来实现生产消费
RingQueue<std::string> rq;

// 5 信息要群发给所有人,要记录所有人的ip和端口号
std::unordered_map<std::string, sockaddr_in> _users;

// 6 一把锁保证在读取要发送给哪些人时是安全的
//  在读取_users时,另一个线程收到了消息,会修改这个map
pthread_mutex_t _mmtx;

成员函数

  • 构造函数:

今天我们知道,对于服务器而言,需要指定端口号。我们在初始化服务器的时候,对锁和线程都进行初始化。

UdpServer(uint16_t port = default_port) : _port(port)
{
    pthread_mutex_init(&_mmtx,nullptr);

    p = new Thread(1,std::bind(&UdpServer::Recv,this));
    c = new Thread(1,std::bind(&UdpServer::BroadCast,this));
}

  • 析构函数

析构函数完成资源释放,不要忘记等待进程。

~UdpServer()
{
    pthread_mutex_destroy(&_mmtx);

    p->join();
    c->join();

    delete p;
    delete c;
}

  • Start
void start()
{
    // 1 创建socket接口,打开网络文件
    _sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (_sock < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(SOCKED_ERR);
    }
    std::cout << "create socket success" << std::endl;

    // 2 给服务器指明IP地址和Port
    struct sockaddr_in local;
    memset(&local, sizeof(local), 0);

    local.sin_family = AF_INET;
    local.sin_port = htons(_port);      // host to network short
    local.sin_addr.s_addr = INADDR_ANY; // bind本主机任意ip
    if (bind(_sock, (sockaddr *)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind socket error" << std::endl;
        exit(BIND_ERR);
    }
    std::cout << "bind socket success" << std::endl;

    c->run();
    p->run();
}

  • addUser

因为UDP不面向连接,要构建一个聊天室,就必须要记录下曾经所有给服务器发送过消息的主机,这样才能转发信息。因为是向map里插入数据,本身是线程不安全的,需要加锁。

void addUser(std::string &name, sockaddr_in &peer)
{
    LockGuard lg(&_mmtx);
    _users[name] = peer;
}

  • recv
    接口细节我们已经在上一节着重说过,这里不做赘述。
void Recv()
{
    char buffer[1024];
    // 网络服务都是循环!
    while (true)
    {
        // recv里有两个输出型参数
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int n = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);
        if (n > 0)
        {
            buffer[n] = 0;
        }
        else
        {
            continue;
        }

        // 发消息人的ip和port
        std::string client_ip = inet_ntoa(peer.sin_addr); // net->ascii
        uint16_t client_port = ntohs(peer.sin_port);

        // 构造了发消息人的姓名
        std::string name = client_ip + "-" + to_string(client_port);

        addUser(name, peer);
        rq.push(name + buffer);
    }
}

  • broadcast
    这里我们用了一个临时数组来存储要广播发送的主机,再向这个数组内的所有主机发送队列里的消息。那这样做有什么好处呢?

先把用户拷贝走,再让这个线程去独立发送数据,这样可以让使用map的其他线程更快得到锁,因为拷贝的过程要比发送数据快得多。

void BroadCast()
{
    while (true)
    {
        std::string send_string;
        rq.pop(&send_string);

        std::vector<sockaddr_in> v; // 临时数组,存放要发给那些人

        {
            LockGuard lg(&_mmtx);
            for (auto user : _users)
            {
                v.push_back(user.second);
            }
        }

        for (auto user : v)
        {
            sendto(_sock, send_string.c_str(), send_string.size(), 0, (sockaddr *)&user, sizeof(user));
        }
    }
}

编写UDP客户端

客户端的编写和服务器端编写大同小异。有一个地方需要我们注意一下,套接字编程需要绑定ip和端口号,我们在服务端也确实这样做了,那么客户端需不需要bind端口号和ip呢

答案是肯定需要,只是这个工作不是由我们来干了,而是操作系统替我们做了,在第一次发送的时候自动绑定ip和端口号。

原因也是很好想的,对于客户端来说,端口号都是随机的,自然应该让操作系统帮忙。

void *recv(void *args)
{
    int sock = *(static_cast<int *>(args));
    char buffer[1024];
    sockaddr_in temp;
    socklen_t len = sizeof(temp);

    while (true)
    {
        int n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&temp, &len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "请输入程序名 + 对方ip + 对方端口号" << std::endl;
        exit(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(SOCKED_ERR);
    }
    // 系统在我们第一次调用系统调用发送的时候,会自动给我绑定ip和端口号

    // 填充server的信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));

    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    //
    pthread_t r_id;
    pthread_create(&r_id, nullptr, recv, (void*)&sock);

    while(true)
    {
        std::string message;
        std::cout << "请输入..." << std::endl;
        getline(std::cin,message);

        sendto(sock,message.c_str(),message.size(),0,(sockaddr*)&server,sizeof(server));
    }

    pthread_join(r_id,nullptr);

    return 0;
}

效果测试

这样一个简单的Udp服务器就完成了。
在这里插入图片描述

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

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

相关文章

JUC并发、JVM相关

文章目录 JUC并发synchronized锁对象底层原理 synchronized锁升级reentrantlock公平锁和非公平锁可重入锁 / 递归锁 死锁死锁产生条件如何排查死锁?如果解决死锁&#xff1f; LockSupport与中断机制中断机制中断相关的三大API如何中断运行中的线程&#xff1f; LockSupportLoc…

【C++】C++11--- 线程库及详解lock_guard与unique_lock

目录 一、thread类的介绍二、线程函数参数三、 原子性操作库四、lock_guard与unique_lock4.1、mutex的种类4.2 lock_guard4.3 unique_lock 一、thread类的介绍 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如**windows和linux下各有自己…

【css】属性选择器

有些场景中需要在相同元素中获取具有特定属性的元素&#xff0c;比如同为input&#xff0c;type属性有text、button&#xff0c;可以通过属性选择器设置text和button的不同样式。 代码&#xff1a; <style> input[typetext] {width: 150px;display: block;margin-bottom…

自动配置要点解读

目录 要点1&#xff1a;什么是自动配置&#xff1f; 要点2&#xff1a;配置文件与默认配置 要点3&#xff1a;自动配置设置思想来源 要点4&#xff1a;spring.factories文件作用 要点5&#xff1a;自动配置的核心 本文只对自动配置的思想进行基本的解读&#xff0c;不涉…

21、p6spy输出执行SQL日志

文章目录 1、背景2、简介3、接入3.1、 引入依赖3.2、修改database参数&#xff1a;3.3、 创建P6SpyLogger类&#xff0c;自定义日志格式3.4、添加spy.properties3.5、 输出样例 4、补充4.1、参数说明 1、背景 在开发的过程中&#xff0c;总希望方法执行完了可以看到完整是sql语…

通用人工智能操作系统

随着科技的飞速发展&#xff0c;人工智能已经成为了当今世界最热门的技术领域之一。从智能手机、自动驾驶汽车到智能家居系统&#xff0c;人工智能技术已经渗透到了我们生活的方方面面。然而&#xff0c;尽管人工智能在很多领域取得了显著的成果&#xff0c;但它仍然存在一些局…

matplotlib+tkinter实现一个简单的绘图系统

文章目录 封装成类布局实现绘图功能 绘图系统系列&#xff1a;将matplotlib嵌入到tkinter 封装成类 在理解matplotlib嵌入到tkinter中的原理之后&#xff0c;就已经具备了打造绘图系统的技术基础&#xff0c;接下来要做的&#xff0c;就是做一个较有可读性的绘图类&#xff0…

Java异常体系总结(下篇)

目录 1. 异常处理的三种方法 1.1 JVM 默认处理异常 1.2 通过 try...catch...自己处理异常 1.3 使用 throws和throw 抛出异常 1.3.1 使用 throws 抛出异常 1.3.2 使用 throw 抛出异常 2. try...catch.. 捕获到异常之后代码的执行顺序&#xff1f; 3. try...catch... 相关…

Mysql进阶(中) -- 索引

索引上部分 -> Mysql进阶(上) -- 存储引擎&#xff0c;索引_千帐灯无此声的博客-CSDN博客 &#x1f442; 爸爸妈妈 - 王蓉 - 单曲 - 网易云音乐 &#x1f448;目录看左栏 目录 &#x1f33c;索引 &#x1f43b;性能分析 - show profiles &#x1f43b;性能分析 - exp…

Cocos 适配 HarmonyOS NEXT,亮相 HDC2023,携手华为共筑鸿蒙生态!

HDC 2023 8月4-6日&#xff0c;作为华为合作伙伴&#xff0c;Cocos 引擎应邀参加了华为开发者大会 2023 - HDC 2023 暨 HarmonyOS 4 发布会&#xff0c;并获得了【鸿蒙生态能力共创奖】。 8月5日&#xff0c;在华为开发者大会&#xff08;HDC.Together&#xff09;游戏服务论坛…

SpringBoot系列---【使用jasypt把配置文件密码加密】

使用jasypt把配置文件密码加密 1.引入pom坐标 <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.5</version> </dependency> 2.新增jasypt配置 2.1…

HCIP-linux知识

linux安装教程参考&#xff0c;https://blog.51cto.com/cloudcs/5245337 yum源配置 本地yum源配置&#xff1a; 8版本配置&#xff1a;将光盘iso挂载到某个目录&#xff0c;/dev/cdrom是/dev/sr0软链接&#xff0c;# mount /dev/cdrom /mnt&#xff0c;# ls /mnt AppStream B…

Elastic:linux设置elasticsearch、kibana开机自启

0. 引言 每次启动服务器都要手动启动es服务&#xff0c;相当之不方便&#xff0c;为此&#xff0c;书写一个脚本&#xff0c;实现es、kibana的开机自启 1. 原理 首先任何服务要实现开机自启&#xff0c;都可分为如下三步&#xff1a; 1、在/etc/init.d目录下创建启动、关闭服…

跳表与Redis

跳表原理 跳表是Redis有序集合ZSet底层的数据结构 首先有一个头结点 这个头结点里面的数据是null 就是他就是这个链表的最小值 就算是Math.Min也比它大 然后我们新建一个节点的时候是怎么操作的呢 先根据参数(假如说是5)创建一个节点 然后把它放在对应位置 就是找到小于他的最…

(JS逆向专栏十一)某融平台网站登入RSA

声明: 本文章中所有内容仅供学习交流&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 名称:点融 目标:登入参数 加密类型:RSA 目标网址:https://www.dianrong.com/accoun…

java: 无法访问org.springframework.web.bind.annotation.GetMapping(springboot构建时出现问题)

spring boot构建完成后出现以下问题 报错原因&#xff1a;SpringBoot 3.0以上版本要求JDK 17以上&#xff0c;jdk版本1.8 与 spring boot 3.0.1 版本不匹配 解决方法&#xff1a;

ORA-48913: Writing into trace file failed, file size limit [50000000] reached

检查某环境的alert_orcl1.log时&#xff0c;发现有很多的ORA-48913报错&#xff0c;细节如下 Sat Jul 22 19:34:04 2023 Non critical error ORA-48913 caught while writing to trace file "/u01/app/oracle/diag/rdbms/orcl/orcl1/trace/orcl1_dw00_138010.trc" E…

Python 中的机器学习简介:多项式回归

一、说明 多项式回归可以识别自变量和因变量之间的非线性关系。本文是关于回归、梯度下降和 MSE 系列文章的第三篇。前面的文章介绍了简单线性回归、回归的正态方程和多元线性回归。 二、多项式回归 多项式回归用于最适合曲线拟合的复杂数据。它可以被视为多元线性回归的子集。…

BenchmarkSQL 支持 TiDB 驱动以及 tidb-loadbalance

作者&#xff1a; GangShen 原文来源&#xff1a; https://tidb.net/blog/3c274180 使用 BenchmarkSQL 对 TiDB 进行 TPC-C 测试 众所周知 TiDB 是一个兼容 MySQL 协议的分布式关系型数据库&#xff0c;用户可以使用 MySQL 的驱动以及连接方式连接 TiDB 进行使用&#xff0…

Butterfly安装文档(三)主题配置-1

语言 修改站点配置文件 _config.yml 默认语言是 en 主题支持三种语言 default(en)zh-CN (简体中文)zh-TW (繁体中文) 网站资料 修改网站各种资料&#xff0c;例如标题、副标题和邮箱等个人资料&#xff0c;请修改博客根目录的_config.yml 导航栏设置 (Navigation bar set…