ESP-IDF:TCP多线程并发服务器

news2025/7/17 5:51:23

核心代码:

核心思想就是主线程只处理socket监听功能,把数据处理部分分配到不同的线程中去处理。来了一个客户端连接,就分配新的线程去处理该客户端的数据请求。
在这里插入图片描述

代码:

/多线程并发服务器/
#include <stdio.h>
#include “sdkconfig.h”
#include “freertos/semphr.h”
#include “esp_system.h”
#include “esp_spi_flash.h”
#include <string.h>
#include “freertos/FreeRTOS.h”
#include “freertos/task.h”
#include “freertos/event_groups.h”
#include “esp_system.h”
#include “esp_wifi.h”
#include “esp_event.h”
#include “esp_event_loop.h”
#include “esp_log.h”
#include “nvs_flash.h”
#include “sdkconfig.h”

#include “lwip/err.h”
#include “lwip/sys.h”

#include “mdns.h”

#include <sys/param.h>
#include “esp_event.h”

#include “esp_netif.h”
//#include “protocol_examples_common.h”

#include “lwip/sockets.h”
#include <lwip/netdb.h>

/* The examples use WiFi configuration that you can set via ‘make menuconfig’.

If you’d rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID “mywifissid”
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY
#define EXAMPLE_ESP_WIFI_AP_SSID CONFIG_ESP_WIFI_AP_SSID
#define EXAMPLE_ESP_WIFI_AP_PASS CONFIG_ESP_WIFI_AP_PASSWORD
#define EXAMPLE_MAX_STA_CONN CONFIG_MAX_STA_CONN
#define EXAMPLE_IP_ADDR CONFIG_SERVER_IP
#define EXAMPLE_ESP_WIFI_AP_CHANNEL CONFIG_ESP_WIFI_AP_CHANNEL

static const char *TAG = “camera wifi”;
static const char *TAG1 = “michael add:”;

static int s_retry_num = 0;

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
case SYSTEM_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, “station:” MACSTR " join, AID=%d",
MAC2STR(event->event_info.sta_connected.mac),
event->event_info.sta_connected.aid);
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, “station:” MACSTR “leave, AID=%d”,
MAC2STR(event->event_info.sta_disconnected.mac),
event->event_info.sta_disconnected.aid);
break;
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, “got ip:%s”,
ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
s_retry_num = 0;
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
{
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG,“retry to connect to the AP”);
}
ESP_LOGI(TAG,“connect to the AP fail”);
break;
}
default:
break;
}
ESP_LOGI(TAG1,“before mdns_handle_system_event”);
mdns_handle_system_event(ctx, event);
ESP_LOGI(TAG1,“after mdns_handle_system_event”);
return ESP_OK;
}

void wifi_init_softap()
{
if (strcmp(EXAMPLE_IP_ADDR, “192.168.4.1”))
{
int a, b, c, d;
sscanf(EXAMPLE_IP_ADDR, “%d.%d.%d.%d”, &a, &b, &c, &d);
tcpip_adapter_ip_info_t ip_info;
IP4_ADDR(&ip_info.ip, a, b, c, d);
IP4_ADDR(&ip_info.gw, a, b, c, d);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
ESP_ERROR_CHECK(tcpip_adapter_dhcps_stop(WIFI_IF_AP));
ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(WIFI_IF_AP, &ip_info));
ESP_ERROR_CHECK(tcpip_adapter_dhcps_start(WIFI_IF_AP));
}
wifi_config_t wifi_config;
memset(&wifi_config, 0, sizeof(wifi_config_t));
snprintf((char*)wifi_config.ap.ssid, 32, “%s”, EXAMPLE_ESP_WIFI_AP_SSID);
wifi_config.ap.ssid_len = strlen((char*)wifi_config.ap.ssid);
snprintf((char*)wifi_config.ap.password, 64, “%s”, EXAMPLE_ESP_WIFI_AP_PASS);
wifi_config.ap.max_connection = EXAMPLE_MAX_STA_CONN;
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
if (strlen(EXAMPLE_ESP_WIFI_AP_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
if (strlen(EXAMPLE_ESP_WIFI_AP_CHANNEL)) {
int channel;
sscanf(EXAMPLE_ESP_WIFI_AP_CHANNEL, “%d”, &channel);
wifi_config.ap.channel = channel;
}

ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));

ESP_LOGI(TAG, "wifi_init_softap finished.SSID:%s password:%s",
         EXAMPLE_ESP_WIFI_AP_SSID, EXAMPLE_ESP_WIFI_AP_PASS);

}

void wifi_init_sta()
{
wifi_config_t wifi_config;
memset(&wifi_config, 0, sizeof(wifi_config_t));
snprintf((char*)wifi_config.sta.ssid, 32, “%s”, EXAMPLE_ESP_WIFI_SSID);
snprintf((char*)wifi_config.sta.password, 64, “%s”, EXAMPLE_ESP_WIFI_PASS);

ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );

ESP_LOGI(TAG, "wifi_init_sta finished.");
ESP_LOGI(TAG, "connect to ap SSID:%s password:%s",
         EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);

}

void app_wifi_main()
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
wifi_mode_t mode = WIFI_MODE_AP;

if (strlen(EXAMPLE_ESP_WIFI_AP_SSID) && strlen(EXAMPLE_ESP_WIFI_SSID)) {
    mode = WIFI_MODE_APSTA;
} else if (strlen(EXAMPLE_ESP_WIFI_AP_SSID)) {
    mode = WIFI_MODE_AP;
} else if (strlen(EXAMPLE_ESP_WIFI_SSID)) {
    mode = WIFI_MODE_STA;
}

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());
  ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

if (mode == WIFI_MODE_NULL) {
    ESP_LOGW(TAG,"Neither AP or STA have been configured. WiFi will be off.");
    return;
}

tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(mode));

if (mode & WIFI_MODE_AP) {
    wifi_init_softap();
}

if (mode & WIFI_MODE_STA) {
    wifi_init_sta();
}
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));

}

/* BSD Socket API Example

This example code is in the Public Domain (or CC0 licensed, at your option.)

Unless required by applicable law or agreed to in writing, this
software is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/

#define PORT CONFIG_EXAMPLE_PORT
#define KEEPALIVE_IDLE CONFIG_EXAMPLE_KEEPALIVE_IDLE
#define KEEPALIVE_INTERVAL CONFIG_EXAMPLE_KEEPALIVE_INTERVAL
#define KEEPALIVE_COUNT CONFIG_EXAMPLE_KEEPALIVE_COUNT

//static const char *TAG = “example”;

//static void do_retransmit(const int sock)
static void do_retransmit0(const int sock) //michael change for multitasks server
{
int len;
char rx_buffer[128];

do {
    len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
    if (len < 0) {
        ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
    } else if (len == 0) {
        ESP_LOGW(TAG, "Connection closed");
    } else {
        rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
        ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);

        // send() can return less bytes than supplied length.
        // Walk-around for robust implementation.
        int to_write = len;
        char data[]="task0 reply";//michael add for multitasks server
        while (to_write > 0) {
            int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
            written += send(sock, data, sizeof(data), 0);//michael add for multitasks server
            if (written < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
            }
            to_write -= written;
        }
    }
} while (len > 0);
shutdown(sock, 0); //michael add for multitasks server
close(sock);//michael add for multitasks server

}

static void do_retransmit1(const int sock) //michael change for multitasks server
{
int len;
char rx_buffer[128];

do {
    len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
    if (len < 0) {
        ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
    } else if (len == 0) {
        ESP_LOGW(TAG, "Connection closed");
    } else {
        rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
        ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);

        // send() can return less bytes than supplied length.
        // Walk-around for robust implementation.
        int to_write = len;
        char data[]="task1 reply"; //michael add for multitasks server
        while (to_write > 0) {
            int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
            written += send(sock, data, sizeof(data), 0);//michael add for multitasks server
            if (written < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
            }
            to_write -= written;
        }
    }
} while (len > 0);
shutdown(sock, 0); //michael add for multitasks server
close(sock);//michael add for multitasks server

}

static void tcp_server_task(void *pvParameters)
{
char addr_str[128];
int addr_family = (int)pvParameters;
int ip_protocol = 0;
int keepAlive = 1;
int keepIdle = KEEPALIVE_IDLE;
int keepInterval = KEEPALIVE_INTERVAL;
int keepCount = KEEPALIVE_COUNT;
struct sockaddr_storage dest_addr;

if (addr_family == AF_INET) {
    struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
    dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
    dest_addr_ip4->sin_family = AF_INET;
    dest_addr_ip4->sin_port = htons(PORT);
    ip_protocol = IPPROTO_IP;
}

#ifdef CONFIG_EXAMPLE_IPV6
else if (addr_family == AF_INET6) {
struct sockaddr_in6 *dest_addr_ip6 = (struct sockaddr_in6 *)&dest_addr;
bzero(&dest_addr_ip6->sin6_addr.un, sizeof(dest_addr_ip6->sin6_addr.un));
dest_addr_ip6->sin6_family = AF_INET6;
dest_addr_ip6->sin6_port = htons(PORT);
ip_protocol = IPPROTO_IPV6;
}
#endif

int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if (listen_sock < 0) {
    ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
    vTaskDelete(NULL);
    return;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)
// Note that by default IPV6 binds to both protocols, it is must be disabled
// if both protocols used at the same time (used in CI)
setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endif

ESP_LOGI(TAG, "Socket created");

int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (err != 0) {
    ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
    ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
    goto CLEAN_UP;
}
ESP_LOGI(TAG, "Socket bound, port %d", PORT);

err = listen(listen_sock, 1);
if (err != 0) {
    ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
    goto CLEAN_UP;
}

int sockid[3];//保存主线程收到的sock, michael add for 多线程
int i=0;//保存主线程收到的sock, michael add for 多线程

while (1) {

    ESP_LOGI(TAG, "Socket listening");

    struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
    socklen_t addr_len = sizeof(source_addr);
    ESP_LOGI(TAG, "michael add before accept");
    int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
    if (sock < 0) {
        ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
        break;
    }
    ESP_LOGI(TAG, "michael add after accept");
    // Set tcp keepalive option
    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
    setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
    setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
    setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
    // Convert ip address to string
    if (source_addr.ss_family == PF_INET) {
        inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
    }

#ifdef CONFIG_EXAMPLE_IPV6
else if (source_addr.ss_family == PF_INET6) {
inet6_ntoa_r(((struct sockaddr_in6 )&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);
}
#endif
ESP_LOGI(TAG, “Socket accepted ip address: %s”, addr_str);
//把下面三个操作放到子线程中去处理,实现并行
sockid[i] = sock;
switch (i)
{
case 0:
xTaskCreate(do_retransmit0, “transmit_task0”, 4096, sockid[0], 15, NULL);
break;
case 1:
xTaskCreate(do_retransmit1, “transmit_task1”, 4096, sockid[1], 15, NULL);
break;
default:
ESP_LOGI(TAG1, “only can accept 2 connector”);
break;
}
i++;
/
单线程server代码
do_retransmit(sock);

    shutdown(sock, 0);
    close(sock);
    */
}

CLEAN_UP:
close(listen_sock);
vTaskDelete(NULL);
}

void test25() {
app_wifi_main();
#ifdef CONFIG_EXAMPLE_IPV4
xTaskCreate(tcp_server_task, “tcp_server”, 4096, (void*)AF_INET, 15, NULL);
#endif
#ifdef CONFIG_EXAMPLE_IPV6
xTaskCreate(tcp_server_task, “tcp_server”, 4096, (void*)AF_INET6, 5, NULL);
#endif
}

void app_main(void)
{

// /* Print chip information */
// esp_chip_info_t chip_info;
// esp_chip_info(&chip_info);
// printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
//        CONFIG_IDF_TARGET,
//        chip_info.cores,
//        (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
//        (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");

// printf("silicon revision %d, ", chip_info.revision);

// printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
//        (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

// printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());

// for (int i = 10; i >= 0; i--)
// {
//     printf("Restarting in %d seconds...\n", i);
//     vTaskDelay(1000 / portTICK_PERIOD_MS);
// }
// printf("Restarting now.\n");
// fflush(stdout);
// printf("----test 23----\n");
// test23();

// printf("----test 24 基于计数型信号量的生产者和消费者模型代码----\n");
// test24();

// esp_restart();
test25();

}

结果:

电脑端tcp client向esp server发送数据,server端把数据回传并添加是哪个线程发送的。
在这里插入图片描述

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

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

相关文章

实用调试技巧【上篇】

&#x1f534;本文章是在 Visual Studio 2022&#xff08;VS2022&#xff09;编译环境下进行操作讲解 文章目录&#x1f973;1. 什么是bug&#xff1f;&#x1f973;2.调试有多重要&#xff1f;2.1. 我们是如何写代码的&#xff1f;2.2.调试是什么&#xff1f;2.3.调试的基本步…

uni-app 消息推送功能UniPush

uni-app 消息推送功能UniPush,这里用的是uni-app自带的UniPush1.0&#xff08;个推服务&#xff09;&#xff0c;所以只针对UniPush1.0介绍实现步骤。 建议查阅的文章&#xff1a; UniPush 1.0 使用指南[2] Unipush 常见问题[3] 当然现在已经出了UniPush2.0&#xff08;HBuilde…

如何编写一个 npm 插件?

提到写 npm 插件&#xff0c;很多没搞过的可能第一感觉觉得很难&#xff0c;无从下手&#xff0c;其实不然。 我们甚至写个简单的 console.log(hello word)&#xff0c;都是可以当成一个插件发布上去的。 其实无从下手的主要难点还是在于你的具体要做的功能逻辑&#xff0c;这…

FPGA纯verilog代码实现sobel 边缘检测,提供2套工程源码和技术支持

目录1、前言2、理论基础3、设计思路和架构4、图像输入5、RGB转灰度6、3x3卷积滑窗获取7、Sobel卷积运算8、FDMA图像缓存9、图像输出10、工程1详解&#xff1a;ov5640输入11、工程2详解&#xff1a;hdmi输入12、上板调试验证并演示13、福利&#xff1a;工程代码的获取1、前言 边…

vue 在线编辑、实时预览的代码交互组件 vue-code-view

文章目录前言实现安装依赖vue.config.js配置main.js 全局注册参数配置新建vue单文件组件库混合使用错误处理前言 vue-code-view是一个基于 vue 2.x、轻量级的代码交互组件&#xff0c;在网页中实时编辑运行代码、预览效果的代码交互组件。 官方手册&#xff1a; Vue Code Vie…

LeetCode 25. K 个一组翻转链表

原题链接 难度&#xff1a;hard\color{red}{hard}hard 题目描述 给你链表的头节点 headheadhead &#xff0c;每 kkk 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 kkk 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 kkk 的整数倍&#…

vue2配置cesium详细教程

1.简介 网络上现在关于vue配置cesium的教程有很多&#xff0c;包括csdn和掘金等。虽然这些教程在一定意义上提供了开发者如何配置cesium的方法&#xff0c;但是大部分的方法都不切实际&#xff0c;因为每个人的电脑中npm、node、cesium、vue、webpack的版本都基本不一致的&…

汽车直营模式下OTD全流程

概述 随着新能源汽车的蓬勃发展&#xff0c;造车新势力的涌入&#xff0c;许多新能源车企想通过直营的营销模式来解决新能源汽车市场推广速度缓慢问题&#xff0c;而直营模式下OTD&#xff08;Order-To-Delivery&#xff0c;订单-交付&#xff09;全流程的改革创新在这过程中无…

高压放大器在非线性超声传播研究中的应用

实验名称&#xff1a;高压放大器在非线性超声传播研究中的应用研究方向&#xff1a;超声波测试目的&#xff1a;超声波在混凝土中的传播是一个极为复杂的非线性过程。当超声波穿过混凝土材料时&#xff0c;携带了大量有关混凝土内部结构和构造的信息。传统的超声波检测方法虽然…

Android13通知运行时权限

部分应用更新到Android13以上之后&#xff0c;没有横幅(在屏幕上弹出)通知了。 Android 13&#xff08;API 级别 33&#xff09;及更高版本支持用于从应用发送非豁免&#xff08;包括前台服务 [FGS]&#xff09;通知的运行时权限&#xff1a;POST_NOTIFICATIONS。此更改有助于…

死锁(5.1)

死锁 1 死锁的基本概念 1.1 死锁的定义 死锁是发生在一组相互合作或竞争的线程或进程中的一个问题。因此可以定义为&#xff1a;一组竞争系统资源或相互通信的进程相互的“永久”阻塞。若无外力作用&#xff0c;这组进程将永远不能继续执行。 1.2死锁产生的原因进程 &…

第四章 统计机器学习

机器学习&#xff1a;从数据中学习知识&#xff1b; 原始数据中提取特征&#xff1b;学习映射函数f&#xff1b;通过映射函数f将原始数据映射到语义空间&#xff0c;即寻找数据和任务目标之间的关系&#xff1b; 机器学习&#xff1a; 监督学习&#xff1a;数据有标签&#x…

基于Java实现的商品出入库管理系统

基于Java实现的商品出入库管理系统&#xff08;文末附源码&#xff09; 前言 一、出入库管理系统含义介绍&#xff1a; 出入库管理系统是一套利用一物一码技术对仓库货物各环节实施全过程控制管理的系统&#xff0c;可对仓库货物进行入库、出库、货位、批次、保质期等实现一…

DDL 数据定义语言

DDL 数据定义语言 目录概述一、库的管理1、库的创建2、库的修改【一般不修改&#xff0c;容易出现错误】3、库的删除二、表的管理【重要】1、表的创建2、表的修改3、表的删除4、表的复制 【可以跨库复制】练习题概述 数据定义语言 库和表的管理 一、库的管理 创建、修改、删除…

分享116个HTML电子商务模板,总有一款适合您

分享116个HTML电子商务模板&#xff0c;总有一款适合您 116个HTML电子商务模板下载链接&#xff1a;https://pan.baidu.com/s/1gaff8RsoYUD_ep0ejhGkMw?pwdzby2 提取码&#xff1a;zby2 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 建筑行业电子商务模板 建…

2.1单区域集成IS-IS

2.2.1 实验一:单区域集成IS-IS 实验目的实现IS-IS协议基本配置实验拓扑配置单区域集成IS-IS的拓扑图如图2-4所示: 图2-4:配置单区域集成IS-IS 实验步骤配置IP地址R1的配置 <Huawei>system-view

MySQL必会四大函数-窗口函数

在了解窗口函数之前&#xff0c;我们必须了解聚合函数。常见的聚合函数&#xff0c;包括 AVG、COUNT、MAX、MIN、SUM 以及 GROUP_CONCAT&#xff0c;常和GROUP BY 函数一起使用。聚合函数的作用就是对一组数据行进行汇总计算&#xff0c;并且返回单个分析结果。 窗口函数和聚合…

用最新版 VoxEdit 来提升你们的创造力!

众所周知&#xff0c;VoxEdit 可以为你们提供 Voxel 资产建模、装配和制作动画并与全世界分享的一站式服务&#xff0c;而这些全都是免费的。我们一直倾听社区意见并希望让所有人都能更容易进入 The Sandbox 元宇宙。VoxEdit 最新更新版本已经推出&#xff01;为了软体更本土化…

MySQL的触发器

目录 一.概述 介绍 触发器的特性 操作—创建触发器 操作—new和old 操作—查看触发器 操作—删除触发器 注意事项 一.概述 介绍 触发器&#xff0c;就是一种特殊的存储过程。触发器和存储过程一样是一个能够完成特定功能、存储在数据库服务器上的SQL片段&#xff0c;但是…

MACD红二波选股公式,选出MACD二次翻红的标的

经过一段上涨行情之后&#xff0c;市场出现了时间稍长或者幅度稍大的调整&#xff0c;MACD指标的DIF、DEA会出现死叉&#xff0c;柱线由红色转变为绿色。 而调整时间较短或者幅度较小&#xff0c;MACD红柱会缩短&#xff0c;但不出现绿柱&#xff0c;之后红柱开始变长&#xff…