Linux socket编程(12):Unix套接字之socketpair、sendmsg和recvmsg详解

在上一篇文章Unix套接字编程及通信例子中,我们对Unix套接字编程有一个基本的了解。但在Unix套接字编程的领域中,有一组特殊而强大的工具:socketpairsendmsgrecvmsg,它们为实现本地进程间通信提供了便捷的方式。

文章目录

  • 1 socketpair
  • 2 sendmsg和recvmsg
    • 2.1 函数原型
    • 2.2 msghdr结构体
    • 2.3 cmsghdr结构体
    • 2.4 实例
      • 2.4.1 初始化
      • 2.4.2 子进程实现
      • 2.4.3 父进程实现
    • 2.4.4 实验结果
    • 2.4.5 完整代码

1 socketpair

socketpair是一个用于在同一台计算机上创建一对相互连接的套接字的系统调用。这对套接字可以用于进程间的本地通信,通常用于父子进程或兄弟进程之间。它创建的套接字对是相互连接的,因此数据可以直接在这两个套接字之间传递,而无需经过内核缓冲区,从而提高了通信的效率。

int socketpair(int domain, int type, int protocol, int sv[2]);
  • domain:地址族,通常设置为 AF_UNIX,表示使用Unix域套接字。
  • type:套接字类型,通常设置为 SOCK_STREAMSOCK_DGRAM
  • protocol:指定使用的协议,通常设置为 0,表示使用默认协议。
  • sv:一个包含两个整数的数组,用于存储创建的套接字描述符。

这和匿名管道(pipe)很像,但匿名管道中的文件描述符是单方向的,只能支持一个方向的数据流,其中描述符0固定用于读,描述符1固定用于写。而socketpair是一个全双工通信通道,它同时支持双向的数据流。两个文件描述符都支持双向通信,下面来看一个例子:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>void send_message(int sockfd, const char* message) {send(sockfd, message, strlen(message), 0);
}void receive_message(int sockfd, char* buffer, size_t buffer_size) {ssize_t received_bytes = recv(sockfd, buffer, buffer_size - 1, 0);if (received_bytes > 0) {buffer[received_bytes] = '\0';  // Null-terminate the received dataprintf("Received: %s\n", buffer);} else {perror("Error receiving message");}
}int main() {int sv[2];if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {perror("Error creating socket pair");return -1;}pid_t pid = fork();if (pid == -1) {perror("Error forking");return -1;}if (pid == 0) {// 子进程close(sv[0]);char buffer[1024];receive_message(sv[1], buffer, sizeof(buffer));send_message(sv[1], "get a message from father");close(sv[1]);  // 关闭写端} else {// 父进程close(sv[1]);send_message(sv[0], "123");char buffer[1024];receive_message(sv[0], buffer, sizeof(buffer));close(sv[0]);  // 关闭读端}return 0;
}

在这个例子中,创建了一个子进程,其中sv[0]用于表示子进程的套接字,sv[1]用于表示父进程的套接字。在父进程中,向子进程发送123后开始接收数据,而子进程收到123后发送get a message from father给父进程,然后退出程序。父进程收到后也退出程序。实验结果如下:

在这里插入图片描述

在这里sv[0]sv[1]既用来读也用来写,表明这两个套接字都是全双工的。

2 sendmsg和recvmsg

2.1 函数原型

sendmsg函数向套接字发送消息,允许同时发送多个缓冲区的数据以及附带文件描述符等辅助信息。

recvmsg函数用于接收通过套接字传输的消息,并允许接收辅助数据,如控制信息、文件描述符等。

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
  • sockfd:套接字描述符
  • msg:指向 struct msghdr结构的指针,该结构定义了消息的各个部分,包括数据缓冲区、控制信息等
  • flags:标志参数,通常设置为 0

2.2 msghdr结构体

struct msghdr结构的定义如下:

struct msghdr {void         *msg_name;       /* optional address */socklen_t     msg_namelen;    /* size of address */struct iovec *msg_iov;        /* scatter/gather array */size_t        msg_iovlen;     /* # elements in msg_iov */void         *msg_control;    /* ancillary data, see below */size_t        msg_controllen; /* ancillary data buffer len */int           msg_flags;      /* flags on received message */
};
  1. msg_name(消息地址):

    用于指定消息的目标地址,通常在发送消息时为sendmsg提供目标地址信息,而在接收消息时,可以存储发送方的地址。通常设置为NULL,表示不指定目标地址。

  2. msg_namelen(地址长度):

    用于指定msg_name指向的地址结构的长度。通常在发送消息时为sendmsg提供地址结构的长度,而在接收消息时则用于存储实际接收到的地址的长度。

  3. msg_iov(I/O 向量):

    msg_iov是一个指向struct iovec结构的指针,该结构用于指定消息中的数据缓冲区,可以是多个缓冲区。通过msg_iovlen来指定缓冲区数组的长度。

    struct iovec {void  *iov_base; /* 指向缓冲区的起始地址 */size_t iov_len;  /* 缓冲区的大小 */
    };
    
  4. msg_iovlen(I/O 向量长度):

    用于指定msg_iov指向的缓冲区数组的长度,即消息中包含多少个缓冲区。

    • 所以sendmsg/recvmsgsendto/recvfrom最明显的不同是,前者可以通过msg_iovmsg_iovlen发送/接收多个缓冲区,而后者只能发送/接收一个。
  5. msg_control(控制信息):

    msg_control用于传递辅助信息,通常是控制信息或者辅助数据。这可以包括在套接字编程中使用的辅助信息,如辅助文件描述符等。通常设置为NULL,表示不传递控制信息。

  6. msg_controllen(控制信息长度):

    用于指定 msg_control 指向的控制信息的长度。在发送消息时,为sendmsg提供控制信息的长度,而在接收消息时,用于存储实际接收到的控制信息的长度。

  7. msg_flags(消息标志):

    用于存储消息的标志,包括一些操作的状态信息。在recvmsg函数中,可以通过msg_flags获取一些接收消息时的状态信息。

2.3 cmsghdr结构体

在使用msg_control时,通常会搭配使用struct cmsghdr结构,该结构定义了一种通用的、可扩展的辅助数据头部。

struct cmsghdr {socklen_t cmsg_len;    /* 辅助数据的总长度 */int       cmsg_level;  /* 源层协议,一般设置为 SOL_SOCKET */int       cmsg_type;   /* 辅助数据的类型 *//* 后续紧随辅助数据 *///unsigned char cmsg_data[];
};

cmsg_level常见取值:

  1. SOL_SOCKET 表示这是与套接字相关的辅助数据。
  2. 自定义层级: 除了 SOL_SOCKET,还可以定义其他自定义的层级,用于特定的应用或协议

cmsg_type 常见取值(对于SOL_SOCKET层级):

  1. SCM_RIGHTS 表示辅助数据用于传递文件描述符。
  2. SCM_CREDENTIALS 表示辅助数据用于传递进程凭证(例如用户标识)。

在Linux中提供了一些宏定义来使用这个结构体:

  1. CMSG_FIRSTHDR宏: 获取消息头的第一个辅助数据块。如果消息头中没有足够的空间来存储一个struct cmsghdr,则返回 NULL

    #define CMSG_FIRSTHDR(mhdr) ((mhdr)->msg_controllen >= sizeof(struct cmsghdr) ? (struct cmsghdr *)(mhdr)->msg_control : NULL)
    
  2. CMSG_NXTHDR宏: 获取下一个辅助数据块。通过传递当前的辅助数据块,可以获取下一个辅助数据块的指针。如果没有下一个块,返回 NULL

    #define CMSG_NXTHDR(mhdr, cmsg) ((char *)(cmsg) + CMSG_ALIGN((cmsg)->cmsg_len) + sizeof(struct cmsghdr) > (char *)(mhdr)->msg_control + (mhdr)->msg_controllen ? NULL : (struct cmsghdr *)((char *)(cmsg) + CMSG_ALIGN((cmsg)->cmsg_len)))
    
  3. CMSG_DATA宏: 获取辅助数据块中实际数据的指针。通过传递辅助数据块的指针,可以获取实际数据的起始位置。

    #define CMSG_DATA(cmsg) ((unsigned char *)(cmsg) + CMSG_ALIGN(sizeof(struct cmsghdr)))
    
  4. CMSG_LEN宏: 计算一个辅助数据块的总长度,包括头部和实际数据。

    • 它的值等于结构体cmsghdr中的cmsg_len字段的值
    #define CMSG_LEN(len) (_CMSG_HDR_ALIGN(sizeof(struct cmsghdr)) + (len))
    
  5. CMSG_SPACE宏: 计算辅助数据块所需的总空间,包括头部和实际数据,并进行对齐

    #define CMSG_SPACE(len) (_CMSG_HDR_ALIGN(sizeof(struct cmsghdr)) + _CMSG_DATA_ALIGN(len))
    

    如下图所示,为msg_control字段的示意图

在这里插入图片描述

  • 图中的pad是为了字节对齐的填充部分

2.4 实例

上面的理论挺复杂的,理论还是得通过实践才能更好的理解。

目的:使用多个struct iovec来发送和接收一个缓冲区的数据,并在msg_control字段中传递文件描述符作为辅助数据。

2.4.1 初始化

首先声明两个buffer,这里设置buffer1的初始值为0xab,而buffer2的初始值为0xcd,为了后续判断内容是否成功接收。然后创建用于父子进程通信的套接字,并fork子进程。

#define BUF_SIZE 1024
unsigned char buffer1[BUF_SIZE], buffer2[BUF_SIZE];
memset(buffer1, 0xab, sizeof(buffer1));
memset(buffer2, 0xcd, sizeof(buffer2));int sockfd[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
pid_t pid = fork();

2.4.2 子进程实现

子进程发送buffer1的内容给父进程,同时在辅助信息中传递一个文件描述符。

(1)声明msghdr结构体

struct msghdr message = {0};

(2)填充发送缓冲区

首先填充我们要发送的字段:struct iovec *msg_iov,这里声明一个字段iov[1],内容为buffer1

struct iovec iov[1];
iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1);message.msg_iov = iov;
message.msg_iovlen = 1;

(3)填充辅助信息

我们希望通过辅助信息传递文件描述符,首先声明辅助信息字段:

char control_data[CMSG_SPACE(sizeof(int))];message.msg_control = control_data;
message.msg_controllen = sizeof(control_data);
  • control_data用于存储辅助数据。CMSG_SPACE(sizeof(int))用于计算辅助数据所需的总空间,包括头部和实际数据的空间,并进行对齐。我们现在想传递一个文件描述符(int类型),所以使用 sizeof(int) 计算其大小。

填充辅助信息字段:

struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;int file_descriptor = open("example.txt", O_RDONLY);
*((int *)CMSG_DATA(cmsg)) = file_descriptor;

这里的CMSG_FIRSTHDR(&message)实际上就指向message.msg_control,通过这个宏定义强制转换,然后填充cmsg_lencmsg_levelcmsg_type字段。

  • cmsg_level设置为SOL_SOCKET,表示这是一个与套接字相关的辅助数据
  • cmsg_type设置为SCM_RIGHTS, 表示这是一个用于传递文件描述符的辅助数据块
  • *((int *)CMSG_DATA(cmsg))设置cmsgdata字段为文件描述符,这里打开目录下的example.txt文件

(4)发送数据

sendmsg(sockfd[1], &message, 0);

2.4.3 父进程实现

父进程则接收子进程发来的消息

(1)声明接收缓冲区和辅助信息结构体

接收和发送的数据大小要匹配,这里设置接收的iov_basebuffer2

struct iovec iov[1];
iov[0].iov_base = buffer2;
iov[0].iov_len = sizeof(buffer2);char control_data[CMSG_SPACE(sizeof(int))];
struct msghdr message = {0};
message.msg_iov = iov;
message.msg_iovlen = 1;
message.msg_control = control_data;
message.msg_controllen = sizeof(control_data);

(2)接收消息

recvmsg(sockfd[0], &message, 0);

(3)打印接收缓冲区内容

这里就打印前4字节的内容,如果接收成功buffer2的内容应该为0xab,而不是0xcd。

printf("buffer2[0]~buffer2[4] = %x %x %x %x\n", buffer2[0], buffer2[1], buffer2[2], buffer2[3]);

(4)使用辅助信息中的文件描述符

这里得到子进程传过来的文件描述符,然后打开这个文件并读取到buffer2中,然后输出文件的内容。

// 从辅助数据中获取文件描述符
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message);
int received_fd;
memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));ssize_t bytes_read = read(received_fd, buffer2, sizeof(buffer2));

2.4.4 实验结果

首先我们需要在目录下创建一个example.txt文件,随便输入一点内容:

在这里插入图片描述

接着我们运行程序,实验结果如下:

在这里插入图片描述

符合我们的预期。

2.4.5 完整代码

#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define BUF_SIZE 1024int main() {int sockfd[2];unsigned char buffer1[BUF_SIZE], buffer2[BUF_SIZE];memset(buffer1, 0xab, sizeof(buffer1));memset(buffer2, 0xcd, sizeof(buffer2));if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) == -1) {perror("Error creating socket pair");return -1;}pid_t pid = fork();if (pid == -1) {perror("Error forking");return -1;}if (pid == 0) {// 子进程 (发送方)close(sockfd[0]);  // 关闭子进程中不需要的读端// 打开文件并获取文件描述符int file_descriptor = open("example.txt", O_RDONLY);if (file_descriptor == -1) {perror("Error opening file");return -1;}// 准备消息struct iovec iov[1];iov[0].iov_base = buffer1;iov[0].iov_len = sizeof(buffer1);char control_data[CMSG_SPACE(sizeof(int))];struct msghdr message = {0};message.msg_iov = iov;message.msg_iovlen = 1;message.msg_control = control_data;message.msg_controllen = sizeof(control_data);// 构建控制信息头部struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message);cmsg->cmsg_len = CMSG_LEN(sizeof(int));cmsg->cmsg_level = SOL_SOCKET;cmsg->cmsg_type = SCM_RIGHTS;// 将文件描述符复制到辅助数据中*((int *)CMSG_DATA(cmsg)) = file_descriptor;// 发送消息if (sendmsg(sockfd[1], &message, 0) == -1) {perror("Error sending message");close(file_descriptor);return -1;}close(file_descriptor);  // 不再需要文件描述符close(sockfd[1]);  // 关闭写端} else {// 父进程 (接收方)close(sockfd[1]);  // 关闭父进程中不需要的写端struct iovec iov[1];iov[0].iov_base = buffer2;iov[0].iov_len = sizeof(buffer2);char control_data[CMSG_SPACE(sizeof(int))];struct msghdr message = {0};message.msg_iov = iov;message.msg_iovlen = 1;message.msg_control = control_data;message.msg_controllen = sizeof(control_data);// 接收消息if (recvmsg(sockfd[0], &message, 0) == -1) {perror("Error receiving message");return -1;}printf("buffer2[0]~buffer2[4] = %x %x %x %x\n", buffer2[0], buffer2[1], buffer2[2], buffer2[3]);// 从辅助数据中获取文件描述符struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message);int received_fd;memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));// 读取文件内容printf("Received file descriptor: %d\n", received_fd);ssize_t bytes_read = read(received_fd, buffer1, sizeof(buffer1));if (bytes_read == -1) {perror("Error reading file");return -1;}// 打印文件内容printf("Received data from file: %.*s\n", (int)bytes_read, buffer1);close(received_fd);  // 关闭接收到的文件描述符close(sockfd[0]);    // 关闭读端}return 0;
}

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

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

相关文章

绝地求生:NH究极天命圈惊险吃鸡,17斩获单日积分第一,4AM梦游暂居倒数

绝地求生PGC小组赛B组第一轮的比赛即将结束&#xff0c;在刚刚那场决赛圈中NH和17给我们上演了非常精彩的较量&#xff0c;双方战至最后一人&#xff0c;最终由NH击败17收获一鸡&#xff0c;这两支PCL老牌强队总分都超过了40分&#xff0c;晋级胜者组对他们来说压力不大了。新队…

【FPGA图像处理实战】- FPGA图像处理仿真测试工程(读写BMP图片)

FPGA开发过程中“行为功能仿真”是非常必要的一个过程&#xff0c;如果仿真都没通过&#xff0c;则上板测试必定失败。 FPGA图像处理需要读写大量的图像数据&#xff0c;单看这些图像数据实际是没有规则的&#xff0c;如果直接上板测试&#xff0c;调试起来非常困难&#xff0…

看图学源码之— HashMap源码分析

简介&#xff1a; 是基于 哈希表 实现的&#xff0c;存放 k-v 键值对&#xff0c;非同步的方式&#xff08;未加 synchronized &#xff09;非线程安全的&#xff0c;hashmap 无序的数据结构&#xff1a; 数组 链表 > 数组 链表 红黑树「链表 和 链表 红黑树 都是为了解…

QT+Unity3D 超详细(将unity3D与QT进行连接,并实现信息传递)

QTUnity3D连接 在QT中连接unity3D&#xff0c;首先要有一个unity.exe执行文件。在这里不提供unity执行文件的编写&#xff0c;只提供QT这边与unity3D连接和信息传递。 创建项目 创建一个新的项目&#xff0c;我创建的项目名称如下。 下图是我建立新项目的文件。APP文件就是…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之Linux文件管理(1)》(25)

《Linux操作系统原理分析之Linux文件管理&#xff08;1&#xff09;》&#xff08;25&#xff09; 8 Linux文件管理8.1 Linux 文件系统概述8.2 EXT2 文件系统8.2.1 EXT2 文件系统的构造8.2.2 EXT2 超级块&#xff08;super block&#xff09;8.2.3 组描述符8.2.4 块位图 8.3 EX…

智能优化算法应用:基于社交网络算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于社交网络算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于社交网络算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.社交网络算法4.实验参数设定5.算法结果6.参考…

用23种设计模式打造一个cocos creator的游戏框架----(七)代理模式

1、模式标准 模式名称&#xff1a;代理模式 模式分类&#xff1a;结构型 模式意图&#xff1a;为其他对象提供一种代理以控制对这个对象的访问。 结构图&#xff1a; ​ 适用于&#xff1a; 远程代理&#xff1a;也称为大使&#xff0c;这是最常见的类型&#xff0c;在分…

2022年第十一届数学建模国际赛小美赛C题人类活动分类解题全过程文档及程序

2022年第十一届数学建模国际赛小美赛 C题 人类活动分类 原题再现&#xff1a; 人类行为理解的一个重要方面是对日常活动的识别和监控。可穿戴式活动识别系统可以改善许多关键领域的生活质量&#xff0c;如动态监测、家庭康复和跌倒检测。基于惯性传感器的活动识别系统用于通过…

Kubernetes入门笔记——(2)k8s设计文档

​k8s最初源自谷歌的Brog项目&#xff0c;架构与其类似&#xff0c;主要包括etcd、api server、controller manager、scheduler、kubelet和kube-proxy等组件 etcd&#xff1a;分布式存储&#xff0c;保存k8s集群的状态 api server&#xff1a;资源操作的唯一入口&#xff0c;…

Kafka 的消息格式:了解消息结构与序列化

Kafka 作为一款高性能的消息中间件系统&#xff0c;其消息格式对于消息的生产、传输和消费起着至关重要的作用。本篇博客将深入讨论 Kafka 的消息格式&#xff0c;包括消息的结构、序列化与反序列化&#xff0c;以及一些常用的消息格式选项。通过更丰富的示例代码和深入的解析&…

2023年山东省职业院校技能大赛信息安全管理与评估二三阶段样题

2023年山东省职业院校技能大赛信息安全管理与评估二三阶段 样题 第二阶段 模块二 网络安全事件响应、数字取证调查、应用程序安全 一、竞赛内容 Geek极安云科专注技能竞赛技术提升&#xff0c;基于各大赛项提供全面的系统性培训&#xff0c;拥有完整的培训体系。团队拥有曾…

docker部署elasticsearch8.x

docker部署elasticsearch8.x 提示1 注意版本差别1.1 docker修改配置1.1.2 docker使用vim报命令不存在的解决办法1.1.3 docker 容器内报错 E: List directory /var/lib/apt/lists/partial is missing. - Acquire ( : No such file or directory) 或者其他权限 PermissionError: …

【Delphi】一个函数实现ios,android震动功能 Vibrate(包括3D Touch 中 Peek 震动等)

一、前言 我们在开发移动端APP的时候&#xff0c;有时可能需要APP能够提供震动功能&#xff0c;以便提醒操作者&#xff0c;特别是ios提供的3D Touch触感功能&#xff0c;操作者操作时会有触感震动&#xff0c;给操作者的感觉很友好。那么&#xff0c;在Delphi的移动端FMX开发中…

团建策划信息展示服务预约小程序效果如何

团建是中大型企业商家每年举办的员工活动&#xff0c;其形式多样化、具备全部参与的娱乐性。但在实际策划流程及内容时&#xff0c;部分公司便会难以入手&#xff0c;术业有专攻&#xff0c;这个时候团建策划公司便会发挥效果。 如拓展训练、露营、运动会、体育竞技等往往更具…

【算法】算法题-20231207

这里写目录标题 一、共同路径二、数字列表排序三、给定两个整数 n 和 k&#xff0c;返回 1 … n 中所有可能的 k 个数的组合。 一、共同路径 给你一个完整文件名组成的列表&#xff0c;请编写一个函数&#xff0c;返回他们的共同目录路径。 # nums[/hogwarts/assets/style.cs…

算法通关村第十七关-黄金挑战跳跃问题

大家好我是苏麟 , 今天说说跳跃问题 . 跳跃游戏 描述 : 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff…

HBase-架构与设计

HBase架构与设计 一、背景二、HBase概述1.设计特点2.适用场景2.1 海量数据2.2 稀疏数据2.3 多版本数据2.4 半结构或者非结构化数据 三、数据模型1.RowKey2.Column Family3.TimeStamp 四、HBase架构图1.Client2.Zookeeper3.HMaster4.HRegionServer5.HRegion6.Store7.StoreFile8.…

Elasticsearch:什么是机器学习?

机器学习定义 机器学习 (ML) 是人工智能 (AI) 的一个分支&#xff0c;专注于使用数据和算法来模仿人类的学习方式&#xff0c;并随着时间的推移逐渐提高准确性。 计算机科学家和人工智能创新者 Arthur Samuel 在 20 世纪 50 年代首次将其定义为 “赋予计算机无需明确编程即可学…

【基于openGauss5.0.0简单使用DBMind】

基于openGauss5.0.0简单使用DBMind 一、环境说明二、初始化tpch测试数据三、使用DBMind索引推荐功能四、使用DBMind实现SQL优化功能 一、环境说明 虚拟机&#xff1a;virtualbox操作系统&#xff1a;openEuler 20.03 TLS数据库&#xff1a;openGauss-5.0.0DBMind&#xff1a;d…

2022年第十一届数学建模国际赛小美赛A题翼龙如何飞行解题全过程文档及程序

2022年第十一届数学建模国际赛小美赛 A题 翼龙如何飞行 原题再现&#xff1a; 翼龙是翼龙目中一个已灭绝的飞行爬行动物分支。它们存在于中生代的大部分时期&#xff1a;从三叠纪晚期到白垩纪末期。翼龙是已知最早进化出动力飞行的脊椎动物。它们的翅膀是由皮肤、肌肉和其他组…