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

news2025/4/22 15:51:52

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 20001

static 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 8
  char            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 20001

static 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.coloradmin.cn/o/2291538.html

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

相关文章

tiktok 国际版抖抖♬♬ X-Bogus参数算法逆向分析

加密请求参数得到乱码&#xff0c;最终得到X-Bogus

C++ Primer 命名空间的using声明

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

c语言(关键字)

前言&#xff1a; 感谢b站鹏哥c语言 内容&#xff1a; 栈区&#xff08;存放局部变量&#xff09; 堆区 静态区&#xff08;存放静态变量&#xff09; rigister关键字 寄存器&#xff0c;cpu优先从寄存器里边读取数据 #include <stdio.h>//typedef&#xff0c;类型…

ARM内核:嵌入式时代的核心引擎

引言 在当今智能设备无处不在的时代&#xff0c;ARM&#xff08;Advanced RISC Machines&#xff09;处理器凭借其高性能、低功耗的特性&#xff0c;成为智能手机、物联网设备、汽车电子等领域的核心引擎。作为精简指令集&#xff08;RISC&#xff09;的典范&#xff0c;ARM核…

Airflow:选择合适执行器扩展任务执行

Apache Airflow是面向开发人员使用的&#xff0c;以编程方式编写、调度和监控的数据流程平台。可伸缩性是其关键特性之一&#xff0c;Airflow支持使用不同的执行器来执行任务。在本文中&#xff0c;我们将深入探讨如何利用这些执行器在Airflow中有效地扩展任务执行。 理解Airfl…

CoRAG 来自微软与人大的创新RAG框架技术

微软与人大合作开发的CoRAG(Chain-of-Retrieval Augmented Generation)是一种创新的检索增强生成(RAG)框架,旨在通过模拟人类思考方式来提升大语言模型(LLM)在复杂问题上的推理和回答能力。以下是对CoRAG的深度介绍: 1. CoRAG的核心理念 CoRAG的核心思想是通过动态调…

Qt Creator 中使用 vcpkg

Qt Creator 中使用 vcpkg Qt Creator 是一个跨平台的轻量级 IDE&#xff0c;做 Qt 程序开发的同学们肯定对这个 IDE 都比较属于。这个 IDE 虽然没有 Visual Stdio 功能那么强&#xff0c;但是由于和 Qt 集成的比较深&#xff0c;用来开发 Qt 程序还是很顺手的。 早期&#xf…

mysql中in和exists的区别?

大家好&#xff0c;我是锋哥。今天分享关于【mysql中in和exists的区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; mysql中in和exists的区别&#xff1f; 在 MySQL 中&#xff0c;IN 和 EXISTS 都是用于子查询的操作符&#xff0c;但它们在执行原理和适用场景上有所不…

智慧园区管理系统推动企业智能运维与资源优化的全新路径分析

内容概要 在当今快速发展的商业环境中&#xff0c;园区管理的数字化转型显得尤为重要。在这个背景下&#xff0c;快鲸智慧园区管理系统应运而生&#xff0c;成为企业实现高效管理的最佳选择。它通过整合互联网、物联网等先进技术&#xff0c;以智能化的方式解决了传统管理模式…

物联网 STM32【源代码形式-使用以太网】连接OneNet IOT从云产品开发到底层MQTT实现,APP控制 【保姆级零基础搭建】

物联网&#xff08;IoT&#xff09;‌是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器等装置与技术&#xff0c;实时采集并连接任何需要监控、连接、互动的物体或过程&#xff0c;实现对物品和过程的智能化感知、识别和管理。物联网的核心功能包括数据采集与监…

谭浩强C语言程序设计(4) 8章(下)

1、输入三个字符串按照字母顺序从小到大输出 #include <cstdio> // 包含cstdio头文件&#xff0c;用于输入输出函数 #include <cstring> // 包含cstring头文件&#xff0c;用于字符串处理函数#define N 20 // 定义字符串的最大长度为20// 函数&#xff1a;…

使用朴素贝叶斯对散点数据进行分类

本文将通过一个具体的例子&#xff0c;展示如何使用 Python 和 scikit-learn 库中的 GaussianNB 模型&#xff0c;对二维散点数据进行分类&#xff0c;并可视化分类结果。 1. 数据准备 假设我们有两个类别的二维散点数据&#xff0c;每个类别包含若干个点。我们将这些点分别存…

【Pytorch和Keras】使用transformer库进行图像分类

目录 一、环境准备二、基于Pytorch的预训练模型1、准备数据集2、加载预训练模型3、 使用pytorch进行模型构建 三、基于keras的预训练模型四、模型测试五、参考 现在大多数的模型都会上传到huggface平台进行统一的管理&#xff0c;transformer库能关联到huggface中对应的模型&am…

Python 深拷贝与浅拷贝:数据复制的奥秘及回溯算法中的应用

引言 在 Python 编程领域&#xff0c;数据复制是极为常见的操作。而深拷贝和浅拷贝这两个概念&#xff0c;如同紧密关联却又各具特色的双子星&#xff0c;在数据处理过程中扮演着重要角色。深入理解它们&#xff0c;不仅有助于编写出高效、准确的代码&#xff0c;还能避免许多…

简单易懂的倒排索引详解

文章目录 简单易懂的倒排索引详解一、引言 简单易懂的倒排索引详解二、倒排索引的基本结构三、倒排索引的构建过程四、使用示例1、Mapper函数2、Reducer函数 五、总结 简单易懂的倒排索引详解 一、引言 倒排索引是一种广泛应用于搜索引擎和大数据处理中的数据结构&#xff0c;…

初级数据结构:栈和队列

目录 一、栈 (一)、栈的定义 (二)、栈的功能 (三)、栈的实现 1.栈的初始化 2.动态扩容 3.压栈操作 4.出栈操作 5.获取栈顶元素 6.获取栈顶元素的有效个数 7.检查栈是否为空 8.栈的销毁 9.完整代码 二、队列 (一)、队列的定义 (二)、队列的功能 (三&#xff09…

阿里云 - RocketMQ入门

前言 云消息队列 RocketMQ 版产品具备异步通信的优势&#xff0c;主要应用于【异步解耦】、【流量削峰填谷】等场景对于同步链路&#xff0c;需要实时返回调用结果的场景&#xff0c;建议使用RPC调用方案阿里云官网地址RocketMQ官网地址 模型概述 生产者生产消息并发送至服务…

Agentic Automation:基于Agent的企业认知架构重构与数字化转型跃迁---我的AI经典战例

文章目录 Agent代理Agent组成 我在企业实战AI Agent企业痛点我构建的AI Agent App 项目开源 & 安装包下载 大家好&#xff0c;我是工程师令狐&#xff0c;今天想给大家讲解一下AI智能体&#xff0c;以及企业与AI智能体的结合&#xff0c;文章中我会列举自己在企业中Agent实…

列表标签(无序列表、有序列表)

无序列表 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head><…

每天学点小知识之设计模式的艺术-策略模式

行为型模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解模板方法模式 模板方法模式是结构最简单的行为型设计模式&#xff0c;在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式&#xff0c;可以将一些复杂流程的实现步骤封装在一系列基…