当前位置: 首页 > news >正文

环形缓冲区

什么是环形缓冲区

环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。

环形缓冲区的用途

  1. 串口通信
    在嵌入式设备中,串口是常用的通信接口。环形缓冲区可用于缓存收发数据,平衡通信速率差异。
  2. 音视频数据处理
    音视频数据往往是连续的数据流。使用环形缓冲区可以平滑数据的生成和消耗,避免数据丢失或延迟。
  3. 传感器数据采集
    传感器数据通常以固定频率采样。环形缓冲区可作为数据采集和处理之间的缓冲,降低实时性要求。
  4. 多线程数据传递
    在多线程编程中,环形缓冲区是一种简单高效的线程间通信方式,无需复杂的同步操作。
  5. 数据打包与解析
    一些通信协议使用特定的数据帧格式。环形缓冲区可用于数据的打包和解析,保证数据的完整性。

环形缓冲区的实现示例

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>// 环形缓冲区大小
#define BUFFER_SIZE 256// 定义解析器状态
typedef enum {STATE_WAIT_START,    // 等待消息开始STATE_READ_LENGTH,   // 读取消息长度STATE_READ_DATA      // 读取消息数据
} ParserState;typedef struct {uint8_t buffer[BUFFER_SIZE];volatile uint16_t head;volatile uint16_t tail;
} RingBuffer;void RingBuffer_Init(RingBuffer *rb) {rb->head = 0;rb->tail = 0;
}bool RingBuffer_Write(RingBuffer *rb, uint8_t data) {uint16_t next = (rb->head + 1) % BUFFER_SIZE;if (next == rb->tail) {// 缓冲区满return false;}rb->buffer[rb->head] = data;rb->head = next;return true;
}bool RingBuffer_Read(RingBuffer *rb, uint8_t *data) {if (rb->head == rb->tail) {// 判满return false;}*data = rb->buffer[rb->tail];rb->tail = (rb->tail + 1) % BUFFER_SIZE;return true;
}// 处理完整消息的函数
void process_message(const uint8_t *msg, uint8_t length) {for (uint8_t i = 0; i < length; i++) {printf("%c", msg[i]);}printf("\n");
}// 有限状态机解析器
void parse_messages(RingBuffer *rb) {static ParserState state = STATE_WAIT_START;static uint8_t msg_length = 0;static uint8_t msg_index = 0;static uint8_t message[128]; //uint8_t byte;while (RingBuffer_Read(rb, &byte)) {switch (state) {case STATE_WAIT_START:  // 等待消息起始if (byte == 0xAA) { // 0xAA是消息起始标志state = STATE_READ_LENGTH;}break;case STATE_READ_LENGTH:  // 读取消息长度msg_length = byte;if (msg_length > 0 && msg_length < sizeof(message)) {msg_index = 0;state = STATE_READ_DATA;} else {// 无效长度,重置状态state = STATE_WAIT_START;}break;case STATE_READ_DATA:  // 读取消息数据message[msg_index++] = byte;if (msg_index >= msg_length) {process_message(message, msg_length);state = STATE_WAIT_START;}break;default:state = STATE_WAIT_START;break;}}
}// 发送函数
void threadA_send(RingBuffer *rb, const uint8_t *msg, uint8_t length) {RingBuffer_Write(rb, 0xAA); // 起始标志RingBuffer_Write(rb, length); // 长度字段for (uint8_t i = 0; i < length; i++) {RingBuffer_Write(rb, msg[i]);}
}// 接收函数
void threadB_receive(RingBuffer *rb) {parse_messages(rb);
}void test_ring_buffer(void) {// 1. 初始化环形缓冲区RingBuffer rb;RingBuffer_Init(&rb);printf("=== 环形缓冲区测试开始 ===\n\n");// 2. 测试基本消息发送和接收printf("测试1: 基本消息收发\n");const uint8_t test_msg1[] = "Hello World";threadA_send(&rb, test_msg1, sizeof(test_msg1) - 1);threadB_receive(&rb);// 3. 测试空缓冲区printf("\n测试2: 空缓冲区读取\n");uint8_t temp;if (!RingBuffer_Read(&rb, &temp)) {printf("空缓冲区测试通过: 无法从空缓冲区读取数据\n");}// 4. 测试缓冲区满状态printf("\n测试3: 缓冲区满状态\n");uint8_t large_msg[BUFFER_SIZE];for (int i = 0; i < BUFFER_SIZE; i++) {large_msg[i] = 'A' + (i % 26);  // 填充A-Z循环}bool write_result = true;int write_count = 0;while (write_result && write_count < BUFFER_SIZE + 10) {write_result = RingBuffer_Write(&rb, large_msg[write_count % BUFFER_SIZE]);write_count++;}printf("写入计数: %d (应小于缓冲区大小)\n", write_count - 1);// 5. 测试长消息分段发送printf("\n测试4: 长消息分段发送\n");RingBuffer_Init(&rb);  // 重新初始化const uint8_t long_msg[] = "This is a long message to test multiple segments";const int SEGMENT_SIZE = 10;for (size_t i = 0; i < (size_t)(sizeof(long_msg) - 1); i += (size_t)SEGMENT_SIZE) {size_t current_length = ((sizeof(long_msg) - 1 - i) < (size_t)SEGMENT_SIZE) ? (sizeof(long_msg) - 1 - i) : (size_t)SEGMENT_SIZE;threadA_send(&rb, &long_msg[i], current_length);threadB_receive(&rb);}// 6. 测试无效消息printf("\n测试5: 无效消息处理\n");uint8_t invalid_msg[] = {0xAA, 0xFF, 0x01, 0x02};  // 无效长度for (size_t i = 0; i < sizeof(invalid_msg); i++) {RingBuffer_Write(&rb, invalid_msg[i]);}threadB_receive(&rb);// 7. 测试快速读写切换printf("\n测试6: 快速读写切换\n");const uint8_t test_msg2[] = "Test";for (int i = 0; i < 5; i++) {threadA_send(&rb, test_msg2, sizeof(test_msg2) - 1);threadB_receive(&rb);}printf("\n=== 环形缓冲区测试完成 ===\n");
}int main() {test_ring_buffer();return 0;
}

总结

与传统的数组或链表相比,环形缓冲区有以下优点:
1.无需频繁移动数据。环形缓冲区的读写指针移动不会导致数据搬移,效率更高。
2.自动处理缓冲区"满"和"空"的状态。通过读写指针的关系可以判断缓冲区状态,无需额外的计数器。
3. 适用于生产者-消费者模型。一个线程写入数据,另一个线程读取数据,天然支持异步处理。

相关文章:

环形缓冲区

什么是环形缓冲区 环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。…...

jQuery-Word-Export 使用记录及完整修正文件下载 jquery.wordexport.js

参考资料&#xff1a; jQuery-Word-Export导出word_jquery.wordexport.js下载-CSDN博客 近期又需要自己做个 Html2Doc 的解决方案&#xff0c;因为客户又不想要 Html2pdf 的下载了&#xff0c;当初还给我费尽心思解决Html转pdf时中文输出的问题&#xff08;html转pdf文件下载之…...

云服务器部署WebSocket项目

WebSocket是一种在单个TCP连接上进行全双工通信的协议&#xff0c;其设计的目的是在Web浏览器和Web服务器之间进行实时通信&#xff08;实时Web&#xff09; WebSocket协议的优点包括&#xff1a; 1. 更高效的网络利用率&#xff1a;与HTTP相比&#xff0c;WebSocket的握手只…...

C#+数据库 实现动态权限设置

将权限信息存储在数据库中&#xff0c;支持动态调整。根据用户所属的角色、特定的功能模块&#xff0c;动态加载权限” 1. 数据库设计 根据这种需求&#xff0c;可以通过以下表设计&#xff1a; 用户表 (Users)&#xff1a;存储用户信息。角色表 (Roles)&#xff1a;存储角色…...

(原创)Android Studio新老界面UI切换及老版本下载地址

前言 这两天下载了一个新版的Android Studio&#xff0c;发现整个界面都发生了很大改动&#xff1a; 新的界面的一些设置可参考一些博客&#xff1a; Android Studio新版UI常用设置 但是对于一些急着开发的小伙伴来说&#xff0c;没有时间去适应&#xff0c;那么怎么办呢&am…...

Ubuntu24虚拟机-gnome-boxes

推荐使用gnome-boxes&#xff0c; virtualbox构建失败&#xff0c;multipass需要开启防火墙 sudo apt install gnome-boxes创建完毕&#xff5e;...

k8s rainbond centos7/win10 -20241124

参考 https://www.rainbond.com/ 国内一站式云原生平台 对centos7环境支持不太行 [lighthouseVM-16-5-centos ~]$ curl -o install.sh https://get.rainbond.com && bash ./install.sh 2024-11-24 09:56:57 ERROR: Ops! Docker daemon is not running. Start docke…...

SpringBoot+Vue滑雪社区网站设计与实现

【1】系统介绍 研究背景 随着互联网技术的快速发展和冰雪运动的普及&#xff0c;滑雪作为一种受欢迎的冬季运动项目&#xff0c;吸引了越来越多的爱好者。与此同时&#xff0c;社交媒体和在线社区平台的兴起为滑雪爱好者提供了一个交流经验、分享心得、获取信息的重要渠道。滑…...

MySql.2

sql查询语句执行过程 SQL 查询语句的执行过程是一个复杂的过程&#xff0c;涉及多个步骤。以下是典型的关系数据库管理系统 (RDBMS) 中 SQL 查询语句的执行过程概述&#xff1a; 1. ‌客户端发送查询‌ 用户通过 SQL 客户端或应用程序发送 SQL 查询语句给数据库服务器。 2. ‌…...

算法之区间和题目讲解

题干 难度&#xff1a;简单 题目分析 题目要求算出每个指定区间内元素的总和。 然而&#xff0c;区间在输入的最下面&#xff0c;所以按照暴力破解的思路&#xff0c;我们首先要遍历数组&#xff0c;把它的值都存进去。 然后&#xff0c;遍历下面的区间&#xff0c;从索引a…...

价格分类(神经网络)

# 1.导入依赖包 import timeimport torch import torch.nn as nn import torch.optim as optimfrom torch.utils.data import TensorDataset, DataLoader from sklearn.model_selection import train_test_splitimport numpy as np import pandas as pd import matplotlib.pypl…...

对智能电视直播App的恶意监控

首先我们要指出中国广电总局推出的一个政策性文件是恶意监控的始作俑者&#xff0c;这个广电总局的政策性文件禁止智能电视和电视盒子安装直播软件。应该说这个政策性文件是为了保护特殊利益集团&#xff0c;阻挠技术进步和发展的。 有那么一些电视机和电视盒子的厂商和电信运…...

【JavaEE初阶】多线程初阶下部

文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比&#xff08;面试题&#xff09; 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言…...

macOS上进行Ant Design Pro实战教程(一)

由于一个AI项目的前端使用了umi&#xff0c;本教程根据阿里官网上的 《Ant Design 实战教程&#xff08;beta 版&#xff09;》来实操一下&#xff0c;我使用macOS操作系统&#xff0c;VS Code 开发环境。 一、开发环境 1、安装nodejs, npm, yarn 官网上建议使用cnpm&#xf…...

智能合约运行原理

点个关注吧&#xff01;&#xff01; 用一句话来总结&#xff0c;智能合约就像是一个自动售货机&#xff1a;你投入硬币&#xff08;触发条件&#xff09;&#xff0c;选择商品&#xff08;执行合约&#xff09;&#xff0c;然后机器就会自动给你商品&#xff08;执行结果&…...

安卓动态添加View

在安卓应用中&#xff0c;有很多时候需要动态添加View。比如从后台获取商品列表&#xff0c;根据商品数量在页面渲染对应数量的条目&#xff0c;这时候就需要动态添加View。 1.动态添加View的方法 动态添加View有两种方法&#xff1a; 由代码生成子View&#xff1a;这种方式…...

前端预览pdf文件流

需求 后端接口返回pdf文件流&#xff0c;实现新窗口预览pdf。 解决方案 把后端返回的pdf文件流转为blob路径&#xff0c;利用浏览器直接预览。 具体实现步骤 1、引入axios import axios from axios;2、创建预览方法&#xff08;具体使用时将axios的请求路径替换为你的后端…...

【测试工具JMeter篇】JMeter性能测试入门级教程(一)出炉,测试君请各位收藏了!!!

一、前言 Apache JMeter是纯Java的开源软件&#xff0c;最初由Apache软件基金会的Stefano Mazzocchi开发&#xff0c;旨在加载测试功能行为和测量性能。可以使用JMeter进行性能测试&#xff0c;即针对重负载、多用户和并发流量测试Web应用程序。 我们选择JMeter原因 是否测试过…...

【zookeeper03】消息队列与微服务之zookeeper集群部署

ZooKeeper 集群部署 1.ZooKeeper 集群介绍 ZooKeeper集群用于解决单点和单机性能及数据高可用等问题。 集群结构 Zookeeper集群基于Master/Slave的模型 处于主要地位负责处理写操作)的主机称为Leader节点&#xff0c;处于次要地位主要负责处理读操作的主机称为 follower 节点…...

从 Llama 1 到 3.1:Llama 模型架构演进详解

编者按&#xff1a; 面对 Llama 模型家族的持续更新&#xff0c;您是否想要了解它们之间的关键区别和实际性能表现&#xff1f;本文将探讨 Llama 系列模型的架构演变&#xff0c;梳理了 Llama 模型从 1.0 到 3.1 的完整演进历程&#xff0c;深入剖析了每个版本的技术创新&#…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南

在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...

2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版

1.题目描述 2.思路 当前的元素可以重复使用。 &#xff08;1&#xff09;确定回溯算法函数的参数和返回值&#xff08;一般是void类型&#xff09; &#xff08;2&#xff09;因为是用递归实现的&#xff0c;所以我们要确定终止条件 &#xff08;3&#xff09;单层搜索逻辑 二…...