stm32 lwip tcp服务端频繁接收连接失效问题解决(tcp_recved)
一、问题描述
最近用stmf429单片机作为TCP服务端遇到一个问题,就是客户端特别频繁的发送消息,过一段时间以后,客户端的请求不再被客户端接收到,而且服务器端监控的掉线回调函数也不会被调用,好像这个连接就凭空的消失了。
二、问题排除
首先,我以为是QT的客户端出现问题了,因为请求过于频繁,所以tcp的发送缓存区满了,但是我在发送消息的时候加了缓存区内容判断,有内容就不发送,还是不管用,然后用了串口助手软件以同样速度向单片机发送消息,过不了多久也就失去连接了,所以排除了客户端的问题。确定是单片机服务器端的问题,然后我又改了服务器端的返回消息那里,也是加发送缓存区的判断,同样是不管用,最后发现,是没有加tcp_recved的原因。

三、 tcp_recved描述
tcp_recved是 lwIP(Lightweight IP,轻量级 TCP/IP 协议栈)中用于 TCP 连接的数据接收确认操作的重要函数调用。
1.函数作用
tcp_recved 函数的主要作用是告知 lwIP 协议栈,应用程序已经成功处理了指定长度的数据,这样 lwIP 可以更新接收窗口的大小,允许发送方继续发送更多的数据。在 TCP 协议中,接收方会维护一个接收窗口,用于告知发送方自己当前能够接收的数据量。当应用程序处理完一部分数据后,需要调用 tcp_recved 函数来调整这个窗口大小,以保证数据的正常传输。
2.函数原型及参数
void tcp_recved(struct tcp_pcb *tpcb, u16_t len);
- 参数:
tpcb:指向struct tcp_pcb类型的指针,该结构体代表一个 TCP 连接的协议控制块,包含了该连接的所有状态信息。len:表示应用程序已经成功处理的数据长度,单位是字节。
3. 使用场景
在 TCP 服务器或客户端的接收回调函数中,当应用程序接收到数据并完成处理后,需要调用 tcp_recved 函数。例如,在 client_recv_handler 回调函数中,当处理完接收到的 pbuf(数据包缓冲区)中的数据后,就会调用该函数。
4.实现原理
- 更新接收窗口:
tcp_recved函数会根据传入的len参数,更新 TCP 连接的接收窗口大小。接收窗口的大小表示接收方当前还能接收多少字节的数据。当应用程序处理完数据后,接收窗口会相应地增大,允许发送方发送更多的数据。 - 发送确认信息:lwIP 会根据更新后的接收窗口信息,向发送方发送确认信息(ACK),告知发送方可以继续发送的数据范围。
4.示例代码
#include "lwip/tcp.h"// 接收回调函数
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{if (p != NULL) {// 处理接收到的数据// 这里可以添加具体的数据处理逻辑,如解析数据、存储数据等// ...// 告知 lwIP 已经处理完这些数据tcp_recved(tpcb, p->tot_len);// 释放 pbuf 占用的内存pbuf_free(p);} else if (err == ERR_OK) {// 连接关闭处理tcp_close(tpcb);}return ERR_OK;
}
5.注意事项
- 及时调用:在处理完接收到的数据后,应及时调用
tcp_recved函数,否则接收窗口不会更新,发送方可能会认为接收方没有足够的空间来接收新的数据,从而暂停数据发送,导致网络性能下降,我这次就是因为没有调用这个函数,客户端与服务器端频繁通讯出现了连接失效的问题。 - 数据长度准确:传入
tcp_recved函数的len参数必须准确表示应用程序已经处理的数据长度,否则可能会导致接收窗口大小计算错误,影响数据的正常传输。 - 内存管理:在调用
tcp_recved函数后,通常需要释放pbuf占用的内存,避免内存泄漏。可以使用pbuf_free函数来释放内存。
四、TCP服务端完整代码
#include "tcp_demo.h"
extern u8* GETDATA_BUFF8;
extern short *GETDATA_BUFF16;
extern u8 nowtype;extern OS_SEM Gather_SEM;#define TCP_SERVER_PORT 8088 // 定义TCP服务器监听的端口号,可按需修改
#define MAX_CONNECTIONS 2 // 最大允许同时连接的客户端数量
#define RX_BUFFER_SIZE 1024 // 接收缓冲区大小// 用于存储客户端连接的结构体数组
struct tcp_pcb *tcp_server_pcbs[MAX_CONNECTIONS];// 用于保护对tcp_server_pcbs数组操作的互斥信号量
OS_SEM tcp_server_pcbs_sem;// 处理客户端连接的函数
err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err);
// 处理客户端接收数据的函数
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
// 处理客户端发送数据的函数(示例中简单回显数据给客户端)
err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 处理客户端断开连接的函数
void client_err_handler(void *arg, err_t err);
// TCP服务器任务函数
void tcp_server_task(void *p_arg);//TCP客户端任务
#define TCP_PRIO 7
//任务堆栈大小
#define TCP_STK_SIZE 300
//任务控制块
OS_TCB TcpTaskTCB;
//任务堆栈
CPU_STK TCP_TASK_STK[TCP_STK_SIZE];int close_tcp=0;void tcp_server_task(void *p_arg)
{struct tcp_pcb *server_pcb;err_t err;OS_ERR os_err;// 创建一个TCP协议控制块(PCB)用于服务器端server_pcb = tcp_new();if (server_pcb == NULL){printf("Error creating TCP PCB\n");return;}// 绑定服务器的IP地址和端口号err = tcp_bind(server_pcb, IP_ADDR_ANY, TCP_SERVER_PORT);if (err!= ERR_OK){printf("Error binding TCP socket: %d\n", err);tcp_close(server_pcb);return;}// 将服务器PCB设置为监听状态,等待客户端连接server_pcb = tcp_listen(server_pcb);if (server_pcb == NULL){printf("Error listening for connections\n");tcp_close(server_pcb);return;}// 设置接受客户端连接的回调函数tcp_accept(server_pcb, client_connection_handler);while (1){// 任务可以在这里进行适当的阻塞等待,避免过度占用CPU资源OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&os_err); //延时100ms}
}err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err)
{OS_ERR os_err;int client_index;if (err!= ERR_OK){return err;}// 获取互斥信号量,保护对tcp_server_pcbs数组的访问OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量if(tcp_server_pcbs[close_tcp]!=NULL){tcp_close(tcp_server_pcbs[close_tcp]);}tcp_server_pcbs[close_tcp]=newpcb;if(close_tcp==0){close_tcp=1;}else{close_tcp=0;}// 设置接收、发送和错误处理的回调函数tcp_recv(newpcb, client_recv_handler);tcp_sent(newpcb, client_send_handler);tcp_err(newpcb, client_err_handler);OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);return ERR_OK;
}err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{OS_ERR os_err;if (err == ERR_OK && p!= NULL){// 将接收到的pbuf数据复制到本地缓冲区(这里简单示例,可优化)/**char rx_buffer[RX_BUFFER_SIZE];int copied_bytes = 0;struct pbuf *q = p;while (q!= NULL){int bytes_to_copy = (q->len < (RX_BUFFER_SIZE - copied_bytes))? q->len : (RX_BUFFER_SIZE - copied_bytes);memcpy(&rx_buffer[copied_bytes], q->payload, bytes_to_copy);copied_bytes += bytes_to_copy;q = q->next;}**/// 这里可以对接收到的数据进行处理,比如根据协议解析等,现在简单回显数据给客户端//tcp_write(tpcb, rx_buffer, copied_bytes, TCP_WRITE_FLAG_COPY);//tcp_output(tpcb);OSSemPend(&Gather_SEM,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量u16_t free_space = tcp_sndbuf(tpcb);if (free_space >= 1441) {// 缓冲区有足够空间,写入数据tcp_write(tpcb, GETDATA_BUFF8, 1441, TCP_WRITE_FLAG_COPY);tcp_output(tpcb);} else {// 缓冲区空间不足,进行相应处理,如等待或丢弃数据// 这里简单打印提示信息printf("Send buffer is not enough!\n");}OSSemPost (&Gather_SEM,OS_OPT_POST_1,&os_err); //发送信号量// 释放接收到的pbuf内存tcp_recved(tpcb, p->tot_len);pbuf_free(p);}else if (err == ERR_OK && p == NULL){OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量// 客户端关闭了连接,正常处理for (int i = 0; i < MAX_CONNECTIONS; i++){if (tcp_server_pcbs[i] == tpcb){tcp_server_pcbs[i] = NULL;break;}}OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);tcp_close(tpcb);}else{// 出现错误情况,关闭连接tcp_close(tpcb);}return ERR_OK;
}err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len)
{// 这里可以根据实际发送情况做一些后续处理,当前示例只是简单回显,无需额外操作return ERR_OK;
}void client_err_handler(void *arg, err_t err)
{struct tcp_pcb *tpcb = (struct tcp_pcb *)arg;OS_ERR os_err;// 获取互斥信号量,保护对tcp_server_pcbs数组的访问OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量for (int i = 0; i < MAX_CONNECTIONS; i++){if (tcp_server_pcbs[i] == tpcb){tcp_server_pcbs[i] = NULL;break;}}OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);tcp_close(tpcb);
}//创建TCP线程
//返回值:0 TCP创建成功
// 其他 TCP创建失败
u8 tcp_demo_init(void)
{OS_ERR err;CPU_SR_ALLOC();OS_CRITICAL_ENTER();//进入临界区//创建TCP任务OSTaskCreate((OS_TCB * )&TcpTaskTCB, (CPU_CHAR * )"tcp task", (OS_TASK_PTR )tcp_server_task, (void * )0, (OS_PRIO )TCP_PRIO, (CPU_STK * )&TCP_TASK_STK[0], (CPU_STK_SIZE)TCP_STK_SIZE/10, (CPU_STK_SIZE)TCP_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void * )0, (OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,(OS_ERR * )&err);OS_CRITICAL_EXIT(); //退出临界区return err;
}
1. 全局变量和宏定义
- 端口和连接相关:
TCP_SERVER_PORT:定义了 TCP 服务器监听的端口号,这里设为 8088。MAX_CONNECTIONS:规定了服务器最大允许同时连接的客户端数量,为 2。RX_BUFFER_SIZE:设置了接收缓冲区的大小,为 1024 字节。
- 客户端连接数组和信号量:
tcp_server_pcbs:是一个用于存储客户端连接的struct tcp_pcb类型的数组,长度为MAX_CONNECTIONS。tcp_server_pcbs_sem:用于保护对tcp_server_pcbs数组操作的互斥信号量。
- 其他外部变量:
GETDATA_BUFF8、GETDATA_BUFF16、nowtype是外部定义的变量,Gather_SEM是一个外部信号量。
2. 函数声明
声明了多个处理 TCP 连接、数据接收、发送、错误处理以及服务器任务的函数,具体如下:
client_connection_handler:处理客户端连接请求。client_recv_handler:处理客户端发送的数据。client_send_handler:处理数据发送完成后的操作。client_err_handler:处理连接错误。tcp_server_task:TCP 服务器的主任务函数。
3. TCP 服务器任务函数 tcp_server_task
- 创建和绑定 PCB:使用
tcp_new()创建一个 TCP 协议控制块(PCB),并通过tcp_bind()将其绑定到指定的 IP 地址(IP_ADDR_ANY表示监听所有可用的 IP 地址)和端口号。 - 监听连接:调用
tcp_listen()将服务器 PCB 设置为监听状态,等待客户端连接。 - 设置回调函数:使用
tcp_accept()设置接受客户端连接的回调函数为client_connection_handler。 - 任务循环:在一个无限循环中,使用
OSTimeDlyHMSM()函数进行 100ms 的延时,避免任务过度占用 CPU 资源。
4. 客户端连接处理函数 client_connection_handler
- 错误检查:检查连接是否存在错误,若有则直接返回错误码。
- 管理连接数组:获取互斥信号量
tcp_server_pcbs_sem,保护对tcp_server_pcbs数组的访问。如果数组中已有连接,关闭该连接,然后将新的连接添加到数组中。 - 设置回调函数:为新的客户端连接设置接收、发送和错误处理的回调函数,分别为
client_recv_handler、client_send_handler和client_err_handler。 - 释放信号量:操作完成后,释放互斥信号量。
5. 客户端数据接收处理函数 client_recv_handler
- 正常接收数据:当接收到数据(
err == ERR_OK && p != NULL)时,先获取Gather_SEM信号量,检查发送缓冲区是否有足够空间。若空间足够,将GETDATA_BUFF8中的 1441 字节数据写入发送缓冲区并尝试发送;若空间不足,打印提示信息。处理完后释放Gather_SEM信号量,调用tcp_recved()告知 lwIP 已处理的数据长度,并释放接收到的pbuf内存。 - 客户端关闭连接:当客户端关闭连接(
err == ERR_OK && p == NULL)时,获取tcp_server_pcbs_sem信号量,从tcp_server_pcbs数组中移除该连接,然后释放信号量并关闭连接。 - 错误处理:若出现其他错误情况,直接关闭连接。
6. 客户端数据发送处理函数 client_send_handler
该函数目前只是简单返回 ERR_OK,可根据实际发送情况添加后续处理逻辑。
7. 客户端连接错误处理函数 client_err_handler
获取 tcp_server_pcbs_sem 信号量,从 tcp_server_pcbs 数组中移除出现错误的连接,释放信号量并关闭连接。
8. TCP 任务创建函数 tcp_demo_init
使用 uC/OS-II 的 OSTaskCreate() 函数创建 tcp_server_task 任务,指定任务的优先级、堆栈等参数。如果任务创建成功,返回 0;否则返回相应的错误码。
相关文章:
stm32 lwip tcp服务端频繁接收连接失效问题解决(tcp_recved)
一、问题描述 最近用stmf429单片机作为TCP服务端遇到一个问题,就是客户端特别频繁的发送消息,过一段时间以后,客户端的请求不再被客户端接收到,而且服务器端监控的掉线回调函数也不会被调用,好像这个连接就凭空的消失…...
java项目之基于SSM会议管理系统的设计与实现源码(ssm+mysql)
项目简介 基于SSM会议管理系统的设计与实现实现了以下功能: 基于SSM会议管理系统的设计与实现的主要使用者分为:管理员登录后修改个人的密码。用户管理中,对公司内的用户进行管理,包括会议管理员和员工,管理部门信息…...
腿足机器人之二- 运动控制概览
腿足机器人之二运动控制概览 高层运动规划MPCRL 中层逆运动学和逆动力学底层执行器控制传感器校正 上一篇博客是腿足机器人的骨架和关节的机械和电气组件,关节不仅需要通过机械设计实现复杂的运动能力,还必须通过电子组件和控制系统来精确控制这些运动。…...
【MySQL】基础篇
1. MySQL中的NULL值是怎么存放的? MySQL的compact行格式中会用【NULL值列表】来标记值为NULL的列,NULL值不会存储在行格式中的真实数据部分。 NULL值列表会占用1字节空间,当表中所有字段都被定义成NOT NULL,行格式中就不会有NULL值…...
vscode环境搭建
目录 一、安装VSCode 二、安装Python 三、安装Anaconda(可选,但推荐) 四、安装深度学习相关库 五、配置VSCode 六、 结果可视化 一、安装VSCode 访问官网下载:从VSCode官方网站下载适合你操作系统的安装包。安装:运行安…...
tp whereOr用法2
有时候会用到多个whereOr 可以用闭包来完成查询 Db::name(table_name)->whereOr([[age,null,],[age,,]])->select();Db::name(table_name)->whereOr([[birthday,null,],[birthday,,]])->select();这两个是OR(或)关系 Db::name(table_name)->whereOr([[age,nul…...
前端面试题目---页面抖动的原因、如何避免、如何解决
前端页面抖动是一个常见且影响用户体验的问题,下面将从抖动发生的场景、解决办法以及预防措施三个方面进行详细阐述。 页面抖动发生的场景 1. 元素尺寸动态变化 图片加载:当页面中图片的宽高没有预先设定,在图片加载完成后,其实…...
Spring Boot整合DeepSeek实现AI对话(API调用和本地部署)
本篇文章会分基于DeepSeek开放平台上的API,以及本地私有化部署DeepSeek R1模型两种方式来整合使用。 本地化私有部署可以参考这篇博文 全面认识了解DeepSeek利用ollama在本地部署、使用和体验deepseek-r1大模型 Spring版本选择 根据Spring官网的描述 Spring AI是一…...
DeepSeek 的 API 服务引入 WPS Office
以下是将 DeepSeek 的 API 服务引入 WPS Office 的通用集成教程。以调用 DeepSeek 的 AI 功能(如文本生成、数据分析)为例,假设你需要通过 WPS 的宏或插件调用外部 API: 准备工作 注册 DeepSeek 账号并获取 API Key 访问 DeepSe…...
在Vue中,JavaScript数组常用方法,添加,插入,查找,删除等整理
在Vue中,JavaScript数组常用,添加,插入,查找,删除等整理 1.splice()方法可以直接修改原数组,通过指定要删除元素的索引来删除它。 例: let index // 要删除的元素的索引; this.array.splice(i…...
树莓派上 基于Opencv 实现人脸检测与人脸识别
一,需求 基于树莓派4b,usb1080p摄像头,实现人脸检测与人脸识别。尝试了海陵科的模组和百度的sdk。海陵科的模组无法录入人脸,浪费了100多块钱。百度的sdk 在树莓派上也无法录入人脸,官方解决不了。最后只能用opencv自…...
Unity 接入Tripo 文生模型,图生模型
官方网站:https://www.tripo3d.ai/app/home自行注册账号并且登陆下载Unity插件:https://cdn-web.tripo3d.ai/plugin/tripo-unity.zip申请apikey: https://platform.tripo3d.ai/api-keys使用(后续过程就按照第二步下载的插件里面的…...
Redis常见数据结构
目录 基本介绍 特点: 全局命令 数据类型: String Hash List Set Zset 基本介绍 Redis是一个在内存中存储数据的中间件,可作为数据库,缓存,消息队列等。 特点: 持久化:Redis会把数据存储在内存中…...
fps动作系统9:动画音频
文章目录 音频单播放音频文件也是可以的,只不过是2d声音。创建音频蓝图cue(音效)音量乘数 衰减(空间效果)音量自然声音内部半径衰减距离 空间化双声道 绑定到动画动画序列轨道 音频 单播放音频文件也是可以的,只不过是2d声音。 创建音频蓝图 cue(音效…...
十四、GitLab 流水线自动化部署之 Windows Server
一、软件下载 本文章采用 Windows 版本的 Gitlab Runner 客户端,下载地址参考如下: 链接:https://pan.baidu.com/s/1nktWQGR4toRpgzEn9Qj3kQ?pwd0p1d 提取码:0p1d --来自百度网盘超级会员V7的分享 二、环境安装 1、Java 环境…...
数据库数据恢复—MongoDB丢失_mdb_catalog.wt文件导致报错的数据恢复案例
MongoDB数据库存储模式为文档数据存储库,存储方式是将文档存储在集合之中。 MongoDB数据库是开源数据库,同时提供具有附加功能的商业版本。 MongoDB中的数据是以键值对(key-value pairs)的形式显示的。在模式设计上,数据库受到的约束更少。这…...
mysql8.0使用MGR实现高可用与利用MySQL Router构建读写分离MGR集群
MGR是MySQL Group Replication的缩写,即MySQL组复制。 在以往,我们一般是利用MySQL的主从复制或半同步复制来提供高可用解决方案,但这存在以下几个比较严重的问题: 主从复制间容易发生复制延迟,尤其是在5.6以前的版本…...
基于Ubuntu2404搭建k8s-1.31集群
k8s 1.31 环境初始化安装Container安装runc安装CNI插件部署k8s集群安装crictl使用kubeadm部署集群节点加入集群部署Calico网络配置dashboard 本实验基于VMware创建的Ubuntu2404虚拟机搭建k8s 1.31版本集群,架构为一主一从,容器运行时使用Container&#…...
Golang的图形编程应用案例
Golang的图形编程应用案例 一、Golang的图形编程概述 是一种高效、可靠且易于使用的编程语言,具有并发性和简洁性,因此在图形编程领域也有着广泛的应用。Golang的图形编程主要通过各种图形库来实现,其中最知名的是Go图形库(Ebiten…...
PostgreSQL 错误代码 23505 : ERROR: duplicate key value violates unique constraint
目录 1. 确认错误信息2. 检查数据3. 处理重复数据4. 检查唯一约束5. 添加唯一约束6. 使用事务处理并发操作7. 使用触发器8. 使用 ON CONFLICT 子句9. 重置序列10. 捕获异常并重试 错误代码 23505 是 PostgreSQL 中表示违反唯一约束(unique violation)的标…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
