网络编程 - Linux socket编程

news2024/10/9 2:29:48

前言

  socket(套接字)是网络编程编程的一种技巧。通过socket不仅可以实现跨进程通信,还可以实现跨主机的网络通信。使用这种技术,就可以实现全国各地的通讯。例如:深圳的一台电脑接收来自北京一台电脑发来的信息。
  本篇不涉及太底层的网络原理,仅说明socket的基本使用方法。主要参考《Linux网络编程》。

Socket的功能

  socket是通过标准的UNIX文件描述符和其他的程序通讯的一个方法。其可以实现同一主机不同进程间的通信;也可以实现不同主机间的通信。

Socket基础

Socket类型

  套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)和原始套接字。

流式套接字(SOCK_STREAM)
  流式的套接字可以提供可靠的、面向连接的通讯流。如果你通过流式套接字发送了顺序的数据:"1"、"2"。那么数据到达远程时候的顺序也是"1"、"2"。

面向连接的Socket工作流程

数据报套接字(SOCK_DGRAM)
  数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。

无连接的socket工作流程

原始套接字(SOCK_RAM)
  原始套接字主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。

基本结构

  • struct sockaddr
    此结构用于存储套接字地址。在需要通信地址时,此结构体会被用到,例如connect()
struct sockaddr {
    unsigned short sa_family; /* address族, AF_xxx */
    char sa_data[14];         /* 14 bytes的协议地址 */
};

sa_family为指定协议族,通常使用到的有AF_INET、AF_UNIX等。sa_data为不同协议族通信时必要的数据。例如,sa_family为AF_INET时,sa_data要传IP地址和端口号。

  • struct sockaddr_insockaddr_in是用于存储AF_INET的套接字地址,其中in就代表Inet。
      介绍sockaddr时,说到在使用AF_INET需要传IP和端口号,但并不知道要将IP和端口号填到sockaddr的哪个地方。于是,设计了sockaddr_in,定义出地址和端口号成员。在使用时只需要填充sockaddr_in,传参时强转为sockaddr即可(两个结构体大小一致)。
struct sockaddr_in {
    short int sin_family;        /* Internet地址族 */
    unsigned short int sin_port; /* 端口号 */
    struct in_addr sin_addr;     /* Internet地址 */
    unsigned char sin_zero[8];   /* 添0(和struct sockaddr一样大小) */
};
  • struct in_addrin_addrsockaddr_in成员,用于存储4个字节的IP地址。需要注意的是,此值填写时需要按照网络字节来填充,可以通过一些转换函数完成。
struct in_addr {
    unsigned long s_addr;
};
  • struct sockaddr_unsockaddr_un是用于存储AF_UNIX的套接字地址,推测un就代表UNIX(没有求证)。
      和上述类似,当使用AF_UNIX时,需要填充AF_UNIX的地址结构体sockaddr_un,然后传参时强转为sockaddr
struct sockaddr_un
{
    uint8_t sun_len;
    sa_family_t sun_family;  /* AF_LOCAL */
    char sun_path[104];      /* null-terminated pathname */
};

基本转换函数

网络字节序转换
  上文描述填写IP和端口时要注意网络字节序,否则可能连接不到指定的设备。系统提供了如下几种函数方便转换:

  • htons()—— “Host to Network Short” 主机字节顺序转换为网络字节顺序(对无符号短型进行操作 4 bytes)
  • htonl()—— “Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符号长型进行操作 8 bytes)
  • ntohs()—— “Network to Host Short” 网络字节顺序转换为主机字节顺序(对无符号短型进行操作 4 bytes)
  • ntohl()—— “Network to Host Long” 网络字节顺序转换为主机字节顺序(对无符号长型进行操作 8 bytes)

IP地址转换

  • inet_addr()—— 把一个用数字和点表示 IP 地址的字符串转换成网络字节序的无符号长整型。
  • inet_ntoa()—— “Network to ASCII” 将网络字节序的长整型转换成字符串。

inet_ntoa()返回一个字符指针,它指向一个定义在函数 inet_ntoa() 中的 static 类型字符串。所以每次调用 inet_ntoa(),都会改变最后一次调用 inet_ntoa() 函数时得到的结果。

基本Socket使用

  Linux同时支持面向连接和不连接类型的套接字。在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接;在不连接通讯中数据被作为信息的一部分被交换。
  无论那一种方式,服务器总是最先启动,把自己绑定(Banding)在一个套接字上,然后侦听信息。

socket主要使用到如下函数:

  • socket()函数 —— 创建套接字。
  • bind()函数 —— 绑定socket地址信息。(Inet需要传入IP、端口;Unix 需要传入路径)
  • connect()函数 —— 连接指定服务器套接字。
  • listen()函数 —— 服务器监听连接上的套接字客户端。
  • accept()函数 —— 接受远程客户端套接字,会获取到远程连接客户端的地址信息。(阻塞接口)
  • send()函数/recv()函数 —— 连接的流式套接字进行通讯的函数。
  • sendto()函数/recvfrom()函数 —— 非连接的数据报套接字进行通讯的函数。
  • close()函数 —— 关闭套接字描述符所表示的连接。
  • shutdown()函数 —— 指定关闭套接字的方式。
  • setsockopt()函数/getsockopt()函数 —— 套接字设置项的设置和获取。
  • getpeername()函数 —— 取得一个已经连接上的套接字的远程信息。
  • getsockname()函数 —— 取得本地主机的信息。
  • gethostbyname()函数 —— 通过域名获取IP地址。
  • gethostbyaddr()函数 —— 通过一个IPv4的地址来获取主机信息。
  • getprotobyname()函数 —— 获得网络协议名。

TCP Socket实例

  TCP Socket可以理解为Inet使用流式套接字,为保证通讯稳定而采用TCP协议。其优点在于可靠、稳定。

TCP Socket 服务端

void DealClientMsgThread(int fd)
{
    while(1)
    {
        char buf[1024] = {0};
        int ret = read(fd, buf, sizeof(buf));
        if (ret > 0) {
            TSVR_LOG("# RECEIVE: %s.\n", buf);

            // response dstAddr msg
            char ack[20] = {0};
            snprintf(ack, sizeof(ack), "Ack [%ld]", strlen(buf));
            write(fd, ack, strlen(ack));
        } else {
            break;
        }
    }
    
    TSVR_LOG("Disconnect.\n");
}

int main(int argc, char *argv[])
{
    int i = 0;
    std::thread threads[MAX_NUM_THREAD];

    if (argc != 2)
    {
        TSVR_LOG("usage ./tcp_server 8080.\n");
        return -1;
    }

    string port = argv[1];

    int sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd == -1)
    {
        TSVR_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }

    int op = 1;
    if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
        TSVR_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        goto exit;
    }

    struct sockaddr_in myAddr;
    bzero(&myAddr, sizeof(myAddr));
    myAddr.sin_family = AF_INET;
    myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myAddr.sin_port = htons(atoi(port.c_str()));
    if (bind(sockFd, (struct sockaddr *)&myAddr, sizeof(myAddr)) == -1) {
        TSVR_LOGE("bind failed. (%s)", strerror(errno));
        goto exit;
    }

    if (listen(sockFd, MAX_SIZE_BACKLOG) == -1) {
        TSVR_LOGE("listen failed. (%s)\n", strerror(errno));
        goto exit;
    }

    while(1)
    {
        // accept
        struct sockaddr_in dstAddr;
        socklen_t addrSize = (socklen_t)sizeof(dstAddr);
        int conFd = accept(sockFd, (struct sockaddr *)&dstAddr, &addrSize);
        if (conFd < 0) {
            TSVR_LOGE("accept failed. (%s)\n", strerror(errno));
            continue;
        }
        
        if (i < MAX_NUM_THREAD)
        {
            threads[i] = std::thread(DealClientMsgThread, conFd);
            threads[i].detach();
            i++;
        } else {
            TSVR_LOG("The number of threads reaches max(%d).\n", MAX_NUM_THREAD);
        }
    }

exit:
    close(sockFd);
    return 0;
}

TCP Socket 客户端

int main(int argc, char *argv[])
{
    int op = 1024;

    if (argc != 3)
    {
        TCLT_LOG("usage ./tcp_client <ip> <port>.\n");
        return -1;
    }

    string ipAddr = argv[1];
    string port = argv[2];

    int sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd == -1)
    {
        TCLT_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }

    if (setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &op, sizeof(op)) < 0) {
        TCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        close(sockFd);
        return -1;
    }

    if (setsockopt(sockFd, SOL_SOCKET, SO_SNDBUF, &op, sizeof(op)) < 0) {
        TCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        close(sockFd);
        return -1;
    }

    struct sockaddr_in dstAddr;
    dstAddr.sin_family = AF_INET;
    dstAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str());
    dstAddr.sin_port = htons(atoi(port.c_str()));
    // Linux TCP repeat connect directly return EISCONN.
    if (   connect(sockFd, (struct sockaddr*)&dstAddr, sizeof(struct sockaddr_in)) < 0 
        && errno != EISCONN) 
    {
        TCLT_LOGE("connect %s:%d failed. (%s)\n", ipAddr.c_str(), atoi(port.c_str()), strerror(errno));
        close(sockFd);
        return -1;
    }

    thread rTh([&]() {
        char recvBuf[1024] = {0};
        TCLT_LOG("Start write thread.\n");

        while(1)
        {
            int ret = read(sockFd, recvBuf, sizeof(recvBuf));
            if (ret > 0) {
                TCLT_LOG("# RECEIVE: %s.\n", recvBuf);
            } else {
                break;
            }
        }
    });
    
    thread wTh([&]() {
        TCLT_LOG("Start read thread.\n");

        while(1)
        {
            char sndBuf[] = "Hello World";
            int ret = write(sockFd, sndBuf, strlen(sndBuf));
            if (ret > 0)
            {
                TCLT_LOG("# SEND > %s.\n", sndBuf);
            } else {
                break;
            }
            sleep(2);
        }
    });
    
    rTh.join();
    wTh.join();

    return 0;
}

代码是很简单的TCP通讯处理,为方便理解,不做过多封装。

UDP Socket实例

  UDP Socket可以理解为Inet使用数据报套接字,为了快速通讯,客户端与服务端约定采用的UDP的套接字通讯。

UDP Socket 服务端

int main(int argc, char *argv[])
{
    struct sockaddr_in dstAddr;
    socklen_t addrSize = sizeof(struct sockaddr_in);

    if (argc != 2)
    {
        USVR_LOG("usage ./udp_server 8080.\n");
        return -1;
    }
    
    string port = argv[1];

    int sockFd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockFd == -1)
    {
        USVR_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }
    
    int op = 1;
    if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
        USVR_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        goto exit;
    }

    struct sockaddr_in myAddr;
    bzero(&myAddr, sizeof(struct sockaddr_in));
    myAddr.sin_family = AF_INET;
    myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myAddr.sin_port = htons(atoi(port.c_str()));
    if (bind(sockFd, (struct sockaddr *)&myAddr, sizeof(struct sockaddr_in)) < 0) {
        USVR_LOGE("bind failed. (%s)\n", strerror(errno));
        goto exit;
    }

    while(1)
    {
        char buf[1024] = {0};
        int ret = recvfrom(sockFd, buf, sizeof(buf), 0, 
                    (struct sockaddr *)&dstAddr, &addrSize);

        if (ret > 0) {
            char ack[20] = {0};

            snprintf(ack, sizeof(ack), "Ack [%ld]", strlen(buf));
            USVR_LOG("# RECEIVE: %s.\n", buf);
            sendto(sockFd, ack, strlen(ack), 0, 
                    (struct sockaddr *)&dstAddr, addrSize);

        } else {
            USVR_LOG("recvfrom failed. (%s)\n", strerror(errno));
            break;
        }
    }

exit:
    close(sockFd);
    return 0;
}

UDP Socket 客户端

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        UCLT_LOG("usage ./udp_client <ip> <port>.\n");
        return -1;
    }

    string ipAddr = argv[1];
    string port = argv[2];

    int sockFd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockFd == -1)
    {
        UCLT_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }

    int op = 1024;
    if (setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &op, sizeof(op)) < 0) {
        UCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        close(sockFd);
        return -1;
    }

    struct sockaddr_in dstAddr;
    bzero(&dstAddr, sizeof(struct sockaddr_in));
    dstAddr.sin_family = AF_INET;
    dstAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str());
    dstAddr.sin_port = htons(atoi(port.c_str()));

    thread wTh([&]() {
        UCLT_LOG("Start write thread.\n");

        while(1)
        {
            char sndBuf[] = "Hello World";
            int ret = sendto(sockFd, sndBuf, strlen(sndBuf), 0, 
                        (struct sockaddr *)&dstAddr, sizeof(struct sockaddr_in));

            if (ret > 0) {
                UCLT_LOG("# SEND: %s.\n", sndBuf);
            } else {
                break;
            }

            sleep(2);
        }
    });
    
    thread rTh([&]() {
        struct sockaddr_in dstAddr;
        UCLT_LOG("Start read thread.\n");

        while(1)
        {
            char recvBuf[1024] = {0};
            socklen_t addrSize = sizeof(struct sockaddr_in);
            bzero(&dstAddr, addrSize);
            int ret = recvfrom(sockFd, recvBuf, sizeof(recvBuf), 0, 
                        (struct sockaddr *)&dstAddr, &addrSize);

            if (ret > 0)
            {
                UCLT_LOG("# RECEIVE: %s.\n", recvBuf);
            } else {
                break;
            }
        }
    });
    
    wTh.join();
    rTh.join();
    return 0;
}

疑难问题记录

  • TCP服务端和客户端如何精确地检测到对方下线或异常断开?
    ① 接收函数是阻塞的,当对方断开接收函数会返回异常。
    ② 通过错误码和信号判断,当一端异常断开,另一端会收到SIGPIPE信号,再通过getsockopt查询各个套接字确认哪一个断开。

  • UDP 如何保证通讯数据稳定
    UDP通讯因为不需要连接,从而比TCP通讯更快,但是由于其不确保数据是否安全到达,从而显得不靠谱。为了实现通讯靠谱,需要在应用层建立一套机制验证数据准确性,建立失误重传的约定。

总结

  • socket的实现非常优秀,将复杂的网络通信,封装成简单的socket的接口。使用者不用过多考虑TCP、UDP以及其他较底层的网络概念,而快速的实现一套网络通讯的流程。
  • 本文仅列举了socket用于inet地址族的例程,其还可以用于UNIX域的进程间通讯。
  • 网络编程非常有趣,能够实现天南海北之间的通讯,让远距离的人与人、人与物或者物与物之间产生联系,很有意思!

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

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

相关文章

hudi系列-索引机制

1. 索引机制 hudi的索引机制是为了加速upsert/delete操作&#xff0c;它维护着&#xff08;分区 key&#xff09;-> fileID之间的映射关系&#xff0c;所以可以减少对非必要base文件的合并 key是指索引key&#xff0c;可以是表的任意字段&#xff0c;在全局索引中常用主键…

Linux chmod命令详解,Linux修改文件权限

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 chmod 命令一、常用操作1. 字母形式2. 数字形式3. 递归设置二、文件权限解读三、数字权限四、特殊的root权限五、SUID详解1. 设置SUID2. 取消…

chatgpt小程序版本来了,解决你们手机上想用用不了的问题,chatgpt接口用到小程序里面,调用openai接口,提供前后端源码,可以私有部署使用

现在的chatgpt被玩的都开始加广告&#xff0c;又办会员什么的的&#xff0c;今天就把小程序的前后端无广告版本源码和部署方式说一下。 先看效果&#xff1a; 部署环境 前端用的uniapp&#xff0c;基础模版&#xff0c;单页面没有太多引用 后端使用的python的falsk框架&#…

javassist学习

Java 字节码以二进制的形式存储在 .class 文件中&#xff0c;每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法&#xff0c;或者是修改已有的方法&#xff0c;并且不需要对字节码方面有深…

第29届中国国际广告节,酷开科技喜提三奖

第29届中国国际广告节&#xff0c;酷开科技喜提三奖 12月21日至12月23日&#xff0c;在风景如画的鹭岛厦门&#xff0c;第29届中国国际广告节于厦门国际会展中心隆重举行。经过几十年的高歌前行&#xff0c;中国国际广告节业已成为业界的盛会&#xff0c;更是中国广告业发展的…

(十)VUEElement

一、 VUE 1. 概述 Vue 是一套前端框架&#xff0c;免除原生JavaScript中的DOM操作&#xff0c;简化书写。基于MVVM(Model-View-ViewModel)思想&#xff0c;实现数据的双向绑定&#xff0c;将编程的关注点放在数据上。官网&#xff1a;https://cn.vuejs.org 2. 快速入门 &#…

CRM管理系统哪个好用?这五款CRM系统值得推荐

选择一款合适的CRM系统&#xff0c;可以有效解决企业生产经营中分散的数据、不规则的工作流程、缺乏客户服务、销售预测不准确、远程工作效率低、错误的客户沟通、几乎没有销售和市场合作、报告不足等方面的业务难题。 CRM系统的选型可以从以下方面考虑&#xff1a; 下面分享几…

Faster RCNN网络源码解读(Ⅺ) --- 预测结果后处理及预测过程(完结撒花)

目录 一、回顾以及本篇博客内容概述 二、代码解析 2.1 ROIHead类&#xff08;承接上篇博客的2.1节&#xff09; 2.1.1 初始化函数 __init__回顾 2.1.2 正向传播forward回顾及预测结果后处理 2.1.3 postprocess_detections 2.2 FasterRCNNBase类前向传播过程 2.3 Genera…

MySQL Workbench基本用法

MySQL Workbench相当于SQL语言的解释器 目录 1 打开 2 连接数据库 3 创建数据库 4 创建数据表 4.1 字段类型 4.2 字段选项 4.3 其他 5 操作表中的数据 5.1 添加 5.2 更改 5.3 删除 6 代码编辑器 7 保存sql代码 8 加载sql代码 1 打开 搜索MySQL …

滑动窗口题型

先看一个题目&#xff1a;题目描述 题目描述&#xff1a;给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 "" 。 例如&#xff1a; 输入&#xff1a;s "ADOBECODE…

移动Web【rem、flexible、Less、vw / vh】

文章目录remflexibleLessvw / vhrem 网页效果 屏幕宽度不同&#xff0c;网页元素尺寸不同&#xff08;等比缩放&#xff09; px单位或百分比布局可以实现吗&#xff1f; px单位是绝对单位百分比布局特点宽度自适应&#xff0c;高度固定 适配方案 remvw / vh rem单位 相对单位…

Homekit智能家居DIY之智能通断开关

智能通断器&#xff0c;也叫开关模块&#xff0c;可以非常方便地接入家中原有开关、插座、灯具、电器的线路中&#xff0c;通过手机App或者语音即可控制电路通断&#xff0c;轻松实现原有家居设备的智能化改造。 随着智能家居概念的普及&#xff0c;越来越多的人想将自己的家改…

SQL SELECT DISTINCT 语句

SELECT DISTINCT 语句用于返回唯一不同的值。 SQL SELECT DISTINCT 语句 在表中&#xff0c;一个列可能会包含多个重复值&#xff0c;有时您也许希望仅仅列出不同&#xff08;distinct&#xff09;的值。 DISTINCT 关键词用于返回唯一不同的值。 SQL SELECT DISTINCT 语法 …

如何从零开始开发一个小程序

如何从零开始开发一个小程序开始申请账号开发设置开发工具新建小程序阅读文档模版语法项目架构开始编写基础语法wx:for循环wx:if判断页面导航自定义组件引用样式修改单行、多行省略flex布局grid布局发布开始 申请账号 小程序注册页 开发设置 登录 小程序后台 &#xff0c;…

【Vue2+Element ui通用后台】Mock.js

文章目录Mock.js首页数据调用mock数据并完成布局Mock.js Mock.js 官网 Mockjs Github地址 作用&#xff1a;生成随机数据&#xff0c;拦截 Ajax 请求 使用npm i mockjs进行安装&#xff0c;然后在 api 下新建 mock.js import Mock from mockjs// 定义mock请求拦截 Mock.mock…

【CPU是如何执行程序的?】

CPU是如何执行程序的&#xff1f;1、、硬件结构介绍1.1、CPU1.2、内存1.3、总线1.4、输入/输出设备二、程序执行的基本过程三、a11执行的详细过程现代计算机的基本结构为五个部分&#xff1a;CPU、内存、总线、输入/输出设备。或许你了解了这些概念&#xff0c;但是你知道a11在…

leetcode每日一题(python)2023-1.2 1801. 积压订单中的订单总数 (middle)

leetcode每日一题 &#x1f6a9; 学如逆水行舟&#xff0c;不进则退。 —— 《增广贤文》 题目描述&#xff1a; 给你一个二维整数数组 orders &#xff0c;其中每个 orders[i] [price(i), amount(i), orderType(i)] 表示有 amount(i)( )笔类型为 orderType(i) 、价格为 pric…

没错,这是2023年开篇!!

不知不觉小G和Gopher们一起相伴着又走过了一个年头&#xff0c;迎来了2023年。回望这一年我们有遗憾&#xff1a;因疫情的原因没能和大家在线下2022 GopherChina大会相聚一堂我们也有快乐与成就&#xff1a;在经过一番调查、统计、分析后发布了《2022 Q2 GO开发者问卷调查结果》…

阿里、京东、百度“激战”互联网医疗

与衣食住行一样&#xff0c;医疗需求同样对人们的生存具有重要意义。医疗行业经过多年的发展&#xff0c;也已经发生了翻天覆地的变化。除了线下医疗日益完善之外&#xff0c;互联网医疗也随着互联网的飞速发展而实现了快速起飞。现如今&#xff0c;互联网医疗已经逐步成为了线…

沿全尺寸模型水平轴 MHK 涡轮机(DOE RM1)叶片性能表征计算(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 该存储库包括 MATLAB 脚本和输入文件示例&#xff0c;用于沿全尺寸模型水平轴 MHK 涡轮机叶片计算 3D AOA、CL 和 CD。该脚本是…