搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)
引言
随着物联网(IoT)技术的快速发展,基于 STM32 的服务器(类似网关)在数据采集、设备控制等方面的应用越来越广泛。本文将介绍搭建一个基于 STM32 的服务器所需的技术栈,以及详细的搭建步骤和代码示例。
技术栈介绍
在搭建基于 STM32 的服务器时,我们需要用到以下技术栈和组件:
1. 硬件平台
- STM32 微控制器:选择 STM32F4 或 STM32F7 系列,根据性能需求和外设支持。
- 网络模块:可以选择 ESP8266(Wi-Fi)、ESP32(Wi-Fi + 蓝牙)或以太网模块(如 W5500)。
- 电源管理:使用稳压器或电源管理芯片,确保系统供电稳定。
2. 开发环境
- IDE:使用 STM32CubeIDE 或 Keil MDK 进行开发。
- 库和驱动:
- STM32 HAL 库:简化硬件访问。
- LWIP(轻量级IP协议栈):实现 TCP/IP 协议栈。
- FreeRTOS(可选):支持多任务处理。
3. 网络协议
- TCP/IP:实现基本的网络通信。
- HTTP/HTTPS:用于支持 Web 服务。
- MQTT:适合 IoT 设备间的轻量级通信。
4. 开发语言
- C/C++:主要用于 STM32 的底层开发。
- HTML/CSS/JavaScript:用于开发 Web 界面。
5. 数据存储
- Flash 存储:存储配置和小型数据。
- SD 卡(可选):用于大容量数据存储。
6. 安全性
- TLS/SSL:实现数据加密,确保通信安全。
- 认证机制:如 JWT,确保设备和用户身份验证。
搭建步骤
下面将详细介绍如何搭建基于 STM32 的服务器,涵盖硬件连接、软件开发和测试等步骤。
一、硬件搭建
1.1 准备硬件
- 开发板:选择 STM32F4 或 STM32F7 开发板。
- 网络模块:选择 ESP8266 或 W5500 以实现网络连接。
- 其他配件:稳压电源模块、面包板、杜邦线等。
1.2 硬件连接
-
连接 STM32 和网络模块:
-
ESP8266/ESP32:
- 将 ESP 模块的 VCC 连接到 STM32 的 3.3V,GND 连接到 GND。
- 将 ESP 的 TX 接口连接到 STM32 的 RX 引脚,RX 接口连接到 STM32 的 TX 引脚。
-
W5500:
- 将 W5500 的 SPI 接口连接到 STM32 的相应引脚:
- MOSI -> STM32 MOSI
- MISO -> STM32 MISO
- SCK -> STM32 SCK
- CS -> STM32 GPIO(选择任意 GPIO 作为片选引脚)
- 连接电源(VCC 和 GND)。
- 将 W5500 的 SPI 接口连接到 STM32 的相应引脚:
-
-
确保电源管理:
- 使用适当的稳压器,确保 STM32 和其他模块的电压符合要求。
1.3 硬件连接示意图
二、软件开发
2.1 开发环境设置
2.2 引入必要的库文件
-
下载 STM32CubeIDE:
- 访问 ST 官网 下载并安装 STM32CubeIDE。
-
创建新工程:
- 打开 STM32CubeIDE,选择新建 STM32 项目。
- 选择所用的 STM32 微控制器型号。
-
配置外设:
- 在 STM32CubeMX 中,配置 UART(用于串口通信)和 SPI(如果使用 W5500)。
- 在“Pinout & Configuration”选项卡中,设置 UART 和 SPI 引脚。
-
启用 LWIP 协议栈:
- 在“Middleware”选项卡中,启用 LWIP。配置 LWIP 参数,包括 IP 地址、网络掩码和网关。
- 配置 LWIP 的使用模式(如 DHCP 或静态 IP)。
-
生成代码:
- 点击“Project”菜单,选择“Generate Code”,生成项目代码。
-
STM32 HAL 库:
- 在项目中默认已经包含 STM32 HAL 库,无需额外引入。
-
LWIP 协议栈:
- LWIP 协议栈已经在 STM32CubeMX 中配置并生成,可以直接使用。
-
FreeRTOS(可选):
- 如果需要多任务处理,选择 FreeRTOS,设置任务优先级及堆大小。
2.3 编写代码
2.3.1 初始化代码
在 main.c
文件中,增加网络初始化和服务器启动代码。以下是基本实现步骤:
#include "lwip/init.h"
#include "lwip/netconn.h"// 网络配置函数
void init_network() {// 初始化硬件HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI_Init(); // 如果使用W5500MX_LWIP_Init(); // 初始化LWIP
}// TCP/IP 服务器实现
void start_server() {struct netconn *conn, *newconn;err_t err;// 创建 TCP 连接conn = netconn_new(NETCONN_TCP);netconn_bind(conn, NULL, 80); // 绑定到端口 80netconn_listen(conn);while (1) {err = netconn_accept(conn, &newconn); // 接受新的连接if (err == ERR_OK) {// 处理请求// 这里可以添加处理 HTTP 请求的代码netconn_delete(newconn); // 处理完毕后关闭连接}}
}
2.3.2 主函数
在 main()
函数中调用初始化和服务器启动代码:
int main(void) {init_network(); // 初始化网络start_server(); // 启动TCP服务器while (1) {sys_check_timeouts(); // 处理 LWIP 超时}
}
2.4 处理 HTTP 请求
为了让服务器能够响应 HTTP 请求,可以在 start_server()
函数中添加 HTTP 请求处理逻辑。以下是一个简单的 HTTP 响应示例:
void handle_request(struct netconn *newconn) {struct netbuf *inbuf;char *buffer;u16_t len;// 等待接收数据netconn_recv(newconn, &inbuf);netbuf_data(inbuf, (void**)&buffer, &len);// 简单处理 HTTP GET 请求if (strncmp(buffer, "GET ", 4) == 0) {// 发送 HTTP 响应const char* response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n""<html><body><h1>Hello, STM32!</h1></body></html>";netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);}netbuf_delete(inbuf); // 释放内存
}// 在 start_server() 中调用
if (err == ERR_OK) {handle_request(newconn); // 处理 HTTP 请求netconn_delete(newconn);
}
2.5 编译和上传
-
编译代码:
- 在 STM32CubeIDE 中,点击“Build”按钮编译项目,确保代码没有错误。
-
上传代码:
- 连接开发板,选择正确的调试器(如 ST-Link),点击“Run”按钮将代码上传到 STM32。
2.6 测试服务器
-
连接网络:
- 确保 STM32 开发板通过 ESP8266/ESP32 或 W5500 网络模块正确连接到网络。
- 如果使用 ESP8266/ESP32,请确保模块已连接到 Wi-Fi 网络。如果使用 W5500,请确保以太网线连接到路由器。
-
获取 IP 地址:
- 如果使用 DHCP,STM32 会自动获取 IP 地址。
- 可以在调试输出(例如使用 UART)中打印出分配的 IP 地址。可以在
lwip
初始化后加入如下代码:ip_addr_t ipaddr, netmask, gw; netif_default->ip_addr.addr = netif_default->ip_addr.addr; netif_default->netmask.addr = netif_default->netmask.addr; netif_default->gw.addr = netif_default->gw.addr;printf("IP Address: %s\n", ipaddr_ntoa(&netif_default->ip_addr));
-
使用浏览器访问服务器:
- 在 PC 或手机的浏览器中输入 STM32 的 IP 地址,例如
http://192.168.1.100
(请根据实际分配的 IP 地址修改)。 - 如果一切正常,您应该能看到服务器返回的 HTML 页面,显示内容为 "Hello, STM32!"。
- 在 PC 或手机的浏览器中输入 STM32 的 IP 地址,例如
-
调试:
- 如果未能访问网页,请检查以下事项:
- 确保 STM32 开发板的电源正常。
- 检查网络连接是否正常。
- 使用串口监视器查看调试信息,确认 IP 地址是否正确。
- 使用 Wireshark 等工具监控网络流量,检查请求是否到达 STM32。
- 如果未能访问网页,请检查以下事项:
3.1 增加更多的功能
处理不同的 HTTP 请求
可以根据不同的 URL 路径处理不同的请求,例如通过 GET /data
获取传感器数据。以下是如何实现的步骤:
-
修改请求处理函数:
在handle_request
函数中,根据请求的 URL 处理不同的请求。void handle_request(struct netconn *newconn) {struct netbuf *inbuf;char *buffer;u16_t len;// 等待接收数据netconn_recv(newconn, &inbuf);netbuf_data(inbuf, (void**)&buffer, &len);// 简单处理 HTTP GET 请求if (strncmp(buffer, "GET /data", 9) == 0) {// 假设我们有一个函数获取传感器数据char sensor_data[100]; // 假设存储传感器数据的数组get_sensor_data(sensor_data); // 实现这个函数以获取传感器数据// 发送 HTTP 响应char response[150];snprintf(response, sizeof(response), "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n%s", sensor_data);netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);} else {// 处理其他请求const char* response = "HTTP/1.1 404 Not Found\r\n\r\n";netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);}netbuf_delete(inbuf); // 释放内存 }void get_sensor_data(char *data) {// 模拟传感器数据sprintf(data, "Temperature: 25.5 C\nHumidity: 60%%"); }
实现 MQTT 支持
如果需要实现 IoT 设备间的通信,可以集成 MQTT 协议。可以使用如 Paho MQTT 或 MQTT-C 等轻量级的 MQTT 客户端库。以下是实现步骤:
-
下载并集成 MQTT 库:
- 根据选定的库,下载源代码并将其添加到 STM32 项目中。
-
初始化 MQTT 客户端:
#include "MQTTClient.h" // 根据所用的 MQTT 库引入头文件MQTTClient client; char *mqtt_broker = "tcp://broker.hivemq.com:1883"; // MQTT Broker 地址void mqtt_init() {MQTTClient_create(&client, mqtt_broker, "stm32_client_id", MQTTCLIENT_PERSISTENCE_NONE, NULL);MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL);MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;conn_opts.keepAliveInterval = 20;conn_opts.cleansession = 1;if (MQTTClient_connect(client, &conn_opts) != MQTTCLIENT_SUCCESS) {printf("Failed to connect to MQTT broker\n");return;}printf("Connected to MQTT broker\n"); }
-
发布和订阅消息:
void publish_message(const char *topic, const char *payload) {MQTTClient_message pubmsg = MQTTClient_message_initializer;pubmsg.payload = (void*)payload;pubmsg.payloadlen = strlen(payload);pubmsg.qos = 1;pubmsg.retained = 0;MQTTClient_publishMessage(client, topic, &pubmsg, NULL); }void subscribe_to_topic(const char *topic) {MQTTClient_subscribe(client, topic, 1); }
3.2 数据存储
使用 Flash 存储
可以将设备配置(如 Wi-Fi SSID 和密码)存储在 Flash 中,以便在重启时自动加载。
- Flash 存储函数:
#include "stm32f4xx_hal_flash.h"void write_flash(uint32_t address, uint8_t *data, uint16_t size) {HAL_FLASH_Unlock(); // 解锁 Flash 写入// 擦除页FLASH_Erase_Sector(FLASH_SECTOR_2, VOLTAGE_RANGE_3); // 擦除选择的区域// 写入数据for (uint16_t i = 0; i < size; i++) {if (HAL_FLASH_Program(TYPEPROGRAM_BYTE, address + i, data[i]) != HAL_OK) {// 处理写入错误return;}}HAL_FLASH_Lock(); // 锁定 Flash 写入}void read_flash(uint32_t address, uint8_t *data, uint16_t size) {for (uint16_t i = 0; i < size; i++) {data[i] = *(__IO uint8_t*)(address + i); // 读取数据}}
使用示例
-
写入 Wi-Fi 配置:
void save_wifi_config(const char* ssid, const char* password) {uint8_t ssid_len = strlen(ssid);uint8_t password_len = strlen(password);uint32_t address = 0x080E0000; // 假设选择这个地址存储配置// 写入 SSIDwrite_flash(address, (uint8_t *)ssid, ssid_len);// 写入密码write_flash(address + 0x40, (uint8_t *)password, password_len); // 假设密码紧跟在 SSID 后 }
-
读取 Wi-Fi 配置:
void load_wifi_config(char* ssid, char* password) {uint32_t address = 0x080E0000; // 读取配置的地址uint8_t ssid_len = 32; // 假设 SSID 最大长度为 32uint8_t password_len = 32; // 假设密码最大长度为 32read_flash(address, (uint8_t *)ssid, ssid_len);read_flash(address + 0x40, (uint8_t *)password, password_len); }
使用 SD 卡存储数据
如果需要存储大量数据,可以添加 SD 卡模块,并使用 FATFS 文件系统进行数据读写。
1. 添加 SD 卡模块
- 硬件连接:
- 将 SD 卡模块连接到 STM32 的 SPI 接口(MOSI、MISO、SCK 和 CS)。
- 连接 VCC 和 GND。
2. 配置 FATFS
-
在 STM32CubeMX 中启用 FATFS:
- 在中间件部分选择 FATFS,并配置为 SPI 模式。
-
生成代码:
- 生成代码后,您将在项目中看到 FATFS 的相关文件。
3. SD 卡读写示例
-
初始化 SD 卡:
FATFS FatFs; // FatFs工作区 FIL fil; // 文件对象 FRESULT fr; // FATFS 结果void init_sd_card() {fr = f_mount(&FatFs, "", 1); // 挂载文件系统if (fr != FR_OK) {// 处理错误} }
-
写入数据到 SD 卡:
void write_to_sd_card(const char* filename, const char* data) {fr = f_open(&fil, filename, FA_WRITE | FA_CREATE_ALWAYS); // 打开文件if (fr == FR_OK) {f_write(&fil, data, strlen(data), NULL); // 写入数据f_close(&fil); // 关闭文件} else {// 处理打开文件错误} }
-
从 SD 卡读取数据:
void read_from_sd_card(const char* filename, char* buffer, uint32_t buffer_size) {fr = f_open(&fil, filename, FA_READ); // 打开文件if (fr == FR_OK) {f_read(&fil, buffer, buffer_size, NULL); // 读取数据f_close(&fil); // 关闭文件} else {// 处理打开文件错误} }
3.3 增强安全性
启用 HTTPS
使用 TLS/SSL 库(如 mbedTLS)实现 HTTPS,确保数据传输安全。
1. 集成 mbedTLS
-
下载 mbedTLS:
- 访问 mbedTLS GitHub 页面 下载最新版本的 mbedTLS。
- 将相关源代码和头文件添加到 STM32 项目中。
-
配置 mbedTLS:
- 在
mbedtls/config.h
中,启用所需功能,例如:#define MBEDTLS_SSL_CLI_C #define MBEDTLS_SSL_SRV_C #define MBEDTLS_TLS_C #define MBEDTLS_X509_CRT_PARSE_C #define MBEDTLS_SHA256_C #define MBEDTLS_AES_C
- 在
2. 初始化 mbedTLS
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/error.h"// 全局变量
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;// 初始化 mbedTLS
void init_mbedtls() {mbedtls_ssl_init(&ssl);mbedtls_ssl_config_init(&conf);mbedtls_entropy_init(&entropy);mbedtls_ctr_drbg_init(&ctr_drbg);// 设置随机数生成器const char *pers = "ssl_client";mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers));// 配置 SSLmbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
}// 连接到 HTTPS 服务器
int connect_https(const char *hostname, const char *port) {mbedtls_net_context server_fd;mbedtls_net_init(&server_fd);// 连接到服务器if (mbedtls_net_connect(&server_fd, hostname, port, MBEDTLS_NET_PROTO_TCP) != 0) {return -1;}// 设置 SSLmbedtls_ssl_setup(&ssl, &conf);mbedtls_ssl_set_hostname(&ssl, hostname);mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);// 完成 SSL 握手if (mbedtls_ssl_handshake(&ssl) != 0) {mbedtls_net_free(&server_fd);return -1;}return 0; // 连接成功
}
3. 发送 HTTPS 请求
void send_https_request(const char *hostname, const char *path) {char request[512];snprintf(request, sizeof(request),"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", path, hostname);mbedtls_ssl_write(&ssl, (unsigned char *)request, strlen(request));// 读取响应unsigned char buf[1024];int ret;do {ret = mbedtls_ssl_read(&ssl, buf, sizeof(buf) - 1);if (ret > 0) {buf[ret] = '\0'; // 添加字符串结束符printf("%s", (char *)buf); // 打印响应}} while (ret > 0);// 清理mbedtls_ssl_close_notify(&ssl);mbedtls_net_free(&server_fd);
}
用户身份认证
1. JWT 生成和验证
1. 生成 JWT
使用 JWT 库生成 JSON Web Token,以下是如何生成 JWT 的示例代码:
#include "jwt.h"// 生成 JWT
char* generate_jwt(const char *secret, const char *username) {jwt_t *jwt = NULL;char *token = NULL;// 创建新的 JWTif (jwt_new(&jwt) != 0) {return NULL; // 处理错误}// 设置 JWT 的声明jwt_add_grant(jwt, "sub", username); // 用户名jwt_add_grant_int(jwt, "exp", time(NULL) + 3600); // 设置过期时间为1小时// 签名 JWTif (jwt_set_alg(jwt, JWT_ALG_HS256, (unsigned char *)secret, strlen(secret)) != 0) {jwt_free(jwt);return NULL; // 处理错误}// 获取 JWT 字符串token = jwt_encode_str(jwt);jwt_free(jwt); // 释放 JWT 结构体return token; // 返回生成的 JWT
}
2. 验证 JWT
在服务器端验证 JWT,确保请求的用户身份合法。
int verify_jwt(const char *token, const char *secret) {jwt_t *jwt = NULL;const char *username;// 解析 JWTif (jwt_decode(&jwt, token, (unsigned char *)secret, strlen(secret)) != 0) {return 0; // 验证失败}// 检查过期时间if (jwt_get_grant_int(jwt, "exp") < time(NULL)) {jwt_free(jwt);return 0; // JWT 已过期}username = jwt_get_grant(jwt, "sub"); // 获取用户名printf("Authenticated user: %s\n", username); // 打印用户信息jwt_free(jwt); // 释放 JWT 结构体return 1; // 验证成功
}
3. 结合 HTTP 请求与 JWT 验证
在处理 HTTP 请求时,检查 Authorization 头中是否包含有效的 JWT。
void handle_request(struct netconn *newconn) {struct netbuf *inbuf;char *buffer;u16_t len;netconn_recv(newconn, &inbuf);netbuf_data(inbuf, (void**)&buffer, &len);// 示例:处理 GET 请求,检查 JWTif (strncmp(buffer, "GET /data", 9) == 0) {// 检查 Authorization 头char *auth_header = strstr(buffer, "Authorization: ");if (auth_header) {char *token = strtok(auth_header + 15, "\r\n"); // 提取 JWT (Bearer token)if (verify_jwt(token, "your_secret_key")) { // 验证 JWT// 返回传感器数据char sensor_data[100];get_sensor_data(sensor_data); // 获取传感器数据char response[150];snprintf(response, sizeof(response),"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n%s", sensor_data);netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);} else {// JWT 验证失败const char* response = "HTTP/1.1 401 Unauthorized\r\n\r\n";netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);}} else {// 未提供 JWTconst char* response = "HTTP/1.1 401 Unauthorized\r\n\r\n";netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);}} else {// 处理其他请求const char* response = "HTTP/1.1 404 Not Found\r\n\r\n";netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);}netbuf_delete(inbuf); // 释放内存
}
总结
通过本文,我们详细介绍了如何搭建一个基于 STM32 的服务器(类似网关),并实现了多种功能,具体包括:
-
硬件搭建:
- 选择合适的 STM32 微控制器(如 STM32F4 或 STM32F7)和网络模块(如 ESP8266、ESP32 或 W5500)。
- 确保电源管理稳定,为系统提供稳定的电压。
-
软件开发:
- 设置开发环境,使用 STM32CubeIDE 创建项目。
- 配置并初始化网络模块,使用 LWIP 协议栈实现 TCP/IP 通信。
- 编写处理 HTTP 请求的代码,支持根据 URL 路径返回不同的响应。
-
增加更多功能:
- 处理不同的 HTTP 请求,获取传感器数据并返回。
- 集成 MQTT 协议,实现 IoT 设备间的通信。
- 提供 HTTP 服务和 MQTT 服务,增加系统的灵活性。
-
数据存储:
- 使用 Flash 存储设备配置(如 Wi-Fi SSID 和密码)。
- 使用 SD 卡模块存储大量数据,并通过 FATFS 文件系统读写数据。
-
增强安全性:
- 使用 mbedTLS 库启用 HTTPS,确保数据传输的安全性。
- 实现 JWT(JSON Web Token)身份认证,确保只有授权用户能够访问服务器。
非常感谢您阅读到这里!您的关注和支持是我不断前进的动力。跟随着我探索嵌入式领域,希望因为兴趣而成为嵌入式领域的专家。
在这个快速发展的技术时代,嵌入式系统无处不在,从智能家居到医疗设备,从自动驾驶汽车到工业控制,每一个领域都离不开嵌入式技术的支持。对我来说,嵌入式不仅仅是一门技术,更是一种激情和追求。通过不断学习和实践,我深深爱上了这个充满挑战和机遇的领域。每一次调试成功,每一个创新的实现,都是我继续前行的动力。
——by 极客小张
相关文章:
搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)
引言 随着物联网(IoT)技术的快速发展,基于 STM32 的服务器(类似网关)在数据采集、设备控制等方面的应用越来越广泛。本文将介绍搭建一个基于 STM32 的服务器所需的技术栈,以及详细的搭建步骤和代码示例。 …...
一款免费强大的电脑锁屏工具,中文绿色免安装
这款软件主要特点是锁屏后不显示密码输入框,直接输入密码即可解锁。 ScreenBlur是一款功能强大的电脑屏幕锁软件,主要用于保护用户的隐私和数据安全。该软件的主要功能包括自动锁屏、隐藏桌面、加密锁机等。 功能特点 自动锁屏:用户可以设…...
Python | Leetcode Python题解之第319题灯泡开关
题目: 题解: class Solution:def bulbSwitch(self, n: int) -> int:return int(sqrt(n 0.5))...
前端Web-JavaScript(上)
要想让网页具备一定的交互效果,具有一定的动作行为,还得通过JavaScript来实现, 这门语言会让我们的页面能够和用户进行交互。 什么是JavaScript JavaScript(简称:JS) 是一门跨平台、面向对象的脚本语言,是…...
【积累】Python的类
类和方法的概念及实例 类 (Class):类是对具有相同属性和方法的对象集合的抽象描述。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 方法:类中定义的函数。 构造方法 __init__():这是一个特殊的方法,会在类实例…...
Golang | Leetcode Golang题解之第318题最大单词长度乘积
题目: 题解: func maxProduct(words []string) (ans int) {masks : map[int]int{}for _, word : range words {mask : 0for _, ch : range word {mask | 1 << (ch - a)}if len(word) > masks[mask] {masks[mask] len(word)}}for x, lenX : ra…...
【感想】支持八股文在面试的应用
八股文:程序员面试中的利与弊 在现代社会的职场竞争中,尤其是IT行业,面试环节常常成为决定一个人能否入职的重要关卡。在这其中,“八股文”作为一种被广泛应用的考核工具,已经成为面试中不可或缺的一部分。然而&#…...
B - 02-计算球的体积 51Nod - 3266
对于半径为 rr 的球,其体积的计算公式为 V4/3πr3V4/3πr3 ,这里取 π3.14π3.14 。现给定 rr ,求 VV 。 Input 输入为一个不超过 100100 的非负实数,即球半径,类型为 doubledouble 。 Output 输出一个实数&#x…...
Qt pro文件详解
概述 在Qt中,.pro 文件(也称为项目文件)是Qt项目管理系统(qmake)所使用的配置文件。这个文件定义了如何构建你的Qt应用程序或库,其使用简单的键值对语法,允许你指定源文件、头文件、库依赖、配置…...
JavaFX布局-ButtonBar
JavaFX布局-ButtonBar 常用属性buttonOrderpaddingbuttonMinWidth 实现方式Java实现fxml实现 一个特殊的容器,用于创建一组按钮,水平排列按钮太多,会被遮住,不会自动产生滚动条 常用属性 buttonOrder 预制顺序 buttonBar.setBut…...
【C++程序设计】——利用数组处理批量数据(二)
👨💻个人主页:开发者-削好皮的Pineapple! 👨💻 hello 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 削好皮的Pineapple! 原创 👨Ǵ…...
使用 1panel面板 部署 php网站
代码仓库:https://github.com/talmudmaster/RedCorpus 目录 网站介绍安装步骤1. 准备云服务器2. 准备域名(可跳过)3. 安装1panel面板4. 服务器开放端口5. 进入1panel面板6. 安装并启动软件(服务器和面板开放端口)7. 创…...
Windows调大虚拟内存来代替升级物理运行内存(RAM)真的有用吗?
前言 前段时间有个粉丝突发奇想说:电脑运行内存不足,调大虚拟内存来代替升级物理运行内存(内存条)不就可以了?剩下的大几百块钱吃香的喝辣的不好吗? 嗯。。。直到2024年的今天,估计还有很多小…...
[Unity] ShaderGraph实现DeBuff污染 溶解叠加效果
本篇是在之前的基础上,继续做的功能衍生。 [Unity] ShaderGraph实现Sprite消散及受击变色 完整连连看如下所示:...
java算法day28
java算法day28 300 最长递增子序列136 只出现一次的数字169 多数元素234 回文链表53 最大子数组和 300 最长递增子序列 这个是记忆化搜索的代码。是从递归改过来的。 这题显然是要用dp做比较合适。因为很容易看到原问题与子问题之间的关系。 还是从后往前看。 然后可以利用选…...
vue实现歌词滚动效果
1.结构 <template><div class"lyricScroll"><div class"audio"><audio id"audio" src"./common/周传雄-青花1.mp3" controls></audio></div><div class"container" id"contai…...
【算法设计题】合并两个非递减有序链表,第1题(C/C++)
目录 第1题 合并两个非递减有序链表 得分点(必背) 题解 函数声明与初始化变量: 初始化合并链表的头节点: 合并两个链表: 处理剩余节点: 返回合并后的链表: 完整测试代码 🌈…...
Vue前端工程
创建一个工程化的vue项目 npm init vuelatest 全默认回车就好了 登录注册校验 //定义数据模型 const registerDataref({username:,password:,rePassword: }) //校验密码的函数 const checkRePassword(rule,value,callback)>{if (value){callback(new Error(请再次输入密…...
什么是药物临床试验?
药物临床试验是指在人体上进行的新药试验研究,旨在确定新药的疗效、安全性、药代动力学和药效学。临床试验不仅帮助确认药物是否对特定疾病或症状有效,还帮助识别和评估药物的副作用和风险。 药物临床试验(Clinical Trial,CT&…...
编译和汇编的区别
一、编译 编译是将高级语言(如C、C、Java等)编写的源代码转换成计算机可以直接执行的低级语言(通常是机器语言或汇编语言)的过程 编译 —— 将人类可读的源代码转换为计算机可执行的指令集 编译过程 通常包括词法分析、语法分…...
C# 设计倒计时器、串口助手开发
文章目录 1. 实现一个简单的倒计时器开始、暂停2. 串口助手开发 1. 实现一个简单的倒计时器开始、暂停 namespace Timer {public partial class Form1 : Form{int count;//用于定时器计数int time;//存储设定的定时值bool parse false;//控制暂停计时public Form1(){Initiali…...
图论① dfs | Java | LeetCode 797,Kama 98 邻接表实现(未完成)
797 所有可能路径 https://leetcode.cn/problems/all-paths-from-source-to-target/description/ 输入:graph [[1,2],[3],[3],[]] 题目分析,这里 class Solution {//这个不是二维数组,而是listList<List<Integer>> res new Ar…...
Mac安装nvm以及配置环境变量
安装nvm brew install nvm安装成功后会出现这样一段话: Add the following to your shell profile e.g. ~/.profile or ~/.zshrc:export NVM_DIR"$HOME/.nvm"[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh&q…...
AUTOSAR实战教程-使用DET来发现开发错误
2年之前因为在调试AUTOSAR存储协议栈的时候使用DET并没发现有用的信息,所以就武断下结论--这玩意没有用。活到老学到老吧,bug经历的多了,发现这玩意还挺有用的。说一下这个bug的背景。 在将时间同步报文改道CanTsync之后,由于这个AUTOSAR工具本身的问题以及配置工程师本身的…...
ZeroMQ(二):请求-响应模式,C和C++。
目录 请求响应基础 基本概念 工作流程 典型应用 请求-响应模式的特点 应用实例 优点 缺点 ZEROMQ C语言 2.1 服务器端代码(Reply Server) 2.2 客户端代码(Request Client) 3. 编译代码 4. 详细说明 ZEROMQ C 1. …...
【虚拟仿真】Unity3D中实现2DUI显示在3D物体旁边
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 这篇文章来实现2DUI显示在3D物体旁边,当我们需要在3D模型旁边显示2DUI的时候,比如人物的对…...
代码随想录 day 29 贪心
第八章 贪心算法 part03 134. 加油站 本题有点难度,不太好想,推荐大家熟悉一下方法二 https://programmercarl.com/0134.%E5%8A%A0%E6%B2%B9%E7%AB%99.html 135. 分发糖果 本题涉及到一个思想,就是想处理好一边再处理另一边,不…...
开源:LLMCompiler高性能工具调用框架
开源:LLMCompiler高性能工具调用框架 LLMCompilerLLMCompiler 框架图任务提取单元使用方式参考链接 LLMCompiler LLMCompiler 是一种 Agent 架构,旨在通过在DAG中快速执行任务来加快 Agent 任务的执行速度。它还通过减少对 LLM 的调用次数来节省 Tokens …...
【学习方法】高效学习因素 ① ( 开始学习 | 高效学习因素五大因素 | 高效学习公式 - 学习效果 = 时间 x 注意力 x 精力 x 目标 x 策略 )
文章目录 一、高效学习因素1、开始学习2、高效学习因素五大因素3、高效学习公式 - 学习效果 时间 x 注意力 x 精力 x 目标 x 策略 一、高效学习因素 1、开始学习 对于 学习差 , 调皮捣蛋 的学生 , 不要把 学习成绩差 的 原因 归因为 不爱学习 / 没有学习方法 , 可能是 还没有 …...
LeetCode Medium|【146. LRU 缓存】
力扣题目链接 题意:本题的题意就是希望我们设计一个满足 LRU 缓存的数据结构,LRU即最近最少使用。 需要我们实现 get 和 put 方法,即从缓存中获取值和设置缓存中值的方法。 还有一个约束条件就是缓存应当有容量限制,如果实现 put …...
二维码生成器联图/网络推广的调整和优化
什么是gel文件?gel文件能干什么? gel全称General Extended Language,即通用扩展语言文件,gel文件中由类似C语言的代码构成,gel语言是一种解释性语言,gel文件扩展名为.gel; gel文件用于&#x…...
手机建造网站/seo专业培训学费多少钱
当然可以。你有什么数学题需要我帮你做呢?...
做网站播放未上映的电影是侵权吗/学生网页设计模板
1.慢查询简介顾名思义,慢查询日志中记录的是执行时间较长的查询的SQL语句,默认文件名为hostname-slow.log,默认目录也是数据目录。慢查询日志采用的是简单的文本格式,可以通过各种文本编辑器查看其中的内容。其中记录了语句执行的…...
网站开发成本包括/发布新闻稿
只要最后统计出有多少个集合就好了 我用并查集还有set做的,好像用set慢了一点 #include<stdio.h> #include<set> using namespace std; int f[1010]; int find(int x) {if(xf[x])return f[x];f[x]find(f[x]);return f[x]; } void Union(int x,int y) {i…...
济宁网站建设吊装/百度推广开户免费
书记员必备宝典:如何提高打字速度?01考速录的必要性书记员的工作主要职责是:庭审记录、庭前准备和庭后整理档案。其中庭审记录和庭前准备都会涉及速录。所以速录是书记员的必要技能,需要重视和准备。02速录练习须知部分考生在备考…...