【ESP32】ESP-IDF开发 | WiFi开发 | UDP用户数据报协议 + UDP客户端和服务器例程

1. 简介

        UDP协议(User Datagram Protocol),全称用户数据报协议,它是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接, 不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说UDP协议是一种不可靠的协议。UDP协议适用于一次只传送少量数据、对可靠性要求不高的应用环境。

        因为UDP的数据传输不一定是一对一的,所以也衍生出单播、组播和广播的概念。

1. 单播

        单播(unicast),是指封包在计算机网络的传输中,目的地址为单一目标的一种传输方式。它是现今网络应用最为广泛,通常所使用的网络协议或服务大多采用单播传输,例如TCP协议。

2. 多播

        组播(multicast),也叫多播或群播。 指把信息同时传递给一组目的地址。它使用策略是最高效的,因为消息在每条网络链路上只需传递一次,而且只有在链路分叉的时候,消息才会被复制。

3. 广播

        广播(broadcast),是指封包在计算机网络中传输时,目的地址为网络中所有设备的一种传输方式。

2. lwIP

         ESP-IDF使用lwIP库实现TCP/IP协议栈,这个库在大多数嵌入式系统中都有用到,它是一个轻量的TCP/IP协议层套件。

        支持以下特性:

  • IP (Internet Protocol, IPv4 and IPv6) including packet forwarding over multiple network interfaces
  • ICMP (Internet Control Message Protocol) for network maintenance and debugging
  • IGMP (Internet Group Management Protocol) for multicast traffic management
  • MLD (Multicast listener discovery for IPv6). Aims to be compliant with RFC 2710. No support for MLDv2
  • ND (Neighbor discovery and stateless address autoconfiguration for IPv6). Aims to be compliant with RFC 4861 (Neighbor discovery) and RFC 4862 (Address autoconfiguration)
  • DHCP, AutoIP/APIPA (Zeroconf) and (stateless) DHCPv6
  • UDP (User Datagram Protocol) including experimental UDP-lite extensions
  • TCP (Transmission Control Protocol) with congestion control, RTT estimation fast recovery/fast retransmit and sending SACKs
  • raw/native API for enhanced performance
  • Optional Berkeley-like socket API
  • TLS: optional layered TCP ("altcp") for nearly transparent TLS for any
  • TCP-based protocol (ported to mbedTLS) (see changelog for more info)
  • PPPoS and PPPoE (Point-to-point protocol over Serial/Ethernet)
  • DNS (Domain name resolver incl. mDNS)
  • 6LoWPAN (via IEEE 802.15.4, BLE or ZEP)

        支持以下应用层协议:

  • HTTP server with SSI and CGI (HTTPS via altcp)
  • SNMPv2c agent with MIB compiler (Simple Network Management Protocol), v3 via altcp
  • SNTP (Simple network time protocol)
  • NetBIOS name service responder
  • MDNS (Multicast DNS) responder
  • iPerf server implementation
  • MQTT client (TLS support via altcp)

3. 例程

         例程分别在ESP32上实现TCP客户端和服务端,使用电脑作为另一方进行简单通信测试。需要注意的是,测试时,ESP32和电脑必须处于同一局域网。 

        电脑端测试使用的上位机为VOFA+,下载地址:VOFA+

3.1 发送端

        这个例程配置ESP32为发送端,当连接WiFi热点成功后会向目标IP每隔1秒发送一段数据。

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "netdb.h"
#include "arpa/inet.h"#include <string.h>#define TAG "app"
#define HOST_IP_ADDR "172.16.10.26"
#define HOST_PORT 20001static const char *payload = "Hello from ESP32\r\n";
static TaskHandle_t client_task_handle;static void udp_client_task(void *args)
{struct sockaddr_in dest_addr;dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(HOST_PORT);int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);}ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, HOST_PORT);while (1) {int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err < 0) {ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);break;}ESP_LOGI(TAG, "Message sent");vTaskDelay(1000 / portTICK_PERIOD_MS);}close(sock);vTaskDelete(NULL);
}static void wifi_event_handler(void* arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{if (event_base == IP_EVENT) {if (event_id == IP_EVENT_STA_GOT_IP) {xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, &client_task_handle);}} else if (event_base == WIFI_EVENT) {if (event_id == WIFI_EVENT_STA_DISCONNECTED) {vTaskDelete(client_task_handle);} else if (event_id == WIFI_EVENT_STA_START) {esp_wifi_connect();}}
}int app_main()
{/* 初始化NVS */esp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}/* 初始化WiFi协议栈 */ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_sta();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));wifi_config_t wifi_config = {.sta = {.ssid = "QXL",.password = "88888888",.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());return 0;
}

        ESP32的WiFi驱动初始化在前面的文章已经有详细的介绍了,这里不再赘述。

        因为UDP是面向无连接的,所以编程会相对简单,流程如下:

1. 创建套接字

        调用socket函数。第一个参数表示IP协议,这里使用IPv4,对应IP_INET;第二个参数表示socket类型,UDP协议只能填SOCK_DGRAM;第三个参数表示协议栈类型,这里填IPPROTO_IP。函数会返回套接字描述符。

2. 套接字配置(可选)

        socket的接收和发送默认是阻塞的,即如果数据不到达就一直等待,所以根据应用的需要设置接收的超时时间。

        调用setsockopt函数。第一个参数为套接字描述符;第二个参数为协议层,必须填SOL_SOCKET;第三个参数为设置项,填SO_RCVTIMEO,设置超时时间,当然函数还支持以下配置项:

#define SO_DEBUG        0x0001 /* Unimplemented: turn on debugging info recording */
#define SO_ACCEPTCONN   0x0002 /* socket has had listen() */
#define SO_DONTROUTE    0x0010 /* Unimplemented: just use interface addresses */
#define SO_USELOOPBACK  0x0040 /* Unimplemented: bypass hardware when possible */
#define SO_LINGER       0x0080 /* linger on close if data present */
#define SO_DONTLINGER   ((int)(~SO_LINGER))
#define SO_OOBINLINE    0x0100 /* Unimplemented: leave received OOB data in line */
#define SO_REUSEPORT    0x0200 /* Unimplemented: allow local address & port reuse */
#define SO_SNDBUF       0x1001 /* Unimplemented: send buffer size */
#define SO_RCVBUF       0x1002 /* receive buffer size */
#define SO_SNDLOWAT     0x1003 /* Unimplemented: send low-water mark */
#define SO_RCVLOWAT     0x1004 /* Unimplemented: receive low-water mark */
#define SO_SNDTIMEO     0x1005 /* send timeout */
#define SO_RCVTIMEO     0x1006 /* receive timeout */
#define SO_ERROR        0x1007 /* get error status and clear */
#define SO_TYPE         0x1008 /* get socket type */
#define SO_CONTIMEO     0x1009 /* Unimplemented: connect timeout */
#define SO_NO_CHECK     0x100a /* don't create UDP checksum */
#define SO_BINDTODEVICE 0x100b /* bind to device */

        比较常用的就是SO_RCVTIMEO,设置接收超时;SO_SNDTIMEO,设置发送超时。上面带Unimlemented注释的是ESP-IDF不支持的配置

        每种配置项需要传入的数据是不一样的,用之前建议参考官方的说明文档。设置超时传入的是struct timeval这个结构体。

struct timeval {time_t		tv_sec;suseconds_t	tv_usec;
};

        tv_sec设置秒数,tv_usec设置微秒数。

3. 发送数据

        调用sendto函数。参数1为套接字描述符;参数2为数据指针;参数3为数据长度;参数4为标志位,有以下选项:

#define MSG_PEEK       0x01
#define MSG_WAITALL    0x02
#define MSG_OOB        0x04
#define MSG_DONTWAIT   0x08
#define MSG_MORE       0x10
#define MSG_NOSIGNAL   0x20

        这些标志位是发送和接收都支持的,比较常用的是MSG_DONTWAIT,像发送和接收函数是阻塞的,使能这个标志位可以让函数立即返回,不等待数据。

        参数5为目标地址信息,结构体如下:

struct sockaddr_in {u8_t            sin_len;sa_family_t     sin_family;in_port_t       sin_port;struct in_addr  sin_addr;
#define SIN_ZERO_LEN 8char            sin_zero[SIN_ZERO_LEN];
};
#endif /* LWIP_IPV4 */
  • sin_len:数据长度(一般不需要填);
  • sin_family:套接字类型,IPv4填AF_INET,IPv6填AF_INET6,其他填AF_UNSPEC;
  • sin_port:端口;
  • sin_zero:预留字节(不用管)。

        参数6为目标地址结构体长度。

4. 关闭连接

        调用close函数。传入套接字描述符即可。

        使用上位机时,数据引擎选择“RawData”,数据接口选择UDP,远程IP填写ESP32获取到的IP,本地端口要与ESP32发送报文的端口一致。设置好后,点击左上角的圆形按钮即可启动连接,在下面文本框能看到接收到的消息。

2.2 接收端

        这个例程创建一个UDP接收端,接收所有发送至指定端口的UDP包。 

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "netdb.h"
#include "arpa/inet.h"#include <string.h>#define TAG "app"
#define SERVER_PORT 20001static TaskHandle_t server_task_handle;static void udp_server_task(void *args)
{char rx_buffer[128];struct sockaddr_in dest_addr;dest_addr.sin_addr.s_addr = htonl(IPADDR_ANY);dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(SERVER_PORT);int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);}int opt = 0;setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(int));int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err < 0) {ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);goto __exit;}ESP_LOGI(TAG, "Socket bound to port %d", SERVER_PORT);while (1) {struct sockaddr_in source_addr = {0};socklen_t socklen = sizeof(source_addr);memset(rx_buffer, 0, sizeof(rx_buffer));int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);if (len < 0) {ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);} else {ESP_LOGI(TAG, "Received %d bytes from " IPSTR ":%d, data: %s", len, IP2STR((esp_ip4_addr_t *) &source_addr.sin_addr), source_addr.sin_port, rx_buffer);}}__exit:close(sock);vTaskDelete(NULL);
}static void wifi_event_handler(void* arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{if (event_base == IP_EVENT) {if (event_id == IP_EVENT_STA_GOT_IP) {xTaskCreate(udp_server_task, "udp_server", 4096, NULL, 5, &server_task_handle);}} else if (event_base == WIFI_EVENT) {if (event_id == WIFI_EVENT_STA_DISCONNECTED) {vTaskDelete(server_task_handle);} else if (event_id == WIFI_EVENT_STA_START) {esp_wifi_connect();}}
}int app_main()
{/* 初始化NVS */esp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}/* 初始化WiFi协议栈 */ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_sta();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));wifi_config_t wifi_config = {.sta = {.ssid = "Your SSID",.password = "Your password",.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());return 0;
}

         接收者的流程和发送者差不多,下面只介绍有差异的部分。

1. 创建套接字

        参考前面。

2. 配置套接字(可选)

        调用setsockopt函数。除了前面介绍到的配置,UDP还支持一个SO_BROADCAST的配置,用来使能广播包的接收,我这里是配置为禁用。

3. 绑定IP(可选)

        调用bind函数。参数1为套接字描述符,参数2为IP地址结构体,参数3为结构体长度。通过bind可以绑定监听的IP地址和端口,只有接收到的数据包目标IP和端口一致才会处理,我这里配置接收任意IP,端口为20001。

4. 接收数据

        调用recvfrom函数。传入参数和sendto一致,不同的是后面的IP地址结构体是输出参数,不需要初始化,它会返回数据包的源IP信息。

5. 关闭套接字

        参考前面。

        上位机的配置和前面基本一样,唯一的不同是远程端口要保证和ESP32绑定的端口一致

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

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

相关文章

动手学深度学习-卷积神经网络-3填充和步幅

目录 填充 步幅 小结 在上一节的例子&#xff08;下图&#xff09; 中&#xff0c;输入的高度和宽度都为3&#xff0c;卷积核的高度和宽度都为2&#xff0c;生成的输出表征的维数为22。 正如我们在 上一节中所概括的那样&#xff0c;假设输入形状为nhnw&#xff0c;卷积核形…

Airflow:精通Airflow任务依赖

任务依赖关系是任何工作流管理系统的核心概念&#xff0c;Apache Airflow也不例外。它们确定在工作流中执行任务的顺序和条件&#xff0c;确保以正确的顺序完成任务&#xff0c;并确保在相关任务开始之前成功完成先决任务。在本文中我们将探讨Apache Airflow中的任务依赖关系&a…

【数据结构】_链表经典算法OJ:合并两个有序数组

目录 1. 题目描述及链接 2. 解题思路 3. 程序 3.1 第一版 3.2 第二版 1. 题目描述及链接 题目链接&#xff1a;21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。 新链表是通过拼接给…

全程Kali linux---CTFshow misc入门

图片篇(基础操作) 第一题&#xff1a; ctfshow{22f1fb91fc4169f1c9411ce632a0ed8d} 第二题 解压完成后看到PNG&#xff0c;可以知道这是一张图片&#xff0c;使用mv命令或者直接右键重命名&#xff0c;修改扩展名为“PNG”即可得到flag。 ctfshow{6f66202f21ad22a2a19520cdd…

基于SMPL的三维人体重建-深度学习经典方法之VIBE

本文以开源项目VIBE[1-2]为例&#xff0c;介绍下采用深度学习和SMPL模板的从图片进行三维人体重建算法的整体流程。如有错误&#xff0c;欢迎评论指正。 一.算法流程 包含生成器模块和判别器模块&#xff0c;核心贡献就在于引入了GRU模块&#xff0c;使得当前帧包含了先前帧的先…

深入浅出Linux操作系统大数据定制Shell编程(六)

深入浅出Linux操作系统大数据定制Shell编 1、大数据定制-Shell编程1.1、什么是Shell1.2、Shell脚本执行方式 2、Shell变量2.1、shell变量的定义2.1.1、设置环境变量2.1.2、多行注释 2.2、位置参数变量2.2.1、语法 2.3、预定义变量2.4、运算符2.4.1、条件判断2.4.2、case语句2.4…

30289_SC65XX功能机MMI开发笔记(ums9117)

建立窗口步骤&#xff1a; 引入图片资源 放入图片 然后跑make pprj new job8 可能会有bug,宏定义 还会有开关灯报错&#xff0c;看命令行注释掉 接着把ture改成false 然后命令行new一遍&#xff0c;编译一遍没报错后 把编译器的win文件删掉&#xff0c; 再跑一遍虚拟机命令行…

“““【运用 R 语言里的“predict”函数针对 Cox 模型展开新数据的预测以及推理。】“““

主题与背景 本文主要介绍了如何在R语言中使用predict函数对已拟合的Cox比例风险模型进行新数据的预测和推理。Cox模型是一种常用的生存分析方法&#xff0c;用于评估多个因素对事件发生时间的影响。文章通过具体的代码示例展示了如何使用predict函数的不同参数来获取生存概率和…

Effective Objective-C 2.0 读书笔记—— objc_msgSend

Effective Objective-C 2.0 读书笔记—— objc_msgSend 文章目录 Effective Objective-C 2.0 读书笔记—— objc_msgSend引入——静态绑定和动态绑定OC之中动态绑定的实现方法签名方法列表 其他方法objc_msgSend_stretobjc_msgSend_fpretobjc_msgSendSuper 尾调用优化总结参考文…

【竞技宝】LPL:IG3-1击败RNG

北京时间1月26日&#xff0c;英雄联盟LPL2025正在如火如荼的进行之中&#xff0c;昨日共进行两场比赛。第二场比赛由RNG对阵IG。本场比赛&#xff0c;RNG在首局前期打出完美节奏后一直压制着IG拿下比赛&#xff0c;但此后的三局&#xff0c;IG发挥出自己擅长大乱斗的能力在团战…

web3py+flask+ganache的智能合约教育平台

最近在学习web3的接口文档&#xff0c;使用web3pyflaskganache写了一个简易的智能合约教育平台&#xff0c;语言用的是python&#xff0c;ganche直接使用的本地区块链网络&#xff0c;用web3py进行交互。 代码逻辑不难&#xff0c;可以私信或者到我的闲鱼号夏沫mds获取我的代码…

媒体新闻发稿要求有哪些?什么类型的稿件更好通过?

为了保证推送信息的内容质量&#xff0c;大型新闻媒体的审稿要求一向较为严格。尤其在商业推广的过程中&#xff0c;不少企业的宣传稿很难发布在这些大型新闻媒体平台上。 媒体新闻发稿要求有哪些&#xff1f;就让我们来了解下哪几类稿件更容易过审。 一、媒体新闻发稿要求有哪…

ui-automator定位官网文档下载及使用

一、ui-automator定位官网文档简介及下载 AndroidUiAutomator&#xff1a;移动端特有的定位方式&#xff0c;uiautomator是java实现的&#xff0c;定位类型必须写成java类型 官方地址&#xff1a;https://developer.android.com/training/testing/ui-automator.html#ui-autom…

ThreadLocal概述、解决SimpleDateFormat出现的异常、内存泄漏、弱引用、remove方法

①. ThreadLocal简介 ①. ThreadLocal是什么 ①. ThreadLocal本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程…

总结8..

#include <stdio.h> // 定义结构体表示二叉树节点&#xff0c;包含左右子节点编号 struct node { int l; int r; } tree[100000]; // 全局变量记录二叉树最大深度&#xff0c;初始为0 int ans 0; // 深度优先搜索函数 // pos: 当前节点在数组中的位置&#xff0c…

科普篇 | “机架、塔式、刀片”三类服务器对比

一、引言 在互联网的世界里&#xff0c;服务器就像是默默运转的超级大脑&#xff0c;支撑着我们日常使用的各种网络服务。今天&#xff0c;咱们来聊聊服务器家族中的三位 “明星成员”&#xff1a;机架式服务器、塔式服务器和刀片式服务器。如果把互联网比作一座庞大的城市&…

Day25-【13003】短文,什么是算法?如何衡量时间复杂度?什么是最优,平均时间复杂度?

文章目录 第二节概览什么是算法&#xff1f;算法的5个特性&#xff1f; 算法如何评估&#xff1f;时间指标如何衡量&#xff1f;算法的复杂度如何度量&#xff1f;算法开销上限和下限如何表示&#xff1f;什么是常数复杂度&#xff1f;线性操作&#xff1f;对数复杂度-线性对数…

python基础语法(3) -------- 学习笔记分享

目录: 1. 函数 1.1 语法格式 1.2 函数参数 1.3 函数返回值 1.4 变量的作用域 1.5 函数的执行过程 1.6 函数的链式调用 1.7 函数的嵌套调用 1.8 函数递归 1.9 参数默认值 1.10 函数的关键字传参 2. 列表和元组 2.1 列表和元组是啥 2.2 创建列表 2.3 访问下标 2.…

磐维数据库PanWeiDB2.0日常维护

磐维数据库简介 “中国移动磐维数据库”&#xff08;ChinaMobileDB&#xff09;&#xff0c;简称“磐维数据库”&#xff08;PanWeiDB&#xff09;。是中国移动信息技术中心首个基于中国本土开源数据库打造的面向ICT基础设施的自研数据库产品。 其产品内核能力基于华为 OpenG…

Linux:文件与fd(未被打开的文件)

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《Linux&#xff1a;文件与fd&#xff08;未被打开的文件&#xff09;》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xf…