Linux —— udp实现群聊代码

news2024/11/17 10:33:55

一、介绍

前面我们一步步模拟实现了一个简单的udp服务器和客户端,通过这个服务器,我们简单实现一个群聊的功能,本篇是专门用来记录代码的,详细的实现思路可以去参考我其他两篇,Socket编程(一)和Socket编程(二)

二、代码

udp_server.cc

#include"udp_server.hpp"
#include<string>
#include<memory>
#include<cstdio>
#include<ctype.h>

using namespace std;
using namespace chk;



// 我们最终希望以 ./udp_server port 的形式去启动
static void usage(string proc)//使用手册
{
    std::cout << "Usage:\n\t" << proc << " port\n" << std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<UdpServer> usvr(new UdpServer(port));
    usvr->start();

    return 0;
}

udp_server.hpp

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include "errno.hpp"
#include <cstring>
#include <functional>
#include <unordered_map>
#include "RingQueue.hpp"
#include "Thread.hpp"
#include <vector>
#include "mylock.hpp"

namespace chk
{
    const static uint16_t default_port = 8080;
    using func_t = std::function<std::string(std::string)>;//定义函数指针

    class UdpServer
    {
    public:
        // 对成员变量完成初始化
        UdpServer(uint16_t port = default_port) : _port(port)
        {
            std::cout << "server port: " << _port << std::endl;
            pthread_mutex_init(&lock,nullptr);
            p = new Thread(1,std::bind(&UdpServer::Rev,this));
            c = new Thread(2,std::bind(&UdpServer::Broadcast,this));
        }

        void start() // 创建出套接字,并绑定端口号和ip
        {
            // 1. 创建套接字
            _sock = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sock < 0)
            {
                std::cerr << "create socket error: " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "bind socket success: " << _sock << std::endl; // 3

            // 2. 构建struct sockaddr_in结构体
            struct sockaddr_in local;
            bzero(&local, sizeof(local));       // 初始化
            local.sin_family = AF_INET;         // IPv4
            local.sin_port = htons(_port);      // 端口号
            local.sin_addr.s_addr = INADDR_ANY; // 服务器下的ip

            // 3. 绑定套接字和sockaddr_in
            int n = bind(_sock, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                std::cerr << "bind error: " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cout << "bind socket success: " << _sock << std::endl;

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

        void AddUser(const std::string &name, const struct sockaddr_in &peer)
        {
            LockGuard lockgurad(&lock);
            auto iter = onlineuser.find(name);
            if(iter != onlineuser.end())
                return;//找到了则不需要作为新用户添加
            
            onlineuser.insert(std::pair<const std::string,const struct sockaddr_in>(name,peer));
        }

        void Rev() // 接受信息,并且创建和添加在线用户信息
        {
            char buffer[1024];
            while(true)
            {
                //1. 收数据
                struct sockaddr_in peer; // 客户端信息
                socklen_t len = sizeof(peer);
                int n = recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                if(n>0) buffer[n] = '\0';
                else continue;

                //2. 收到信息后打印出来:对方ip+端口号+内容
                std::cout << inet_ntoa(peer.sin_addr) << " - " << ntohs(peer.sin_port) << " : " << buffer << std::endl;

                //3. 对用户的个人信息构建和添加到在线列表中
                std::string name = inet_ntoa(peer.sin_addr);
                name += " - ";
                name += std::to_string(ntohs(peer.sin_port));
                AddUser(name,peer);

                std::string msg = name + " >> " + buffer;
                rq.push(msg);

                //sendto(_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
            }
        }
        void Broadcast()
        {
            while(true)
            {
                std::string msg;
                rq.pop(&msg);

                std::vector<struct sockaddr_in> v;//每次广播先将要发送的用户列表进行导出
                {
                    LockGuard lockgurad(&lock);
                    for(auto user: onlineuser)
                    {
                        v.push_back(user.second);
                    }
                }
                for(auto user:v) // 将信息广播
                {
                    sendto(_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&user,sizeof(user));
                }
            }
        }

        ~UdpServer() // 析构
        {
            pthread_mutex_destroy(&lock);
            c->join();
            p->join();

            delete c;
            delete p;
        }

    private:
        int _sock;
        uint16_t _port;
        func_t _service; // 信息处理方法
        std::unordered_map<std::string,struct sockaddr_in> onlineuser;//在线用户信息
        RingQueue<std::string> rq;//消息队列
        pthread_mutex_t lock; // 保护用户在线列表的互斥锁

        Thread* c;
        Thread* p;
    };
}

udp_cilent.hpp

#pragma once

#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<cstring>
#include<string>
#include"errno.hpp"

udp_cilent.cc

#include"udp_client.hpp"//基本要用到的头文件
using namespace std;
#include<pthread.h>

static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl; 
}

void* recver(void *args)
{
    int sock = *(static_cast<int*>(args));
    while(true)
    {
        // 接受返回的信息
        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int n = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer << endl;
        }
    }
}


// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0)
    {
        cerr << "client : create socket error" << endl;
        exit(SOCKET_ERR);
    }

    //2. 创建server端的struct sockaddr
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));//初始化方案2
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    server.sin_port = htons(server_port);

    //3. 客户端测试
    // 这里需要多线程进行,一个线程发消息,一个线程进行实时的收广播消息
    pthread_t tid;
    pthread_create(&tid,nullptr,recver,&sock);

    while(true)
    {
        // 用户发送消息
        string messages;
        cerr << "client : " ;
        getline(cin,messages);
        // 发送到sock
        sendto(sock,messages.c_str(),messages.size(),0,(struct sockaddr*)&server,sizeof(server));
    }
    return 0;
}#include"udp_client.hpp"//基本要用到的头文件
using namespace std;
#include<pthread.h>

static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl; 
}

void* recver(void *args)
{
    int sock = *(static_cast<int*>(args));
    while(true)
    {
        // 接受返回的信息
        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int n = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer << endl;
        }
    }
}


// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0)
    {
        cerr << "client : create socket error" << endl;
        exit(SOCKET_ERR);
    }

    //2. 创建server端的struct sockaddr
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));//初始化方案2
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    server.sin_port = htons(server_port);

    //3. 客户端测试
    // 这里需要多线程进行,一个线程发消息,一个线程进行实时的收广播消息
    pthread_t tid;
    pthread_create(&tid,nullptr,recver,&sock);

    while(true)
    {
        // 用户发送消息
        string messages;
        cerr << "client : " ;
        getline(cin,messages);
        // 发送到sock
        sendto(sock,messages.c_str(),messages.size(),0,(struct sockaddr*)&server,sizeof(server));
    }
    return 0;
}

errno.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

mylock.hpp

#pragma once

#include<pthread.h>
#include<iostream>


class Mutex
{
public:
    Mutex(pthread_mutex_t* pm):_pm(pm)
    {}

    void lock()
    {
        pthread_mutex_lock(_pm);
    }
    void unlock()
    {
        pthread_mutex_unlock(_pm);
    }

    ~Mutex()
    {}
private:
    pthread_mutex_t* _pm;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* pm):_mutex(pm)
    {
        _mutex.lock();
    }
    ~LockGuard()
    {
        _mutex.unlock();
    }

private:
    Mutex _mutex;
};

RingQueue.hpp

#pragma once

#include<iostream>

#include<vector>
#include<semaphore.h>
#include<pthread.h>

static int N = 5;

template<class T>
class RingQueue
{
private:
    //封装一下PV操作
    void P(sem_t& sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t& sem)
    {
        sem_post(&sem);
    }
    void Lock(pthread_mutex_t& m)
    {
        pthread_mutex_lock(&m);
    }
    void Unlock(pthread_mutex_t& m)
    {
        pthread_mutex_unlock(&m);
    }

public:
    RingQueue(int num = N):_cap(num),_ring(num)
    {
        sem_init(&_sem_consumer,0,0);
        sem_init(&_sem_producer,0,num);
        pthread_mutex_init(&_mutex_consumer,nullptr);
        pthread_mutex_init(&_mutex_producer,nullptr);
        _p_step = _c_step = 0;
    }
    void push(const T& in)
    {
        //生产
        P(_sem_producer);
        //先申请再锁,能够更好的提高效率
        Lock(_mutex_producer);
        //一定有对应的空间资源给我
        _ring[_p_step++] = in;
        _p_step %= _cap;
        V(_sem_consumer);
        Unlock(_mutex_producer);
    }
    void pop(T* out)
    {
        //消费
        P(_sem_consumer);
        Lock(_mutex_consumer);
        *out = _ring[_c_step++];
        _c_step %= _cap;
        V(_sem_producer);
        Unlock(_mutex_consumer);
    }

    ~RingQueue()
    {
        sem_destroy(&_sem_consumer);
        sem_destroy(&_sem_producer);
        pthread_mutex_destroy(&_mutex_consumer);
        pthread_mutex_destroy(&_mutex_producer);
    }

private:
    std::vector<T> _ring;
    int _cap;
    sem_t _sem_consumer;
    sem_t _sem_producer;
    int _c_step;
    int _p_step;//生产者下标

    //维护多生产者多消费者的互斥关系,生产和生产的互斥,消费和消费的互斥
    pthread_mutex_t _mutex_consumer;
    pthread_mutex_t _mutex_producer;
};

makefile

.PHONY:all
all: udp_client udp_server

udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++11 -lpthread
udp_server:udp_server.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f udp_client udp_server.PHONY:all
all: udp_client udp_server

udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++11 -lpthread
udp_server:udp_server.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f udp_client udp_server

Thread.hpp

#pragma once

#include<iostream>
#include<pthread.h>
#include<string>
#include<stdlib.h>
using namespace std;

class Thread
{
public:
    typedef enum//线程状态
    {
        NEW = 0,
        RUNNING,
        EXITED
    }ThreadStatu;
    
    //typedef void (*func_t)(void*);//函数指针
    using func_t = std::function<void ()>;

public:
    Thread(int num,func_t func):_tid(0),_status(NEW),_func(func)
    {
        char name[128];
        snprintf(name,sizeof(name),"thread-%d",num);
        _name = name;
    }

    int status() { return _status; }
    string threadname() { return _name; } 
    pthread_t threadid()
    {
        if(_status == RUNNING)
        {
            return _tid;
        }
        else
        {
            return 0;
        }
    }

    void operator()()// 仿函数,让线程执行任务的
    {
        if(_func != nullptr) _func();
    }
    static void* runHelper(void* args)//注意,这里的args和用户传入的args不一样,这里是this指针,用于调用函数的
    {
        Thread* pt = (Thread*)args;
        (*pt)();
        return nullptr;
    }

    void run()//启动线程
    {
        int n = pthread_create(&_tid,nullptr,runHelper,this);
        if(n != 0) exit(1);
        _status = RUNNING;
    }

    void join()//这里设计简单一点,默认不需要获取函数的返回值
    {
        int n = pthread_join(_tid,nullptr);
        if(n!=0)
        {
            cerr << "join error" << endl;
            return;
        }
        _status = EXITED;
    }
    ~Thread()
    {}
private:
    pthread_t _tid;//线程id
    string _name;
    func_t _func; //线程未来要执行的回调
    ThreadStatu _status;
    void* _args;
};

三、测试效果

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

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

相关文章

Android性能优化相关的10个经典面试题

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 以下是一些Android性能优化面试问题&#xff0c;包括问题和参考解答&#xff1a; 1. 如何优化Android应用的启动速度&#xff1f; 答案&#…

零基础教你如何开发webman应用插件

0X07 发布插件应用 插件应用发布地址 https://www.workerman.net/app/create。填写好发布相关信息 0X08 上传源码zip文件 提交完成之后等待官方审核就可以啦&#xff01; 0X09 安装插件 应用插件安装有两种方式 在插件市场安装 进入官方管理后台webman-admin 的应用插件页点击…

tauri开发配置文件和文件夹访问路径问题

文件夹没权限&#xff1a;Unhandled Promise Rejection: path not allowed on the configured scope: /Users/song/Library/Application Support/com.pakeplus.app/assets/default.png 没有文件夹&#xff0c;需要先创建&#xff1a;Unhandled Promise Rejection: path: /Users…

GB28181信令交互流程及Android端设备对接探讨

GB28181规范必要性 好多开发者在做比如执法记录仪、智能安全帽、智能监控等设备端视频回传技术方案选型的时候&#xff0c;不清楚到底是用RTSP、RTMP还是GB28181&#xff0c;对GB28181相对比较陌生&#xff0c;我们就GB28181规范的必要性&#xff0c;做个探讨&#xff1a; 实现…

vue+UEditor附件上传问题

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

【软件测试】详解软件测试中的测试级别

目录 一、测试级别二、组件测试三、开发者测试3.1测试与调试3.2 组件测试目标3.3 测试功能 四、稳健性测试4.1 效率的测试4.2 测试可维护性4.3 测试策略4.4 白盒测试 一、测试级别 软件系统通常是由许多子系统组成的&#xff0c;而这些子系统又是由多个组件组成的&#xff0c;…

基于STM32的无人驾驶车辆系统

目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 传感器数据采集与处理路径规划与避障控制实时反馈与控制系统应用场景结论 1. 引言 随着无人驾驶技术的发展&#xff0c;嵌入式系统在无人驾驶车辆中的应用变得越来越重要。STM32作为高效…

ECMAScript 与 JavaScript 的区别详解

ECMAScript 与 JavaScript 的区别详解 在前端开发的学习过程中&#xff0c;很多开发者会遇到两个常见的术语&#xff1a;ECMAScript 和 JavaScript。这两个术语常常被混淆&#xff0c;因为它们密切相关&#xff0c;甚至有时被认为是同一件事。本文将详细解析 ECMAScript 和 Ja…

盘点4款专业高效的数据恢复工具。

超级兔子数据恢复工具具有广泛的系统适配性&#xff0c;功能丰富&#xff0c;操作简单&#xff0c;是一款比较专业的数据恢复软件。如果大家在为数据丢失而烦恼的话&#xff0c;我可以推荐几款好用的数据恢复软件给大家。 1、福昕数据高效恢复 直通车&#xff1a;http://www.p…

有哪些使用的电脑安全小技巧?

以下是一些电脑使用的安全技巧&#xff1a; 1. 定期更新系统和软件&#xff1a; 操作步骤&#xff1a;打开系统设置中的“更新和安全”选项&#xff0c;启用自动更新。对于软件&#xff0c;在其设置中查找更新选项并定期检查。 2. 设置强密码&#xff1a; 操作步骤&#xf…

yakit使用教程(二,配置证书并进行抓包改包操作)

前文链接&#xff1a;yakit下载安装教程。 一&#xff0c;下载并配置证书。 点击mitm&#xff0c;在跳转后的页面点击高级配置。 点击证书下载。 点击下载到本地并打开&#xff08;建议下载到桌面&#xff09;。 在火狐浏览器下载并安装FoxyProxy&#xff0c;具体参数配置如上…

TIM--定时器

TIM–基本定时器 大纲 定时器分类时基基本定时器高级控制定时器高级控制定时器功能框图输入捕获应用输出比较应用定时器初始化结构体详解 具体案例 定时器分类 STM32F1 系列中&#xff0c;除了互联型的产品&#xff0c;共有 8 个定时器&#xff0c;分为基本定时器&#xf…

深入探讨在线教育系统源码:搭建知识付费平台实战方案详解

知识付费平台是软件开发行业内炙手可热的项目&#xff0c;其受众群体非常广&#xff0c;也是很多小伙伴提问比较多的&#xff0c;今天小编将从在线教育系统源码开始&#xff0c;为大家讲解一个知识付费平台的搭建开发实战方案。 一、系统架构设计 搭建在线教育系统需考虑以下几…

linux没有权限安装zip应该如何解压压缩包

linux没有权限安装zip应该如何解压压缩包 &#xff08;1&#xff09;尝试使用unzip命令直接解压 &#xff08;2&#xff09;发现没有安装先安装&#xff0c;发现没有权限安装 &#xff08;3&#xff09;再试试tar命令&#xff0c;好像安装了&#xff0c;但是不能用&#x…

罕见 P0 故障!上交所崩了 ~

大家好啊&#xff0c;我是董董灿。 昨天&#xff08;9月27号&#xff09;很多朋友可能都刷到一个消息&#xff1a;上交所崩了。 原因是在近期经济政策的刺激下&#xff0c;我大A股市场出现反弹&#xff0c;很多投资者纷纷涌入大A进行交易。 A 股反弹本来是件好事&#xff0c…

USB 3.1 标准 B 型连接器的接口定义与引脚分配

连接器 USB 3.1 规范定义了以下连接器&#xff1a; 超速标准 A 插头和插座&#xff1b;超速标准 B 插头和插座&#xff1b;超速 Micro B 插头和插座&#xff1b;超速 Micro A 插头&#xff1b;超速 Micro-AB 插座。 所有超速连接器具有相同的配合接口并且彼此兼容。 下表列…

detectron2是怎么建立模型的?以SparseInst代码为例【结论版】

看SparseInst论文发现论文里有些地方没讲清楚&#xff1b;遂找SparseInst源码来看模型结构 我选择从推理代码来找模型结构&#xff1a; 经探索&#xff0c;在SparseInst代码里&#xff0c;推理需要执行代码 python demo.py --config-file configs/sparse_inst_r50_base.yaml …

windows系统使用代码编辑器远程连接linux主机的项目并直接进行修改和命令行操作的方法

一、使用wsl连接linux主机 1.启用hyper-V&#xff0c;按照截图选中这几项 2.windows11安装使用Ubuntu的shell-bash的说明 如何安装 Windows 11 - Shell-Bash (1) - 芒果文档 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norest…

科技赋能,蔡司智锐渐进镜片为老花初体验者带来视觉革命

随着年龄的增长&#xff0c;许多人都会面临老花眼的问题。接受自己老花并不容易。不少人非常排斥这个感念&#xff0c;感觉自己瞬间变老了了十几岁。 老花眼不仅影响视力&#xff0c;更给日常生活带来诸多不便。然而&#xff0c;随着科技的进步&#xff0c;现在有了专为老花初体…

对小白友好的与易我同级别四款剪辑工具推荐:

2024年四款视频剪辑工具推荐&#xff01;让你的创意尽情展现&#xff01; 在数字化时代&#xff0c;软件工具的多样性为我们的创作提供了无限可能&#xff1b;今天这四款是和易我数据恢复软件同级别的推荐&#xff0c;他们在编辑过程中具有至关重要的地位&#xff1b;下面我们将…