Linux——UDP协议与相关套接字编程
一.概念
在网络通信中,传输层中最常用的通信协议有两个: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
如有错误,敬请斧正
相关文章:
Linux——UDP协议与相关套接字编程
一.概念在网络通信中,传输层中最常用的通信协议有两个:TCP协议与UDP协议。这两种协议虽然都可以用于网络通信,但是通信方式不同决定了应用场景的不同。与TCP协议相比,UDP协议最具特色的不同点有两个:无连接与面向数据报…...
EM算法 简明理解
E:Expection,期望步,利用估计的参数,来确定未知因变量的概率,并利用其来计算期望值。 M:Maximization,最大化,使用最大似然法更新参数值,使E步中期望值出现的概率最大。…...
论坛项目小程序和h5登录
项目中安装uview出现npm安装uview 直接报错:创建一个package.json配置文件在进行安装。cmd到项目。初始化一个package.json文件(vue项目的配置文件) npm init --yes 安装uview项目点击关注进入管页面,需要验证用户是否登录查用户是…...
kubernetes集群pod中的pause容器作用
kubernetes集群pod中的pause容器作用 我们搭建完集群了以后,可以使用最简单的方式创建一个pod,随意你建立什么pod,去访问相应node上执行docker ps 就会看到有一种pause容器,但是你可能从来没有启用 etrics-scraper_dashboard-me…...
【2.24】malloc()分配内存、MySQL事务、项目、动态规划
malloc是如何分配内存的? 在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示: 内核空间与用户空…...
Unity——使用铰链关节制作悬挂物体效果
目的在场景中创建一个悬挂的物体,是把多个模型悬挂在一起可以自由摇摆,类似链条的效果效果图前言什么是铰链关节?铰链关节 将两个刚体(Rigid body)组会在一起,从而将其约束为如同通过铰链连接一样进行移动。…...
plsql过程语言之uxdb与oracle语法差异
序号场景uxdboracle1在存储过程中使用goto子句create or replace procedure uxdbc_oracle_extension_plsql_goto_0001_procedure01(t1 int) language plsql as $$ begin if t1%20 then goto even_number; else goto odd_number; end if; <<even_number>> raise…...
file_get_contents 打开本地文件报错: failed to open stream: No such file or directory
php 使用file_get_contents时报错 failed to open stream: No such file or directory (打开流失败,没有这样的文件或目录) 1. 首先确保文件路径没问题 最好是直接复制一下文件的路径 2. windows电脑可以右键该文件 → 属性→安全 →对象名称 选中后复制一下 3. 然后…...
Candence allegro 创建等长的方法
随着源同步时序电路的发展,越来越多的并行总线开始采用这种时序控制电路,最典型的代表当属目前炙手可热的DDRx系列。下图这种点到点结构的同步信号,对于攻城狮来说,设置等长约束就非常easy了图片。 But,对于有4、6、8、、、等多颗DDR芯片的ACC同步信号来说,要设置等长约束…...
使用Python批量修改文件名称
下载了一些图片,想要更改其文件的名称。 试了许多方法,都不太理想。 于是想到了使用Python来实现。 需要用到的模块及函数: import osrename() 函数用于改变文件或文件夹的名称。它接受两个参数:原文件名和新文件名。 os.rena…...
【跟我一起读《视觉惯性SLAM理论与源码解析》】第八章 ORB-SLAM2中的特征匹配
特征匹配在ORB-SLAM2中是很重要的内容,函数有多次重载,一般而言分为以下 单目初始化下的特征匹配通过词袋进行特征匹配通过地图点投影进行特征匹配通过Sim(3)变化进行特征匹配 在单目初始化下的特征匹配是参考帧和当前帧之间的特…...
【Leedcode】数据结构中链表必备的面试题(第四期)
【Leedcode】数据结构中链表必备的面试题(第四期) 文章目录【Leedcode】数据结构中链表必备的面试题(第四期)1.题目2.思路图解(1)思路一(2)思路二3.源代码总结1.题目 相交链表: 如下(示例)&…...
【2023】助力Android金三银四面试
前言 新气象,新生机。在2023年的Android开发行业中,又有那些新的面试题出现呢?对于Android面试官的拷问,我们又如何正确去解答?万变不离其宗,其实只要Android的技术层面没变化,面试题也就是差不…...
Leetcode.1801 积压订单中的订单总数
题目链接 Leetcode.1801 积压订单中的订单总数 Rating : 1711 题目描述 给你一个二维整数数组 orders,其中每个 orders[i] [pricei, amounti, orderTypei]表示有 amounti笔类型为 orderTypei、价格为 pricei的订单。 订单类型 orderTypei 可以分为两种…...
红帽Linux技术-cp命令
cp是一个复制文件或者目录的命令,其作用是将一个或多个文件或目录从源位置复制到目标位置。 格式:cp [选项] 源文件或目录 目标文件或目录 常用选项: -r:复制目录及其子目录下的所有文件和目录; -p:保留…...
代码随想录算法训练营day41 | 动态规划 01背包问题基础 01背包问题之滚动数组
01背包问题基础 问题描述 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。 举个栗子 背包最大重量为4。 物品为: 重量价值…...
MyBatis学习笔记(三) —— MyBatis核心配置文件详解
3、核心配置文件详解 id是唯一标识,不能重复,但是在真正开发过程中,不可能一个项目中同时使用两个环境,肯定会使用其中的某一个,这时候它的default就比较重要了。 default是设置我们当前使用的默认环境的id <?x…...
使用GDAL进行坐标转换
1、地理坐标系与投影坐标系空间参考中主要包含大地水准面、地球椭球体、投影坐标系等几部分内容。地图投影就是把地球表面的任意点,利用一定数学法则,转换到地图平面上的理论和方法,一般有两种坐标系来进行表示,分别是地理坐标系和…...
日常编程中和日期相关的代码和bug
本文主要是Java中和日期时间相隔的几个常用代码函数代码,做了总结,希望在日常编码中,可以帮到大家。 1.计算闰年 记住一个短语,“四年一润,百年不闰,四百再润”,不管换啥语言,相信…...
ATT与Intel汇编语法区别
寄存器、变量(常量)与立即数 在Intel汇编中,无论是寄存器、变量(常量)还是立即数,都是直接使用的,例如下列例子中分别加载一个变量(常量)与立即数到寄存器中:…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
