ESP-C3入门9. 创建TCP Server

news2024/11/18 4:34:18

ESP-C3入门9. 创建TCP Server

  • 一、ESP32 IDF的TCP/IP协议栈
  • 二、BSD套接字API介绍
  • 三、创建TCP Server的步骤
    • 1. 引用TCP/IP协议栈
    • 2. 创建 TCP套接字拼绑定端口
    • 3. 接收客户端请求
    • 4. 启动服务
  • 四、完整代码
    • 1. wifi.h
    • 2. wifi.c
    • 3. tcpServer.h
    • 4. tcpServer.c
    • 5. main.c
    • 6. CmakeLists.txt
    • 7. 组件内CMakeLists.txt内容

在这里插入图片描述

一、ESP32 IDF的TCP/IP协议栈

TCP/IP协议栈是ESP32 IDF的一个核心组件。它实现了TCP、UDP、IP、DHCP、DNS和其他网络协议,使ESP32可以与其他设备通信。具体来说,ESP32 IDF的TCP/IP协议栈包括以下几个主要模块:

  1. WiFi协议栈
  2. TCP/IP协议栈
  3. LWIP协议栈
  4. SPI Flash文件系统
    通过TCP/IP协议栈,ESP32可以轻松地实现各种网络应用,例如HTTP服务器、MQTT客户端、TCP/UDP服务器和客户端等等。同时,ESP32 IDF提供了丰富的API和组件,使得开发人员可以快速地实现自己的应用程序。

ESP32主要使用LwIP协议栈,支持函数:

  • BSD风格 Sockets API
  • Netconn API (未正式启用)

官方文档提示:
在使用任何lwIP API(除BSD Sockets API之外)时,请确保它是线程安全的。
要检查特定的API调用是否安全,请启用CONFIG_LWIP_CHECK_THREAD_SAFETY并运行应用程序。这样,lwIP会断言TCP/IP核心功能被正确访问,如果没有正确锁定或从正确的任务(lwIP FreeRTOS任务)访问,则执行中止。
一般建议使用ESP-NETIF组件与lwIP交互。

ESP-IDF对lwIP进行了一些封装,以间接支持部分功能,如:

  • DHCP
  • SNTP
  • ICMP Ping
  • NetBIOS查找可使用的lwIP API
  • mDNS
  • 串行PPP接口
    等。

二、BSD套接字API介绍

BSD Sockets API 是一个常见的跨平台TCP/IP套接字API, 有时被称为POSIX Sockets或 Berkeley Sockets。

lwIP支持BSD Sockets API的所有常见用法,一些功能如下:

  • socket()
  • bind()
  • accept()
  • shutdown()
  • getpeername()
  • getsockopt()
  • setsockopt()
  • close()
  • read(), readv() write() writev()
  • recv() recvmsg() recfrom()
  • send() sendmsg() sendto()
  • select()
  • poll()
  • fcntl()
    另外lwIP还支持ioctl()非标准的功能。

三、创建TCP Server的步骤

1. 引用TCP/IP协议栈

在CMakeLists.txt包含以下内容:


idf_component_register(SRCS "your_source_files.c"
                    INCLUDE_DIRS "include"
                    REQUIRES "tcpip_adapter")

2. 创建 TCP套接字拼绑定端口

创建一个TCP套接字,并将其绑定到指定的端口。然后使用listen()函数将服务器套接字设置为侦听连接。

#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netdb.h>
#include <lwip/sockets.h>

#define PORT CONFIG_EXAMPLE_PORT

static struct sockaddr_in server_addr;
static int server_socket = -1;

void create_tcp_server() {
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if (server_socket < 0) {
        ESP_LOGE(TAG, "Failed to create server socket!");
        return;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    int err = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (err != 0) {
        ESP_LOGE(TAG, "Failed to bind server socket!");
        close(server_socket);
        return;
    }

    err = listen(server_socket, 1);
    if (err != 0) {
        ESP_LOGE(TAG, "Failed to listen on server socket!");
        close(server_socket);
        return;
    }

    ESP_LOGI(TAG, "TCP server started on port %d", PORT);
}

3. 接收客户端请求

使用accept()函数从服务器套接字接受新连接。
下面代码里,创建一个TCP客户端,使用结构体client_addr存储客户端的信息。

  • 接收到一般消息时,自动原文回复;
  • 收到ping时,回复pong;
  • 收到bye时,回复bye并关闭连接。

关闭连接后,程序会重新开始等待新连接。

void process_tcp_client(){
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
    if(client_socket <0){
        ESP_LOGE(TAG, "Failed to accept client socket!");
        return;
    }
    ESP_LOGI(TAG, "Accepted new client connection");
    // 处理客户端请求
    bool end=false;
    while (!end) {
        char buffer[1024];
        // 初始化数组
        memset(buffer, 0, sizeof(buffer));
        int len = recv(client_socket, buffer, sizeof(buffer), 0);
        if (len > 0) {
            const char *response;
            if (strncmp(buffer, "ping", 4) == 0) {
                response = "pong";
                ESP_LOGI(TAG, "Received ping, sending pong");
            } else if (strncmp(buffer, "bye", 3) == 0) {
                response = "bye";
                ESP_LOGI(TAG, "Received bye, closing connection");
                end = true;
            } else {
                response = buffer;
                ESP_LOGI(TAG, "Received data: %s", response);
            }
            send(client_socket, response, strlen(response), 0);

        }
    }
    close(client_socket);
}

4. 启动服务

在应用程序的main()函数中调用create_tcp_server()函数来启动TCP服务器

void app_main() {
    create_tcp_server();
    while (1) {
        process_tcp_client();
    }
}

四、完整代码

本文代码会基于前一章内容实现,将wifi连接部分独立文件放。
项目框架:
在这里插入图片描述

1. wifi.h

#ifndef WIFI_LIB
#define WIFI_LIB
#include <string.h>
/* 宏定义 */
#include <esp_event_base.h>

#include "esp_log.h"

#define LIGHT_ESP_WIFI_SSID     "你的wifi账号"
#define LIGHT_ESP_WIFI_PASS     "你的wifi密码"
#define LIGHT_ESP_MAXIMUM_RETRY 5

// 事件组允许每个事件有多个位,这里只使用两个事件:
// 已经连接到AP并获得了IP
// 在最大重试次数后仍未连接
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1


/* 函数声明 */
void wifi_initialize(void);
void wifi_station_initialize(void);
void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data);

#endif /* WIFI_LIB */

2. wifi.c

#include <freertos/FreeRTOS.h>
#include "main/network/include/wifi.h"
#include <freertos/event_groups.h>
#include <esp_wifi.h>

static const char *TAG = "wifi lib";

// FreeRTOS事件组,连接成功时发出信号
static EventGroupHandle_t s_wifi_event_group = NULL;
static int s_retry_num = 0;

// 事件回调
void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    // 如果是Wi-Fi事件,并且事件ID是Wi-Fi事件STA_START
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        // 如果是Wi-Fi事件,并且事件ID是Wi-Fi事件STA_DISCONNECTED
        /* 如果重试次数小于最大重试次数 */
        if (s_retry_num < LIGHT_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG, "connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        // 如果是IP事件,并且事件ID是IP事件STA_GOT_IP

        // 获取事件结果
        ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));

        // 将重试次数重置为 0;
        s_retry_num = 0;
        // 通过调用 xEventGroupSetBits 函数,将 WIFI_CONNECTED_BIT 设置到事件组中,表示成功连接到 AP
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_initialize(void)
{
    // 创建一个事件组,用于管理Wi-Fi连接事件。
    s_wifi_event_group = xEventGroupCreate();

    // 初始化 TCP/IP 协议栈。
    ESP_ERROR_CHECK(esp_netif_init());

    // 创建默认事件循环。
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 创建默认的Wi-Fi网络接口。
    esp_netif_create_default_wifi_sta();
    // 设置 Wi-Fi 初始化配置为默认配置
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 注册事件处理器,以处理 Wi-Fi 和 IP 相关事件
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
}

void wifi_station_initialize(void)
{
    // 配置WiFi的配置信息
    wifi_config_t wifi_config = {
            .sta = {
                    .ssid = LIGHT_ESP_WIFI_SSID,
                    .password = LIGHT_ESP_WIFI_PASS,
                    // 启用WPA2模式,常用的WiFi连接方式
                    .threshold.authmode = WIFI_AUTH_WPA2_PSK,

                    .pmf_cfg = {
                            .capable = true,
                            .required = false
                    },
            },
    };
    // WiFi工作模式设置为STA
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    // 设置WiFi工作模式
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    // 启动WiFi
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_station_initialize finished.");

    /* 等待连接建立(WIFI_CONNECTED_BIT)或连接失败的次数达到最大值(WIFI_FAIL_BIT)。
     * 这些位通过 event_handler() 设置(详见上面)*/
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    /* xEventGroupWaitBits() 返回调用前的 bits,因此我们可以测试实际发生了什么事件。 */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", LIGHT_ESP_WIFI_SSID, LIGHT_ESP_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", LIGHT_ESP_WIFI_SSID, LIGHT_ESP_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
}

3. tcpServer.h

#ifndef TCP_SERVER
#define TCP_SERVER
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netdb.h>
#include <lwip/sockets.h>

#define PORT 3000
void create_tcp_server();
void process_tcp_client();
#endif

4. tcpServer.c

//
// 
//
#include <esp_log.h>
#include "tcpServer.h"
static struct sockaddr_in server_addr;
static int server_socket = -1;
static const char* TAG = "TCP SERVER";
void create_tcp_server(){
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if(server_socket<0){
        ESP_LOGE(TAG, "Failed to create server socket!");
        return;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    int err = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if(err !=0){
        ESP_LOGE(TAG, "Failed to bind server socket!");
        close(server_socket);
        return;
    }
    err = listen(server_socket, 1);
    if(err != 0){
        ESP_LOGE(TAG, "Failed to listen on server socket!");
        close(server_socket);
        return;
    }
    ESP_LOGI(TAG, "TCP server started on port %d", PORT);
}

void process_tcp_client(){
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
    if(client_socket <0){
        ESP_LOGE(TAG, "Failed to accept client socket!");
        return;
    }
    ESP_LOGI(TAG, "Accepted new client connection");
    // 处理客户端请求
    bool end=false;
    while (!end) {
        char buffer[1024];
        // 初始化数组
        memset(buffer, 0, sizeof(buffer));
        int len = recv(client_socket, buffer, sizeof(buffer), 0);
        if (len > 0) {
            const char *response;
            if (strncmp(buffer, "ping", 4) == 0) {
                response = "pong";
                ESP_LOGI(TAG, "Received ping, sending pong");
            } else if (strncmp(buffer, "bye", 3) == 0) {
                response = "bye";
                ESP_LOGI(TAG, "Received bye, closing connection");
                end = true;
            } else {
                response = buffer;
                ESP_LOGI(TAG, "Received data: %s", response);
            }
            send(client_socket, response, strlen(response), 0);

        }
    }
    close(client_socket);
}

5. main.c

#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include <nvs_flash.h>
#include "network/include/wifi.h"
#include "network/include/tcpServer.h"
static const char *TAG = "wifi connection";

void app_main()
{
    int i = 0;
    ESP_LOGE(TAG, "app_main");
    // 初始化NVS存储区
    ESP_ERROR_CHECK(nvs_flash_init());

    // Wi-Fi初始化
    ESP_LOGI(TAG, "Wi-Fi initialization");
    wifi_initialize();

    // Wi-Fi Station初始化
    wifi_station_initialize();

    // 创建 tcp server
    create_tcp_server();

    while (1) {
        ESP_LOGI(TAG, "[%02d] Wait Client Connection!", i++);
        process_tcp_client();
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

6. CmakeLists.txt

# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(PYTHON "D:/Espressif/python_env/idf4.4_py3.8_env/Scripts/python")

# 设置项目的额外组件目录,允许使用ESP-IDF示例中的公共组件。
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/led_strip)

# 设置项目的目标芯片。这决定了编译器和链接器的选项。
set(IDF_TARGET "esp32c3")
# 设置项目使用的C标准
set(CMAKE_C_STANDARD 99)

# 设置项目的源文件,即要编译和链接的文件
set(SOURCES
        ${SOURCES}
        )
# 设置项目的包含目录,这些是要搜索头文件的目录
set(INCLUDE_DIRS
        ${INCLUDE_DIRS}
        ${CMAKE_CURRENT_SOURCE_DIR}
        )
include_directories(${INCLUDE_DIRS})

# 包含ESP-IDF工具中的project.cmake文件。这设置了项目的配置和构建系统。
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# 设置项目的名称
project(blink)

7. 组件内CMakeLists.txt内容

idf_component_register(SRCS "main.c" "network/wifi.c" "network/tcpServer.c"
                    INCLUDE_DIRS "network/include"
                    REQUIRES "tcpip_adapter" "nvs_flash"
        )
# 这将把当前组件的路径添加到编译器的头文件搜索路径中,否则提示找不到string.h等头文件
target_include_directories(${COMPONENT_LIB} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(${COMPONENT_LIB} PUBLIC "network/include")

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

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

相关文章

BNB Greenfield 成存储赛道“新贵”,BNB 生态的野心与破局

“从BNB Beacon Chain&#xff0c;到BNB Chain&#xff0c;再到BNB Greenfield &#xff0c;三位一体的 BNB 生态格局正式形成。 ”在今年的2月1日&#xff0c;币安发布了分布式存储链BNB Greenfield&#xff0c;根据白皮书信息&#xff0c;它的特别之处在于其不仅具备基于SP&a…

完成四种方式的MySQL安装

1.仓库安装 1.1查看版本和安装mysql包 [rootlocalhost ~]# cat /etc/redhat-release Red Hat Enterprise Linux release 9.1 (Plow) [rootlocalhost ~]# rpm -ivh https://repo.mysql.com/mysql80-community-release-el9-1.noarch.rpm1.2装包 [rootlocalhost ~]# dnf instal…

千峰jquery【案例】

滑动选项卡&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widt…

RabbitMQ学习(六):发布确认

一、发布确认的原理生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的 消息都将会被指派一个唯一的 ID(从 1 开始)&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker 就会发送一个确认给生产者(包含消…

V4l2框架基础知识(一)

V4L2框架-v4l2 device V4l2视频设备驱动基础 1.V4L2是专门为linux设备设计的整套视频框架&#xff08;其主要核心在linux内核&#xff0c;相当于操作系统上层的视频源捕获驱动框架&#xff09;&#xff0c;为上层访问系统底层的视频设备提供了一个统一的标准接口&#xff0c;…

【LeetCode】剑指 Offer 05. 替换空格 p50 -- Java Version

题目链接&#xff1a; https://leetcode.cn/problems/ti-huan-kong-ge-lcof/ 1. 题目介绍&#xff08;05. 替换空格&#xff09; 请实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20"。 【测试用例】&#xff1a; 示例1&#xff1a; 输入&#xff1a;s …

TransH模型原理

从TransE到TransH模型 在之前知识图谱模型中&#xff0c;我们介绍了TransE模型的基本原理&#xff0c;对于TransE模型其基本原理为&#xff1a; hrth r thrt 其中hhh是头实体向量&#xff0c;rrr是关系向量&#xff0c;ttt是尾实体向量。根据这个核心公式&#xff0c;我们不…

AI工衣工服智能识别检测算法 yolov7

AI工衣工服智能识别检测算法通过yolov7网络模型深度学习算法&#xff0c;AI工衣工服智能识别检测算法对场人员穿戴进行实时不间断监测&#xff0c;发现现场人员未按要求穿戴时&#xff0c;立即抓拍告警。YOLO 的核心思想就是把目标检测转变成一个回归问题&#xff0c;利用整张图…

Unity 编辑器工具之批量设置图片压缩

一个简单的工具,对Unity下的图片做批量压缩处理,主要有以下功能:自动取消 "Generte Mip Maps" 勾选;针对文件夹批量自动(或手动选择压缩格式)设置图片压缩并自动保存;单个图片文件的压缩设置;使用方法,右键单张图片(或者包含图片的文件夹)会打开一个设置窗口 如下,窗…

Vue笔记(2)——页面渲染与数据收集

一、条件渲染 v-show v-if 1. v-show 2. v-if v-else的块和v-if的块间不能有中断&#xff0c;否则无效 3. v-if与template配合 当同时条件渲染多个元素时&#xff0c;可以将v-if与template的配合使用&#xff0c;若条件值为false&#xff0c;vue模板解析时会直接去掉这一块…

AcWing语法基础课笔记 第二章 printf语句与C++中的判断结构

第二章 printf语句与C中的判断结构 学习语言最好的方式就是实践&#xff0c;每当掌握一个新功能时&#xff0c;就要立即将这个功能应用到实践中。 ——闫学灿 一、printf输出格式 注意&#xff1a;使用printf 时最好添加头文件 #include <cstdio>。 Int、float、double、…

基于共聚焦显微技术的显微镜和荧光显微镜的区别

荧光显微镜主要应用在生物领域及医学研究中&#xff0c;能得到细胞或组织内部微细结构的荧光图像&#xff0c;在亚细胞水平上观察诸如Ca2 、PH值&#xff0c;膜电位等生理信号及细胞形态的变化&#xff0c;是形态学&#xff0c;分子生物学&#xff0c;神经科学&#xff0c;药理…

GEE学习笔记 八十九:在自己的APP中使用绘制矢量(中)

这一篇先讲一下ui.Map.GeometryLayer(...)&#xff0c;也就是生成显示的绘制矢量图形图层&#xff0c;具体来讲就是地图上左上角绘制的图形后添加的图层。 1、什么是GeometryLayer&#xff1f; &#xff08;1&#xff09;直接在地图上加载定义的图层 //1. add normal layer …

基于springboot+bootstrap+mysql+redis搭建一套完整的权限架构【二】【整合springSecurity】

1、创建数据库 注意&#xff1a;mysql默认字符集为utf8&#xff0c;默认排序规则为utf8_general_ci。一般我们也会选择字符集为utf-8 MySQL在5.5.3之后增加了这个utf8mb4的编码&#xff0c;utf8mb4完全向下兼容utf8&#xff0c;为了节省空间&#xff0c;一般情况下使用utf8也就…

中国国家级地面气象站基本气象要素日值数据集(V3.0)

数据集摘要 数据集包含了中国基本气象站、基准气候站、一般气象站在内的主要2474个站点1951年1月以来本站气压、气温、降水量、蒸发量、相对湿度、风向风速、日照时数和0cm地温要素的日值数据。数据量为21.3GB。 (1)SURF_CLI_CHN_MUL_DAY-TEM-12001-201501.TXT 气温数据TEM, 包…

央行数据-一款查逆回购 LPR 货币供应量 资产负债表 Shibor 数据的专业工具

自己开发的APP, App Store搜索"央行数据" 即可下载欢迎大家下载,给修改意见逆回购、正回购、MLF、票据&#xff0c;俗称央行发钱房贷基准利率多少? M2/M1/M0, 资产负债表,Shibor 了解下这款APP是经济,投资理财,股市,房价分析参考利器适用于关注经济、货币政策的用户…

第五章.与学习相关技巧—权重初始值(随机初始值,Xavier初始值,He初始值)

第五章.与学习相关技巧 5.2 权重初始值 本节将介绍权重初始值的推荐值&#xff0c;并通过实验确认神经网络的学习是否会快速进行。 1.权值衰减 权值衰减就是一种以减少权重参数的值为目的进行学习的方法&#xff0c;通过减少权重参数值来抑制过拟合的情况发生。 2.权重初始值不…

表现良好的最长时段[前缀和思想子数组]

前缀和与最长子数组前言一、表现良好的最长时间段二、前缀和思想&子数组1、前缀和&map2、前缀和&单调栈总结参考文献前言 对于子数组/子串问题&#xff0c;紧密连续前缀和/滑动窗口/单调栈&#xff1b;挖掘内在规律&#xff0c;可以简化代码&#xff0c;降低时空复…

Python多进程同步——文件锁

多个进程共享同一份资源&#xff08;共享内存、文件等&#xff09;时&#xff0c;会涉及到资源竞争问题。为了解决这种问题&#xff0c;一般采取的措施是进程在访问资源前加锁保护&#xff0c;避免多个进程同时读写。本文介绍的Python文件锁可以用来解决多进程的同步问题。 目录…

天荒地老修仙功-第六部第二篇:Spring Cloud Eureka自我保护机制

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%&#xff0c;如果低于 85%&#xff0c;Eureka Server 会将这些实例保护起来&#xff0c;让这些实例不会过期&#xff0c;但是在保护期内如果服务刚好这个服务提供者非正常下线了&#xff0c;此时服务消…