【网络】UDP协议的简单使用

news2024/11/22 19:54:16

目录

服务器

客户端

测试


UDP是基于socket进行网络通信的,那我们这篇博客就来介绍一下基于UDP通信的基本流程,先让服务端和客户端进行简单的跨网络通信。

服务器

首先我们需要创建UDP套接字,用到的接口是

man socket

如果要使用UDP通信,第一个参数就传

它用来表示基于网络通信,第二个参数就传

它用来表示是面向数据报,第三个参数我们就默认传0就可以了,因为前两个参数就可以确定它就是UDP协议。它的返回值我们可以认为是一个文件描述符,未来我们就可以向这个文件中去写入或读取东西

接下来是bind socket和网络信息

man 2 bind

第一个参数就是前面接口的返回值,第二个参数是输入性参数,是一个结构体的指针,所以我们要创建这样一个结构体并且填充内容,第三个参数就是第二个参数的大小

所以在绑定之前,我们需要创建struct sockaddr的对象并且填充内容:

因为socket编程是有不同种类的,而操作系统又是拿C语言写的,为了统一接口,我们就用struct sockaddr这个类型,具体到网络通信是struct sockaddr_in这个类型,所以我们创建的是struct sockaddr_in这个类型的变量,到时候进行强制类型转换成struct sockaddr类型

local的类型是一个结构体,结构体中有这些类型:

我们可以看到这里面有port,就是我们传进来的端口号,这个端口号当然也要进行网络传输,因为放到网络中的数据必须是大端,所以我们有接口用来主机序列转网络序列

有了这些接口,我们就可以任意的16位,32位,主机序列转网络序列,网络序列转主机序列。

传完了端口号,就要传ip地址了,就是sin_addr的内容,同样这个也需要主机序列转网络序列,但同时我们知道从命令行参数传进来的IP地址是字符串风格的点分十进制的格式(比如193.168.111.222,这样占15个字节),这样占的空间就大,我们知道每一位都是0~255,这用一个字节来表示整数足够了(这样只占四个字节),所以我们要将字符串风格的点分十进制转换成4字节IP。上面两个工作可以通过下面的接口全部完成

man inet_addr

填充完了我们就可以进行bind了,到此初始化的工作就完成了

下面就是不断的从网络中获取信息,我们可以设置成死循环的形式,我们先实现这样的场景,谁给我们发的,我们再发回去

UDP是面向数据报的,从网络中获取信息的接口是:

man recvfrom

第二个参数是要把从网络中获得的信息存到哪,后两个参数是输出型参数,里面就存放着这个消息是谁发来的一些信息

我们再把消息发回去的接口是:

man sendto

后两个参数就存放着要把消息发送给谁

这样,服务器大致就写好了,还有一些细节需要处理

我们运行主函数的代码时命令行参数就要写上ip和端口号

客户端

客户端要运行主函数肯定是要加上你想访问的服务器的ip地址和端口号,因为这样可以在互联网中唯一确定一个进程,客户端和服务器一样,也是要创建socket,但是不用显示bind,就是我们不要bind,客户端第一次向服务器发消息的时候操作系统会自动bind,因为如果客户端显示的bind,那就意味着不同的客户端可能用一个端口号(比如手机中的抖音和淘宝用一个端口号),这样就导致两个客户端无法同时启动,就会出现端口号冲突的问题。那么就意味着客户端的代码要比服务器要好写

测试

我们像下面这样运行服务器

它是可以成功的,127.0.0.1这个IP我们称为本地环回,它不将服务发送到网络中,可以用于本地通信,常用于代码测试,于是此时我们再起一个SSH渠道就可以实现本地的服务器和客户端通信了

这是本地环回,毕竟不是跨网络的,我们把IP换成服务器的公网IP试一试

可以看到它会报错,因为我们的服务器,无法直接bind公网IP(云服务器不允许),我们也严重不推荐绑定任意一个确定的IP(虚拟机可以)

因为一台设备可能有多个IP地址,我向每个IP地址发,进程都应该收到,如果bind特定的IP地址,那么只有这个IP地址可以收到,所以我们一般传0.0.0.0,简写成0,表示任意IP地址bind,所以之前的代码中还是不要传IP了,因为确定是0

这样,客户端给本地环回和公网IP就都可以发消息了

还有一个查看网络服务的命令

netstat -puan

p代表process进程,u代表udp,a代表all,n代表number,就是能显示成数字的都显示成数字

//UdpServer.hpp

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

enum error
{
    SOCKET_ERR = 0,
    BIND_ERR,
    USAGE_ERR,

};
class UdpServer
{
public:
    UdpServer(uint16_t port) : _port(port), _socketfd(-1)
    {
    }
    void InitServer()
    {
        // 1.创建UDP socket套接字
        _socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            LOG(FATAL, "socket error,%d,%s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        LOG(INFO, "create socket success,socket:%d", _socketfd);

        // 填充struct sockaddr_in结构
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 对空间进行清0
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr=INADDR_ANY;//这个数就是0,因为是0,大小端也就没意义了
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());

        int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error,%d,%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        LOG(INFO, "bind success");
    }
    void start()
    {
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);
            ssize_t n=recvfrom(_socketfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                buffer[n]=0;
                LOG(DEBUG,"get message from[%s,%d]:%s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buffer);//这样解析就知道是谁发过来的
                sendto(_socketfd,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);
            }
        }
    }

private:
    int _socketfd;
    // std::string _ip;
    uint16_t _port;
};
//Main.cc

#include<iostream>
#include"UdpServer.hpp"
void Usage(char* arg)
{
    std::cout<<"Usage:  please enter:"<<std::endl;
    std::cout<<arg<<' '<<"server port"<<std::endl;
}
int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    Enablescreen();
    uint16_t port=std::stoi(argv[1]);
    UdpServer udps(port);
    udps.InitServer();
    udps.start();


    return 0;
}
//UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void Usage(char *arg)
{
    std::cout << "Usage:  please enter:" << std::endl;
    std::cout << arg << ' ' << "server ip " << "server port" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    //创建socket套接字
    int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }
    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());

    std::string message;
    while(true)
    {
        std::cout<<"Please Enter# ";
        std::getline(std::cin,message);
        sendto(socketfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));//给服务器发消息
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        char buffer[1024];
        ssize_t n=recvfrom(socketfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//接收来自服务器的消息
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<"server echo# "<<buffer<<std::endl;
        }
    }

    return 0;
}

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

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

相关文章

【Python知识宝库】面向对象编程:Python类的深度剖析

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、类的定义二、类的属性1. 类属性2. 实例属性 三、类的方法1. 实例方法2. 类方法 四、继承五、总结 前言 面向…

【C++ Primer Plus习题】12.3

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include "stock20.h&quo…

嵌入式OpenHarmony源码基本原理详解

大家好,今天主要给大家分享一下,如何分析与使用OpenHarmony源码,欢迎交流学习。 第一:OpenHarmony源码简介 在3.0版本中,Lite系统(即轻量系统和小型系统)、标准系统各有一套独立的构建入口和上层的构建流程,但在3.2版本中,两者开始互相借鉴,取长补短并实现了融合统一;…

【环境领域EI稳定 I 院士主讲】第九届能源与环境研究进展国际学术会议(ICAEER 2024)

ICAEER 2024会议投稿经过2-3位组委会专家严格审核之后&#xff0c;符合Springer ESE征稿要求的论文将由斯普林格&#xff08;Springer-Nature&#xff09;旗下的 Environmental Science and Engineering (ISSN: 1863-5520) 出版&#xff0c;出版后提交至EI Compendex&#xff…

初步了解VTK装配体

VTK还不太了解&#xff0c;根据资料&#xff0c; vtk.vtkAssembly 是 VTK库中的一个重要类&#xff0c;允许通过将多个vtkActor对象组合在一起来创建复杂的3D模型。 import vtk import math from vtk.util.colors import *filenames ["cylinder.stl","sphere…

C++11 --- 智能指针

序言 在使用 C / C 进行编程时&#xff0c;许多场景都需要我们在堆上申请空间&#xff0c;堆内存的申请和释放都需要我们自己进行手动管理。这就存在容易造成堆内存泄露&#xff08;忘记释放&#xff09;&#xff0c;二次释放&#xff0c;程序发生异常时内存泄露等问题&#xf…

QML入门之创建可重用的组件(一)

我们在日常开发中都会封装一些组件以便于项目内重复利用。QML创建可重用组件一般有两种方法。 自定义Item使用Component创建自定义组件 自定义Item 以一个自定义按钮举例&#xff1a; import QtQuick 2.12Rectangle {id: root// 自定义属性property string btnDis: qsTr(&qu…

Windows环境利用VS2022编译 libvpx 源码教程

libvpx libvpx 是一个开源的视频编码库&#xff0c;由 WebM 项目开发和维护&#xff0c;专门用于 VP8 和 VP9 视频编码格式的编解码处理。它支持高质量的视频压缩&#xff0c;广泛应用于视频会议、在线教育、视频直播服务等多种场景中。libvpx 的特点包括跨平台兼容性、硬件加速…

JavaSE-易错题集-002

1. 下面有关java基本类型的默认值和取值范围&#xff0c;说法错误的是&#xff1f; A 字节型的类型默认值是0&#xff0c;取值范围是-2^7—2^7-1 B boolean类型默认值是false&#xff0c;取值范围是true\false C 字符型类型默认是0&#xff0c;取值范围是-2^15 —2^15-1 D l…

iOS——retain和release底层原理

retain实现原理 retain的源码&#xff1a; //使用此方法等价于使用[this retain] inline id objc_object::retain() {//确保对象不是tagged pointerASSERT(!isTaggedPointer());return rootRetain(false, RRVariant::FastOrMsgSend); }ALWAYS_INLINE id objc_object::rootR…

关系代数 | 数据库SQL

文章目录 关系运算符笛卡尔积笛卡尔积应用 运算符符号含义集合运算符并∪交∩差-笛卡尔积专门的关系运算符选择σ投影π连接⋈除 关系运算符 笛卡尔积 集合运算符中&#xff0c;主要对笛卡尔积做解释&#xff1a; 在数学中&#xff0c;两个集合X和Y的笛卡儿积&#xff08;英语…

【Linux】进程控制(一)

1. 进程创建 &#xff08;一&#xff09;认识fork函数 从已存在进程中创建一个新进程&#xff08;新进程为子进程&#xff0c;而原进程为父进程&#xff09; 进程调用fork&#xff0c;当控制转移到内核中的fork代码后&#xff0c;内核做&#xff1a; 分配新的内存块和内核数…

Allegro PCB--报错

1。 走线上打孔 问题&#xff1a;在走线上打的Via&#xff0c;我通过"Assign net to Via", 给与网络。成功后。 跑Tools\Database check\ Update all DRC(including batch), Via 网络又没有了 原因& 解决方法&#xff1a; VIA没有和走线完全重合 换个方法&#x…

【吊打面试官系列-Redis面试题】说说 Redis 哈希槽的概念?

大家好&#xff0c;我是锋哥。今天分享关于 【说说 Redis 哈希槽的概念&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 说说 Redis 哈希槽的概念&#xff1f; Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念&#xff0c;Redis 集群有 16384 个哈希槽&a…

高精度加法,减法,乘法,除法

加法&#xff1a; 大整数该如何储存&#xff1f; 用数组储存&#xff1a; 把个位放在数下标为0的位置&#xff0c;十位放在数组下标为1的位置&#xff08;也就是高位放在数组的后面&#xff09; 因为这样&#xff0c;如果需要增加一位最高位&#xff0c;那我们就可以直接在…

C语言小游戏--贪吃蛇实现

C语言小游戏--贪吃蛇实现 1.游戏实现背景2.Win32 API介绍2.1什么是Win32 API2.2控制台程序(Console)2.3控制台屏幕的坐标COORD2.4GetStdHandle2.4.1函数语法2.4.2函数的使用 2.5GetConsoleCursorInfo2.5.1函数语法2.5.2函数的使用 2.6CONSOLE_CURSOR_INFO2.6.1结构体结构2.6.2结…

自制游戏手柄--电位器的使用

在前面的讨论中&#xff0c;我们考虑了使用陀螺仪来获取手柄的运动情况来进行瞄准&#xff0c; 自制实战吃鸡手柄原理-CSDN博客 也可以使用图像识别来实现&#xff0c;这里我们再考虑下使用电位器来获取运动状态&#xff0c;一个电位器可以获取到一个平面上的旋转情况&#x…

2025年25届新文出炉:如何打造Java SpringBoot Vue个性化课程推荐系统?

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

根据NVeloDocx Word模板引擎生成Word(二)

前面讲到了根据“永久免费开放的E6低代码开发平台”的NVeloDocx Word模版引擎生成Word文件的基础取数方法&#xff0c;包括取本表单字段以及引用字段&#xff0c;详见《根据NVeloDocx Word模板引擎生成Word&#xff08;一&#xff09;》。 针对这种基本的取数方法&#xff0c;…

枚举相关知识点

1.是用户定义的数据类型&#xff0c;为一组相关的常量赋予有意义的名字。 2.enum常量本身带有类型信息&#xff0c;即Weekday.SUN类型是Weekday&#xff0c;编译器会自动检查出类型错误&#xff0c;在编译期间可检查错误。 3.enum定义的枚举类有什么特点。 a.定义的enu…