【计算机网络】UDP网络程序

news2024/12/21 15:20:07

一、服务端

1.udpServer.hpp

此文件负责实现一个udp服务器

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// 默认参数
static const std::string defaultIp = "0.0.0.0";
static const int gnum = 1024;

// 回调函数类型
typedef std::function<void (int, std::string, uint16_t, std::string)> func_t;

// 错误码
enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    OPEN_ERR
};

// udp服务器
class udpServer
{
public:
    // 构造函数
    udpServer(func_t &callback, uint16_t &port, std::string &ip = defaultIp)
        :_callback(callback), _port(port), _ip(ip), _sockfd(-1)
    {}
    // 析构函数
    ~udpServer(){}
    // 初始化服务器
    void initServer();
    // 启动服务器
    void start();

private:
    uint16_t _port;  // 服务器进程的端口号
    std::string _ip; // 服务器的IP 一款网络服务器不建议指明一个IP
    int _sockfd;     // 创建socket后返回的文件描述符,利用这个fd进行读写
    func_t _callback;// 回调函数:利用这个函数处理业务
};

(1)initServer()

void initServer()
{
    // 1. 创建socket
    _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (_sockfd == -1)
    {
        cerr << "socket error" << errno << " : " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }
    std::cout << "socket success" << " : " << _sockfd << std::endl;

    // 2. 绑定 port ip
    // 未来服务器要明确的port,不能随意改变
    struct sockaddr_in local;
    bzero(&local, sizeof(local)); // 初始化结构体为全0
    local.sin_family = AF_INET;
    // 你如果要给别人发消息,你的port和ip要发送给对方
    // 所以port和ip要从当前主机到网络 h -> n
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = inet_addr(_ip.c_str()); 
    // local.sin_addr.s_addr = htonl(INADDR_ANY);// 任意地址bind:INADDR_ANY:全0

    // 把创建的fd和端口号绑定
    int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
    if (n == -1)
    {
        cerr << "bind error" << errno << " : " << strerror(errno) << endl;
        exit(BIND_ERR);
    }
    // UDP Server 的预备工作完成
}

(2)start()

void start()
{
    // 服务器的本质其实就是一个死循环
    char buffer[gnum]; // 读到的数据放这里
    for (;;)
    {
        // 读取数据
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);                                                                 
        // recvfrom读取数据
        ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        // 处理数据
        if (s > 0)
        {
            // 1.知道是谁发的(获取客户端ip/port)
            std::string clientip = inet_ntoa(peer.sin_addr); // IP
            uint16_t clientport = ntohs(peer.sin_port); // 端口号

            // 2.知道发的什么(获取客户端发送的内容)
            buffer[s] = '\0';
            std::string message = buffer;
            cout << clientip << "[" << clientport << "]# " << message << endl;

            // 我们只把数据读上来就完了吗?还需要对数据做处理
            _callback(_sockfd, clientip, clientport, message);
        }
    }
}

2.udpServer.cpp

此文件负责udp服务器的调用逻辑

#include "udpServer.hpp"
#include <cstdio>
#include <memory>
#include <fstream>
#include <signal.h>

// 使用手册
void Usage(std::string proc)
{
    std::cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

// 对收到的数据做处理
void handlerMessage(int sockfd, std::string clientip, uint16_t clientport, std::string message)
{
    // 填写客户端信息
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

    // 处理并发送消息(服务器 -> 客户端)
    std::string response_message = message;
    response_message = "[server echo] " + message;
    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

// ./udpServer local_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));

    usvr->initServer();
    usvr->start();

    return 0;
}

二、客户端

1.udpClient.hpp

此文件负责实现一个udp客户端

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

class udpClient
{
public:
    // 构造函数
    udpClient(std::string &serverIp, const uint16_t &serverPort)
        : _serverIp(serverIp), _serverPort(serverPort), _sockfd(-1), _quit(false)
    {}
    // 析构函数
    ~udpClient(){}
    // 初始化客户端
    void initClient()
    // 这个线程负责 接受消息
    static void *readMessage(void *args)
    // 主线程负责 发送消息
    void run()

private:
    int _sockfd;           // 自己的fd
    std::string _serverIp; // 目的地
    uint16_t _serverPort;  // 目的地
    bool _quit;            // 表明是否退出
};

(1)initClient()

void initClient()
{
    // 1.创建socket
    _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (_sockfd == -1)
    {
        cerr << "socket error" << errno << " : " << strerror(errno) << endl;
        exit(2);
    }
    std::cout << "socket success" << " : " << _sockfd << std::endl;

    // 2.client要不要bind?要;client要不要显示的bind/程序员自己bind?不需要
}

① 客户访问服务器时需要一个确定的端口号,所以服务器的端口需要我们明确指出

② 客户端的端口号不用我们明确指出,因为写客户端的是许多家公司

假设A公司给Client_A定的端口号是8080,B公司给Client_B定的端口号也是8080

这样这两个程序就无法一起运行,不符合实际,所以客户端由OS自动形成端口进行bind!

(2)run()

// 主线程负责 发送消息
void run()
{
    // 填写服务器的ip/port
    struct 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);

    // 消息处理
    std::string message;
    char line[1024];
    while (!_quit)
    {
        // 发送消息(客户端 -> 服务器)
        std::cout << "Please Enter# ";
        // 这种输入:重点在于可以输入空格
        fgets(line, sizeof(line), stdin);
        line[strlen(line) - 1] = '\0'; // 处理掉输入进来的回车
        message = line;
        sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
        // 没有显示绑定时,第一次发消息的时候绑定port

        // 接收消息(服务器 -> 客户端)
        char buffer[1024]; // 收到的消息放到这里
        struct sockaddr_in serverSock;
        socklen_t len = sizeof(serverSock);
        size_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&serverSock, &len);
        if (n >= 0) buffer[n] = 0;
        std::cout << buffer << std::endl;
    }
}

2.udpClient.cpp

此文件负责udp客户端的调用逻辑

#include "udpClient.hpp"
#include <memory>

// 使用手册
void Usage(std::string proc)
{
    std::cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}

// ./udpClient server_ip server_port -> 输入目的地址(服务器地址)
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    std::unique_ptr<udpClient> ucli(new udpClient(serverIp, serverPort));

    ucli->initClient();
    ucli->run();

    return 0;
}

三、整体调用逻辑

1.双方调用逻辑

UDP服务器可以多个客户端同时访问,用一幅图展示这个过程

2.展示运行结果

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

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

相关文章

【启明智显分享】5G CPE与5G路由器到底有什么区别?

5G路由器和5G CPE在功能和应用场景上存在很明显的差异&#xff0c;小编做了详细比较&#xff0c;希望能帮助到你进一步了解他们的区别及应用。 一、定义与功能 5G路由器 5G路由器是一个将5G网络连接转换为Wi-Fi信号的设备&#xff0c;使多个Wi-Fi设备可以通过5G网络进行连接…

对称加密与非对称加密:密码学的基石及 RSA 算法详解

对称加密与非对称加密&#xff1a;密码学的基石及 RSA 算法详解 在当今数字化的时代&#xff0c;信息安全至关重要。对称加密和非对称加密作为密码学中的两种基本加密技术&#xff0c;为我们的数据安全提供了强大的保障。本文将深入探讨对称加密和非对称加密的特点、应用场景&…

爬虫——数据解析与提取

第二节&#xff1a;数据解析与提取 在网络爬虫开发中&#xff0c;获取网页内容&#xff08;HTML&#xff09;是第一步&#xff0c;但从这些内容中提取有用的数据&#xff0c;才是爬虫的核心部分。HTML文档通常结构复杂且充满冗余信息&#xff0c;因此我们需要使用高效的解析工…

我们是如何实现 TiDB Cloud Serverless 的 - 成本篇

作者&#xff1a; shiyuhang0 原文来源&#xff1a; https://tidb.net/blog/fbedeea4 背景 Serverless 数据库是云原生时代的产物&#xff0c;它提供全托管&#xff0c;按需付费&#xff0c;自动弹性的云数据库服务&#xff0c;让客户免于繁重的数据库运维工作。关于 Serve…

Linux——环境基础开发工具使用2(正在更新中...)

1.自动化构建-make/Makefile 1.1 认识make和Makefile make是一个命令&#xff1b; Makefile是一个文件。 1.2 理解 其中在第一个图片中&#xff0c;第一行的 mytest:test.c 叫做依赖关系&#xff1b;第二行的 gcc test.c -o mytest 叫做依赖方法。 依赖关系和依赖方法共同…

微服务链路追踪skywalking安装

‌SkyWalking是一个开源的分布式追踪系统&#xff0c;主要用于监控和分析微服务架构下的应用性能。‌ 它提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案&#xff0c;特别适用于微服务、云原生架构和基于容器的环境&#xff08;如Docker、K8s、Mesos&…

品牌如何利用大数据工具,进行消费者洞察分析?

存量竞争的时代&#xff0c; 消费者聆听是品牌持续增长的关键&#xff0c;借助大数据的消费者数据洞察&#xff0c;可以帮助品牌分析消费者的所思所想及行为特征&#xff0c;获取消费者对产品的需求痛点、使用感受&#xff0c;对品牌的评价口碑等&#xff0c;从而帮助品牌更好地…

模拟实现优先级队列

目录 定义 特点 构造函数 常用方法 关于扩容的问题 关于建堆的问题 向上调整和向下调整的比较 &#xff08;向上调整&#xff09;代码 &#xff08;向下调整&#xff09;代码 关于入队列和出队列问题 模拟实现优先级队列代码 关于堆排序的问题 堆排序代码 关于对…

【4】GD32H7xx ADC采样

目录 1. GD32H7xx ADC1.1 ADC外设资源1.2 采样时间1.3 片上硬件过采样 2. ADC DMA规则多通道采样程序3. 程序测试 1. GD32H7xx ADC 1.1 ADC外设资源 GD32H7xx 有3个ADC外设&#xff1a; ADC0 20个外部通道&#xff0c;1个内部通道&#xff08;DAC0_OUT0通道&#xff09;32位…

GitLab 如何跨版本升级?

本分分享 GitLab 跨版本升级的一些注意事项。 众所周知&#xff0c;GitLab 的升级必须要严格遵循升级路径&#xff0c;否则就会出现问题&#xff0c;导致升级失败。因此&#xff0c;在 GitLab 升级之前需要做好两件事情&#xff1a; 当前版本的确认升级路径的确认 极狐GitLa…

【咕泡P5人工智能CV 技术NLP项目实战】

人工智能核心代码&#xff1a; 一、什么是人工智能技术&#xff1a; 人工智能&#xff08;Aritificial Intelligence&#xff09;&#xff0c;英文缩写AI&#xff0c;人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 人工智…

R门 - rust第一课陈天 -内存知识学习笔记

内存 #mermaid-svg-1NFTUW33mcI2cBGB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1NFTUW33mcI2cBGB .error-icon{fill:#552222;}#mermaid-svg-1NFTUW33mcI2cBGB .error-text{fill:#552222;stroke:#552222;}#merm…

C# x Unity 从玩家控制类去分析命令模式该如何使用

本文部分内容出自游戏编程模式一书,游戏编程模式,有兴趣的小伙伴可以去看看,虽然不是unity x c#写的 但是思路挺好的 目录 目录 0.先说结论 发现问题 命令模式如何解耦 打个断点更利于分析 怎么实现延迟命令? 如何撤销命令? 脚本整体一览 不足分析(AI) 0.先说结论 …

【C++】—— stack和queue的模拟实现

前言 ​ stack 和 queue使用起来都非常简单&#xff0c;现在来模拟实现一下&#xff0c;理解其底层的原理。 ​ 在实现之前&#xff0c;应该知道&#xff0c;stack 和 queue 都是容器适配器&#xff0c;通过看官网文件也可以看出来&#xff1b;其默认的容器都是deque&#xff…

探索高效的 Prompt 框架:RBTR 提示框架的奥秘与优势

前言 在当今数字化的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为我们生活和工作中不可或缺的一部分。而 Prompt 作为与 AI 交互的关键工具&#xff0c;其质量直接影响着我们获取信息的准确性和有用性。今天&#xff0c;我们将深入探讨一个通用的 Prompt 框架…

动态规划-完全背包问题——322.零钱兑换

1.题目解析 题目来源 322.零钱兑换——力扣 测试用例 2.算法原理 1.状态表示 这里需要寻找硬币使总面值等于一个值求出所需硬币的最小个数&#xff0c;所以不妨设置一个二维dp表&#xff0c;即dp[i][j]&#xff1a;在[1,i]个硬币中选择的硬币总面值完全等于j时所需要的最小硬…

day-82 最少翻转次数使二进制矩阵回文 I

思路 依次计算使行回文和列回文的的翻转次数&#xff0c;返回较小的次数即可 解题过程 计算行翻转次数&#xff1a;对于每一行&#xff0c;如果第一个数与最后一个数不相同&#xff08;前提前一个数的索引小于后一个数的索引&#xff09;则需翻转一次&#xff0c;将所有行翻转…

计算光纤色散带来的相位移动 matlab

需要注意的地方 1.以下内容纯属个人理解&#xff0c;很有可能不准确&#xff0c;请大家仅做参考 2.光速不要直接用3e8 m/s&#xff0c;需要用精确的2.9979.... 3.光的频率无论在真空还是光纤(介质)都是不变的&#xff0c;是固有属性&#xff0c;但是波长lambdac/f在不同的介…

15分钟学 Go 第 53 天 :社区资源与学习材料

第53天&#xff1a;社区资源与学习材料 目标 了解Go语言官方资源掌握社区重要学习平台学会利用开源项目学习构建个人知识体系 一、Go语言官方资源汇总 资源类型网址说明Go官网golang.org官方文档、下载、教程Go Blogblog.golang.org技术博客、最新特性介绍Go Playgroundpla…

丹摩征文活动 |【前端开发】HTML+CSS+JavaScript前端三剑客的基础知识体系了解

前言 &#x1f31f;&#x1f31f;本期讲解关于HTMLCSSJavaScript的基础知识&#xff0c;小编带领大家简单过一遍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 …