Linux——UDP协议与相关套接字编程

news2025/7/19 18:57:57

一.概念

在网络通信中,传输层中最常用的通信协议有两个:TCP协议与UDP协议。这两种协议虽然都可以用于网络通信,但是通信方式不同决定了应用场景的不同。

与TCP协议相比,UDP协议最具特色的不同点有两个:无连接与面向数据报

(一).无连接

所谓无连接就是通信的双方不需要在已建立联系的前提下通信,换句话说就是信息发送方只管发送数据,不需要判断对方是否能够接收到数据。我们知道使用TCP协议通信前,需要让双方“连接”,即客户端发送连接请求,服务器端接收后与之连接,只有在连接成功后双方才能互相发送数据。但是UDP协议的双方不需要提前建立“连接”,需要发送数据时直接发送,“不在乎”对方是否接收。因此相较于TCP协议而言,UDP协议较为不安全(不可靠),有数据丢失的可能。

那可能会有人有疑问,既然UDP协议的通信方式如此不安全,为什么还会有UDP通信协议的存在呢。这是因为虽然UDP的通信方式不安全,但是通信成本较低,通信方式简单,效率高。比如我们观看网络直播晚会正常就是采用的UDP协议,因此会出现观看中突然卡顿的现象,这极有可能就是UDP发送时出现数据丢失。同时因为观看人数极多,如果采用TCP协议那种与每一个用户建立连接又会消耗大量资源。偶尔卡顿对观看体验的降低远小于采用TCP协议所消耗的大量成本。因此网络直播大多采用UDP协议。同样地,如果是非常重要的小众会议,那么就会采用TCP协议通信。

因此,UDP协议一般用于通信人数多且对传输数据质量要求低的通信形式(比如直播)。

(二).面向数据报

所谓面向数据报指的是UDP发送数据时不管报文长度,一次发送一整个报文。因此采用UDP协议的通信需要规定好报文长度,如果报文过长那么IP层需要切片,降低通信效率。而与UDP不同的是TCP协议采用面向字节流的发送方式,含义可以参考输入输出流,TCP内部有一个缓冲区,如果报文过长,那么就可以一部分一部分的推送。打个比方当我们买苹果时,面向数据报就好比商家把苹果一个一个交给我们手上,面向字节流就是商家把一堆苹果装在一个盒子里后,把这个盒子交给我们。

二.相关套接字编程

(一).什么是套接字

套接字英文名为socket,直译是“插座”。插座是用于连接电源和电器,达到通电的效果。而套接字是让应用层与传输层通过IP和端口号port锁定其他网络中的进程来达到通信的目的。因此套接字由IP地址、端口号port以及通信协议方式(TCP/UDP)组成

我们知道,在网络通信中通过一个IP地址可以锁定一台主机,通过端口号可以锁定一台主机上某一个进程。因此IP地址+端口号可以锁定全网中唯一的进程,进而达到通信的目的。用户通过使用套接字,调用相关API接口,就可以与其他主机网络通信,本质是与其他主机的某一个进程通信。而对方用户接收、返回数据也是使用套接字,调用相关接口。这种使用套接字,调用接口从而进行网络通信的方式就是套接字编程。

(二).UDP套接字编程

首先,我们应该先了解采用UDP通信协议的套接字编程大体流程。

头文件<sys/socket.h>、<sys/types.h>

①获取套接字

调用linux系统接口socket,获取一个套接字。

第一个参数用来确定套接字类型。我们知道套接字分为三种:网络套接字、域间套接字、原始套接字。在网络通信中采用网络套接字类型,因此第一个参数需要填入宏AF_INET,代表采用网络套接字。

第二个参数用来确定套接字的协议类型(TCP/UDP)。如果是采用UDP协议就是宏SOCK_DGRAM,代表面向数据报通信。

第三个参数是用来确定通信的协议家族。一般填0,代表用户不指定协议类型,由服务商选择通信协议。

返回值是一个文件描述符。本质就是打开了一个文件(linux下一切皆文件),因此该文件描述符会占用进程中files_struct结构体中fd_array数组一个下标。如果打开失败就会返回-1并设置错误码errno。

示例如下:

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

②创建带有本机器IP地址和本进程端口号的结构体

使用结构体类型为struct sockaddr_in类型,进行网络通信时就是通过该结构体中记录地址找到通信目标。

//struct sockaddr_in类型内部结构
#define    __SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;            /* Port number.  */
    struct in_addr sin_addr;        /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
               __SOCKADDR_COMMON_SIZE -
               sizeof (in_port_t) -
               sizeof (struct in_addr)];
  };

struct sockaddr_in结构体内部有三个参数:sin_family、sin_port、sin_addr

第一个参数sin_family用以确定套接字类型,网络通信中就是网络套接字,即AF_INET。

struct sockaddr_in addr;
addr.sin_family = AF_INET;

第二个参数sin_port用于确定该套接字需要连接的端口号,即本进程端口号。

这里需要格外注意的是,因为端口号需要用于网络传输使用,在网络中统一采用大端存储的网络字节序。linux提供了接口htons来将数据转为大端存储形式:

记忆这些函数的方法很简单,htons即代表host(主机)to(转到)net(网络)采用short短整形(uint16_t)的形式。其他函数类比即可。

uint16_t port = ...//端口号
addr.sin_port = htons(port);

第三个参数sin_addr用来表示本主机对应的IP地址。因为我们输入的IP地址一般采用字符串类型,但是在网络中需要使用in_addr_t类型(本质就是32位无符号整数)。

因此采用函数inet_addr可以将字符串转为in_addr_t类型,或者函数inet_aton:

inet_aton函数如果成功返回非0,失败返回0。

string IP = "127.0.0.1";//示例
addr.sin_addr.s_addr = inet_addr(IP.c_str());//方式一
int i = inet_aton(IP.c_str(), addr.sin_addr.s_addr);//方式二

③绑定:绑定结构体与套接字

在创建好记录IP地址和端口号的结构体struct sockaddr_in之后,需要将该结构体和套接字绑定到一起,调用bind接口进行绑定:

该函数第一个参数是我们需要绑定的套接字文件描述符,也就是socket函数的返回值。

第二个参数是要绑定的网络通信结构体,也就是我们第二步创建的struct sockaddr_in。因为bind函数采用struct sockaddr类型,因此需要将我们创建的结构体强转一下。那么可能有人会有疑问,bind函数参数为什么是struct sockaddr类型而不是struct sockaddr_in类型呢,这俩有什么区别么?

——有区别,struct sockaddr类型是早期的网络通信结构体,但是其内部结构并不合理,IP地址与端口号都采用成员sa_data来表示:

//struct sockaddr类型内部结构
/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];        /* Address data.  */
  };

因此后来出现了struct sockaddr_in类型,能够将IP地址和端口号用两个成员分开表示。

bind函数第三个参数是struct sockaddr类型结构体的大小。

返回值为0代表绑定成功,-1代表绑定失败。

int i = bind(sockfd, (struct sockaddr*)&addr, sizeof addr);
if(i < 0)
{
    //绑定失败
}
//绑定成功

④通信(发送 & 接收)

发送时,采用sendto系统接口。根据目标地址的IP地址和端口号(或者说struct sockaddr结构体)将数据发送过去。

该函数第一个参数是本进程使用的套接字文件描述符(给对方发数据要把自己的信息传过去,这样对方才能回复)。

第二个参数是指针类型,指向要发送数据。第三个参数是数据大小。这两个参数可以参考write函数第二、三个参数含义。

第4个参数代表发送数据策略,一般填0代表正常操作。具体可以参考这篇博客:linux socket中 send recv函数的 flags参数

第四、五个参数代表数据接收方的struct sockaddr结构体。在使用sendto函数之前,要先获取到对方的IP地址和端口号,创建struct sockaddr_in结构体后,再调用sendto函数。

返回值为实际发送数据的长度,发送失败返回-1。

示例如下:

char buf[1024];
//制作数据...
struct sockaddr_in client;//创建数据目标接收方的结构体
//记录接收方的IP地址和端口号
int i = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&client, sizeof client);//发送数据
if(i < 0)
{
    //发送失败
}

上述示例中有一点需要注意,发送数据的大小应该是实际数据的长度,而不是整个buf容器的大小。当然UDP协议采用面向数据报的形式,传buf大小没有关系,但如果是TCP协议那样采用字节流的形式时就会出错,这一点将在讲解TCP协议的博客中说明。

接收时,采用recvfrom函数。在没有接收到数据之前会阻塞在recvfrom函数上。

该函数第一个参数同sendto函数,也是本进程创建的套接字文件描述符。

第二个参数为输出型参数,用于获取接收到的数据。第三个参数是提供的接收数据容器的大小。这两个参数含义同系统read函数第二、三个参数。

第四个参数含义同sendto函数,不再解释,一般填0。

第五、六个参数也是输出型参数,用于获取数据发送方的网络通信结构体和结构体长度。本质是为了获取对方的IP地址和端口号,便于我方向对方主机回复数据。

返回值代表实际接收到的数据大小,接收失败返回-1。值得一提的是,如果对方进程关闭了,recvfrom函数并不会返回-1,而是一直阻塞。

示例如下:

char buf[1024];//用于接收数据
struct sockaddr_in server;
socklen_t leng = sizeof server;
int i = recvfrom(sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)&server, &leng);
//此时server内部记录的已经是发送方的IP地址和端口号
if(i < 0)
{
    //接收失败
}
buf[i] = '\0';

(三).UDP协议通信场景模拟

①服务端

服务端的任务有三个:建立UDP协议、等待接收客户端数据、处理数据后发送给客户端。

需要注意的是实际开发中服务器启动后就会一直运行,因此服务器接收、处理、发送数据的程序应该是死循环。

同时因为实际中一个服务器会有多个网卡即多个IP地址,采用INADDR_ANY宏定义记录IP地址后,服务器会监听本机所有IP地址。INADDR_ANY本质就是"0.0.0.0"。

伪代码如下:

class Server
{
public:
    Server(const uint16_t port)//获取端口号
        : _port(port)
    {}
    bool initServer()//初始化服务器
    {
        // 获取套接字(插座)
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        ...
        // 创建本端口结构体
        struct sockaddr_in local;        
        ...
        local.sin_addr.s_addr = INADDR_ANY;//监听本机所有IP地址        
        // 绑定
        int i = bind(_socket, (struct sockaddr *)&local, sizeof local);
        ...
        return true;
    }
    void startServer()//启动服务器
    {
        char buf[1024]; // 接收数据的缓冲区
        // 启动服务器,死循环
        for (;;)
        {
            // 接收数据
            ...
            ssize_t i = recvfrom(_socket, buf, sizeof buf, 0, (struct sockaddr *)&client, &len);
            if (i > 0)
            {
                ...//处理数据  
                //处理后将数据交给客户端            
                sendto(_socket, replyBuf.c_str(), replyBuf.size(), 0, (struct sockaddr *)&client, sizeof client);
            }
            else
            {
                ...//接收数据失败
            }
            bzero(&buf, sizeof(buf));
        }
    }

private:
    uint16_t _port;
    int _socket;
};

②客户端

客户端的任务有三个:获取服务器端套接字、发送数据给服务器、接收服务器数据。同服务器端一样,也采用死循环的形式。

需要注意的是,客户端在通信时只需要提前建立好本机套接字,并不需要绑定到特定端口号和IP地址,这是因为可能会有多个客户端,如果同时绑定的话可能会发生绑到同一个端口的错误行为,因此绑定的任务直接交给操作系统。

伪代码如下:

//客户端启动格式: ./client.cpp 服务端IP地址 服务端端口号
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        ...//格式错误
    }
    //建立本机套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    //确定服务端IP和端口号,通信使用
    struct sockaddr_in svr;
    bzero(&svr, sizeof(svr));
    svr.sin_family = AF_INET;
    svr.sin_addr.s_addr = inet_addr(argv[1]);
    svr.sin_port = htons(atoi(argv[2]));
    for (;;)//死循环
    {
        ...//客户端制造数据
        //发送数据给服务器
        sendto(fd, str.c_str(), str.size(), 0, (struct sockaddr *)&svr, sizeof svr);
        struct sockaddr_in addr;//用于记录服务器IP和端口
        ...
        //接收服务器数据
        recvfrom(fd, buf, sizeof buf, 0, (struct sockaddr *)&addr, &len);
        ...//处理服务器数据
    }
    return 0;
}

任何优秀的大软件里面都是一个优秀的小程序。— Charles Antony Richard Hoare


如有错误,敬请斧正

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

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

相关文章

kubernetes集群pod中的pause容器作用

kubernetes集群pod中的pause容器作用 我们搭建完集群了以后&#xff0c;可以使用最简单的方式创建一个pod&#xff0c;随意你建立什么pod&#xff0c;去访问相应node上执行docker ps 就会看到有一种pause容器&#xff0c;但是你可能从来没有启用 etrics-scraper_dashboard-me…

C++中的内存管理

文章目录前言1.C中内存空间的划分2.C内存管理方式1.对内置类型的处理2.对自定义类型的处理3.new和delete实现原理4.定位new3.总结1. malloc/free和new/delete的区别2. 内存泄漏前言 C中的内存空间划分和C语言是很像的&#xff0c;基本上区别不大。但是因C中&#xff0c;引入了…

【华为OD机试模拟题】用 C++ 实现 - 找字符(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 货币单位换算(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 选座位(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 停车场最大距离(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 重组字符串(2023.Q1) 【华为OD机试模…

MyBatis-常用SQL操作

一、动态SQL 1.概述】 1.1动态SQL&#xff1a; 是 MyBatis 的强大特性之一&#xff0c;解决拼接动态SQL时候的难题&#xff0c;提高开发效 1.2分类&#xff1a; if choose(when,otherwise) trim(where,set) foreach 2.if 2.1 做 where 语句后面条件查询的,if 语句是可以…

【OpenFOAM】-olaFlow-算例10-wavemakerTank

算例路径&#xff1a; olaFlow\tutorials\wavemakerTank 算例描述&#xff1a; 采用 Flap和Piston两种方式的动网格进行造波 学习目标&#xff1a; 了解 olaDyMFlow 的使用&#xff1b;理解动网格使用和参数设置&#xff0c;理解 dynamicMotionSolverFvMesh 参数设置&#xff1…

ChatGPT对于普通人有什么机会和影响?

ChatGPT爆火“出圈”&#xff0c;短短三个月里&#xff0c;势如破竹。 月活已经达到1亿&#xff0c;什么概念呢&#xff1f;Tiktok在海外达到1亿月活用了将近9个月时间&#xff0c;Instagram用了大约2年半&#xff0c;就连比尔盖茨都表示“Web3没那么重要&#xff0c;元宇宙没…

STM32---备份寄存器BKP和 FLASH学习使用

BKP库函数 学习BKP&#xff0c;首先就是知道BKP每一个函数的作用然后如何使用即可 使用备份域的作用只需要操作上面的两个函数即可&#xff0c;其余的都是它的其他功能 BKP简介 备份寄存器是42个16位的寄存器&#xff0c;可用来存储84个字节的用户应用程序数据。他们处在备份…

【Jupyter Notebook的简单入门使用】

【Jupyter Notebook的简单入门使用】简单介绍安装与配置简单使用Markdown关闭简单介绍 Jupyter官网 Jupyter Notebook 介绍 简单来讲&#xff0c;它是一个网页应用&#xff0c;可以进行文档编写&#xff0c;甚至运行 py 代码等功能 安装与配置 下载合适版本的 python &#…

【C语言】带你彻底理解指针(1)

✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦&#xff01;&#xff01;✨✨✨✨ 文章目录指针的介绍&#xff1a;一、简单指针&#x1f308;1.1 指针的定义与使用1.2 指针与数组二、指针数组✨三、数组指针&#x1f31e;3.1 数组指针的定义3.2 ”数组名“与”&数组名“3.…

达梦数据库DSC集群部署

一、概述 1.1 DSC 集群架构 1.2 架构说明 1、DMDSC 集群是一个多实例、单数据库的系统。 多个数据库实例可以同时访问、修改同一个数据库的数据。 2、数据文件、控制文件在集群系统中只有一份,不论有几个节点,这些节点都平等地使用这些文件, 这些文件保存在共享存储上。 3…

肠道核心菌属——双歧杆菌属,了解并拥有它

双歧杆菌 双歧杆菌属&#xff08;Bifidobacterium&#xff09;是放线菌门严格厌氧的革兰氏阳性多形性杆状细菌。末端常常分叉&#xff0c;故名双歧杆菌。是人和动物肠道的重要核心菌群和有益生理菌群&#xff0c;也是母乳喂养婴儿中发现的第二大菌。 肥胖、糖尿病和过敏等各种疾…

高德地图基础教程超详细版

在当前社会&#xff0c;对于地图的使用是很必须的&#xff0c;所以对于程序员来说也是需要掌握的技能&#xff0c;目前主流的又百度地图和高德地图&#xff0c;但是我建议使用高德地图&#xff0c;因为百度地图的API着实不好用吖&#xff0c;不好理解&#xff0c;对于开发人员来…

浏览器输入www.baidu.com后执行的全部过程

日升时奋斗&#xff0c;日落时自省 <1>URL输入 URL称为 : 统一资源定位符,用于定位互联网上的资源,也就是平常提起的"网址" 地址栏输入网址之后按下回车,浏览器会对输入的信息进行评判 (1)检查输入的内容是否是是一个合法的网址连接(非法地址不行) (2)合法的…

【spring教程】3.IoC容器概述

IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。 Spring 通过 IoC 容器来管理所有 Java 对象的实…

【数据结构】二叉树的原理及实现

1.什么是数&#xff1f; 树这种数据结构在计算机中是非常重要的&#xff0c;是一种非线性数据结构。一些数据库的底层与快速索引都离不开树这种数据结构。树是有很多节点组成的具有一定层次关系的集合。最上面的可以看成是树的头&#xff0c;下面的很多节点就在这个头的基础上…

前端如何实现局部滚动效果?

一、基础版局部滚动 重点在于给需要滚动的区域加上 overflow: auto; 属性 废话不多说&#xff0c;先上基础版的局部滚动代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv…

智能优化算法——遗传算法(GA)(纯理论,不包含代码)

今天接着PSO&#xff0c;记录一下遗传算法的实现原理。&#xff08;若有错误&#xff0c;请大佬帮忙指正&#xff01;&#xff09;&#xff08;同样&#xff0c;主要参考b站视频学习加入自己的一些理解&#xff0c;如果想要看视频学习&#xff0c;可以直接移步最后参考链接&…

深度学习引言

动手学深度学习pytorch版-笔记原文链接日常生活中的机器学习机器学习中的关键组件数据模型目标函数优化算法各种机器学习问题监督学习回归分类标记问题搜索推荐系统序列学习无监督学习与环境互动强化学习特点小结原文链接 动手学深度学习pytorch中文版 日常生活中的机器学习 …

可怕,chatGPT用3小时教会我数据分析

chatGPT这玩意真的是我的救星,用它作为我的Python教练,我用三个小时学会了数据处理(Pandas)和绘图(matplotlib)。 这两个库的学习,在之前已经困扰了我7个月。之前卡壳的原因,是我一直没有耐心从零开始,按照教材设置的教程去学习Python——我擅长在项目中学习,一点一点…

Android实现炫酷跳动的闪屏LOGO

前言&#xff1a;在日常开发中&#xff0c;经常会遇到各种视觉效果&#xff0c;有的效果可能一眼看去会让人觉得很复杂&#xff0c;但是我们必须明确一点&#xff1a;所有复杂动效都是可以分解成单一的基础动作&#xff0c;比如缩放&#xff0c;平移&#xff0c;旋转这些基础单…