上位机开发笔记:环形缓冲区

2023-05-16

文章目录

  • 前言
  • 1. 环形缓冲区工作机制
    • 1.1 实现原理
    • 1.2 区分缓冲区满或者空
      • 1) 总是保持一个存储单元为空
      • 2) 使用计数数据
      • 3) 镜像指示位
  • 2. Qt实现环形缓冲区
    • 2.1 QByteArray环形缓冲区
    • 2.2 QSemaphore实现环形缓冲区
  • 参考资料


前言

环形缓冲区(Ring buffer/Circular buffer)或称环形队列,是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。比如对通信中接收到的数据进行处理,当数据量太大或者处理速度跟不上接收速度时,就会将之前还未处理的数据覆盖掉,即出现丢包的现象。此时,使用环形缓冲区就可以有效解决这一问题。


1. 环形缓冲区工作机制

环形缓冲区是一种先进先出(FIFO)的数据结构,本质上就是将一个顺序存储结构的访问逻辑由线性处理为环形,通常由一个固定大小的顺序容器实现,通过一定的方法对数据的读写范围进行控制。

1.1 实现原理

一般地,环形缓冲区通过读写指针控制读写范围:

  • 读指针:指向缓冲区内下一个可读数据的位置。
  • 写指针:指向缓冲区内下一个可写数据的位置。

初始化时,读指针和写指针都指向缓冲区的0号元素,通过移动这两个指针即可对缓冲区的数据进行读写操作。当指针移动到容器末尾时,立即返回容器头以实现首尾相连的环形结构。

在这里插入图片描述

1.2 区分缓冲区满或者空

缓冲区是满或是空,都有可能出现读指针与写指针指向同一位置,有多种策略用于检测缓冲区是满或是空:

在这里插入图片描述

1) 总是保持一个存储单元为空

缓冲区中总是有一个存储单元保持未使用状态,此时缓冲区最多存入size - 1个数据。如果读写指针指向同一位置,则缓冲区为空;如果读指针位于写指针的相邻后一个位置,则缓冲区为满。

数据结构:

typedef struct
{
    unsigned int size;
    unsigned int writeIndex;
    unsigned int readIndex;
    unsigned char *buff = nullptr;
} RING_BUFFER;

缓冲区满空判断:

bool ringBufferIsEmpty(const RING_BUFFER &rb)
{
    return rb.writeIndex == rb.readIndex; //如果读写指针指向同一位置,则缓冲区为空。
}

bool ringBufferIsFull(const RING_BUFFER &rb)
{
    //通过除余缓冲区大小实现环形索引
    return ((rb.writeIndex + 1) % rb.size) == (rb.readIndex % rb.size); //如果读指针位于写指针的相邻后一个位置,则缓冲区为满。
}

缓冲区写入数据:

一般来说,写操作禁止覆盖缓冲区的数据,采取返回一个错误码或者抛出异常的策略。但在某些情况下覆盖环形缓冲区中未被处理的数据是允许的,特别是在多媒体处理时。例如,音频的生产者可以覆盖掉声卡尚未来得及处理的音频数据。

int writeToRingBuffer(RING_BUFFER &rb, unsigned char *szData, int iLen)
{
    if (ringBufferIsFull(rb))
    {
        return -1;
    }

    //先判断可写数据数量
    int available = 0;
    if (rb.writeIndex >= rb.readIndex)
    {
        available = rb.size - (rb.writeIndex - rb.readIndex) - 1; //写指针不能写到读指针所在位置,要保持一个存储单元
    }
    else
    {
        available = rb.readIndex - rb.writeIndex - 1;
    }
    if (iLen > available)
    {
        // return -2; //抛出异常并退出
        iLen = available; //修改写入数据数量
    }

    //跨越边界要进行分段拷贝
    if ((rb.writeIndex + iLen) >= rb.size)
    {
        unsigned int len1 = rb.size - rb.writeIndex;
        unsigned int len2 = iLen - len1;
        memcpy(rb.buff + rb.writeIndex, szData, len1);
        memcpy(rb.buff, szData + len1, len2);
        rb.writeIndex = len2;
    }
    else
    {
        memcpy(rb.buff + rb.writeIndex, szData, iLen);
        rb.writeIndex += iLen;
    }
    return 0;
}

缓冲区读取数据:

int readFromRingBuffer(RING_BUFFER &rb, unsigned char *szData, int &iLen)
{
    if (ringBufferIsEmpty(rb))
    {
        return -1;
    }

    //先判断可读数据数量
    int available = 0;
    if (rb.writeIndex >= rb.readIndex)
    {
        available = rb.writeIndex - rb.readIndex; //读指针可以读到写指针的位置,此时缓冲区为空
    }
    else
    {
        available = rb.size - (rb.readIndex - rb.writeIndex);
    }
    if (iLen > available)
    {
        iLen = available;
    }

    //跨越边界要进行分段拷贝
    if ((rb.readIndex + iLen) >= rb.size)
    {
        unsigned int len1 = rb.size - rb.readIndex;
        unsigned int len2 = iLen - len1;
        memcpy(szData, rb.buff + rb.readIndex, len1);
        memcpy(szData + len1, rb.buff, len2);
        rb.readIndex = len2;
    }
    else
    {
        memcpy(szData, rb.buff + rb.readIndex, iLen);
        rb.readIndex += iLen;
    }
    return 0;
}

这种策略的优点是简单、粗暴,缺点是语义上实际可存数据量与缓冲区容量不一致,且多线程时需要并发控制。

2) 使用计数数据

这种策略需要在数据结构内添加一个变量保存缓冲区内存储数据的计数,写操作增加计数、读操作减少计数。如果存储数据数量为0,则缓冲区为空;如果存储数据数量等于缓冲区容量,则缓冲区为满。

数据结构:

typedef struct
{
    unsigned int size;
    unsigned int writeIndex;
    unsigned int readIndex;
    unsigned int buffCount; //存储数据计数
    unsigned char *buff = nullptr;
} RING_BUFFER;

缓冲区满空判断:

bool ringBufferIsEmpty(const RING_BUFFER &rb)
{
    return 0 == rb.buffCount;
}

bool ringBufferIsFull(const RING_BUFFER &rb)
{
    return rb.buffCount == rb.size;
}

缓冲区写入数据:

int writeToRingBuffer(RING_BUFFER &rb, unsigned char *szData, int iLen)
{
    if (ringBufferIsFull(rb))
    {
        return -1;
    }

    int available = rb.size - rb.buffCount;
    if (iLen > available)
    {
        // return -2;
        iLen = available;
    }

    if ((rb.writeIndex + iLen) >= rb.size)
    {
        unsigned int len1 = rb.size - rb.writeIndex;
        unsigned int len2 = iLen - len1;
        memcpy(rb.buff + rb.writeIndex, szData, len1);
        memcpy(rb.buff, szData + len1, len2);
        rb.writeIndex = len2;
    }
    else
    {
        memcpy(rb.buff + rb.writeIndex, szData, iLen);
        rb.writeIndex += iLen;
    }
    rb.buffCount += iLen;
    return 0;
}

缓冲区读取数据:

int readFromRingBuffer(RING_BUFFER &rb, unsigned char *szData, int &iLen)
{
    if (ringBufferIsEmpty(rb))
    {
        return -1;
    }

    int available = rb.buffCount;
    if (iLen > available)
    {
        iLen = available;
    }

    if ((rb.readIndex + iLen) >= rb.size)
    {
        unsigned int len1 = rb.size - rb.readIndex;
        unsigned int len2 = iLen - len1;
        memcpy(szData, rb.buff + rb.readIndex, len1);
        memcpy(szData + len1, rb.buff, len2);
        rb.readIndex = len2;
    }
    else
    {
        memcpy(szData, rb.buff + rb.readIndex, iLen);
        rb.readIndex += iLen;
    }
    rb.buffCount -= iLen;
    return 0;
}

该策略的优点是运算速度快、使用简单。缺点是读写操作都需要修改这个存储数据计数,对于多线程访问缓冲区需要并发控制。

还有一种实现方法是用2个变量分别保存写入、读出缓冲区的数据数量,其差值就是缓冲区中尚未被处理的有效数据的数量。如果写入数据数量与读出数据数量相等,则缓冲区为空;如果写入数据数量与读出数据数量的差值等于缓冲区容量,则缓冲区为满。

数据结构:

这种策略甚至可以省略读写指针,通过读写数据数量%缓冲区容量就可以实现索引访问。

typedef struct
{
    unsigned int size;
    unsigned long writeNum;
    unsigned long readNum;
    unsigned char *buff = nullptr;
} RING_BUFFER;

缓冲区满空判断:

bool ringBufferIsEmpty(const RING_BUFFER &rb)
{
    return rb.writeNum == rb.readNum;
}

bool ringBufferIsFull(const RING_BUFFER &rb)
{
    return rb.writeNum == (rb.readNum + rb.size);
}

缓冲区写入数据:

int writeToRingBuffer(RING_BUFFER &rb, unsigned char *szData, int iLen)
{
    if (ringBufferIsFull(rb))
    {
        return -1;
    }

    int available = rb.size - (rb.writeNum - rb.readNum);
    if (iLen > available)
    {
        //return -2;
        iLen = available;
    }

    unsigned int writeIndex = rb.writeNum % rb.size;
    if ((writeIndex + iLen) >= rb.size)
    {
        unsigned int len1 = rb.size - writeIndex;
        unsigned int len2 = iLen - len1;
        memcpy(rb.buff + writeIndex, szData, len1);
        memcpy(rb.buff, szData + len1, len2);
    }
    else
    {
        memcpy(rb.buff + writeIndex, szData, iLen);
    }
    rb.writeNum += iLen;
    return 0;
}

缓冲区读取数据:

int readFromRingBuffer(RING_BUFFER &rb, unsigned char *szData, int &iLen)
{
    if (ringBufferIsEmpty(rb))
    {
        return -1;
    }

    int available = rb.writeNum - rb.readNum;
    if (iLen > available)
    {
        iLen = available;
    }

    unsigned int readIndex = rb.readNum % rb.size;
    if ((readIndex + iLen) >= rb.size)
    {
        unsigned int len1 = rb.size - readIndex;
        unsigned int len2 = iLen - len1;
        memcpy(szData, rb.buff + readIndex, len1);
        memcpy(szData + len1, rb.buff, len2);
    }
    else
    {
        memcpy(szData, rb.buff + readIndex, iLen);
    }
    rb.readNum += iLen;   
    return 0;
}

3) 镜像指示位

一般情况下,环形缓冲区的长度如果是n,则读写指针可访问的范围为0~n-1。该策略规定读写指针的可访问范围为0~2n-1,其中0~n-1对应于常规的逻辑地址空间,n2n-1为镜像逻辑地址空间。

在这里插入图片描述

使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入。在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;如果二者的指示位不同,说明缓冲区为满。

相关代码可查看:维基百科-环形缓冲区

这种方法优点是判断缓冲区满/空很简单、不需要做取余数操作,读写线程可以分别设计专用算法策略,能实现精致的并发控制。 缺点是读写指针各需要额外的一位作为指示位。

2. Qt实现环形缓冲区

2.1 QByteArray环形缓冲区

Qt中专门封装了字节数组QByteArray类,使得我们可以灵活地分配字节数组的内存,不用每次都在堆内存上创建,杜绝了内存泄漏的风险。此外,QByteArray几乎可以说是为通信编程量身定制,不同于QString里面存的是Unicode编码的字符串,QByteArray内存储的是没有经过编码的原始数据。对于上述的环形缓冲区就可以用QByteArray进行改写,这里采用读/写计数的方式:

数据结构:

typedef struct
{
    unsigned int size;
    unsigned int writeNum;
    unsigned int readNum;
    QByteArray buff;
} RING_BUFFER;

初始化:

void initRingBuffer(RING_BUFFER &rb, unsigned int sz)
{
    rb.size = sz;
    rb.readNum = 0;
    rb.writeNum = 0;
    rb.buff.resize(sz); //使用resize()设置数组的大小,每个元素初始化为0
}

缓冲区满空判断与上文相同。

缓冲区写入数据:Qt中绝大部分的通信API都是以char*为基本数据格式的,可以直接将char*作为参数写入缓冲区。

int writeToRingBuffer(RING_BUFFER &rb, char *szData, int iLen)
{
    if (ringBufferIsFull(rb))
    {
        return -1;
    }

    mutex.lock();
    int available = rb.size - (rb.writeNum - rb.readNum);
    mutex.unlock();
    if (iLen > available)
    {
        //return -2;
        iLen = available;
    }

    QByteArray arr(szData, iLen); //通过QByteArray的构造函数将char*转为QByteArray
    for(auto it = arr.cbegin(); it != arr.cend(); ++it)
    {
        rb.buff[rb.writeNum % rb.size] = *it;
        ++rb.writeNum;
    }

    return 0;
}

缓冲区读取数据:

int readFromRingBuffer(RING_BUFFER &rb, QByteArray &arr, int &iLen)
{
    if (ringBufferIsEmpty(rb))
    {
        return -1;
    }

    mutex.lock();
    int available = rb.writeNum - rb.readNum;
    mutex.unlock();
    if (iLen > available)
    {
        iLen = available;
    }

    for (int i = 0; i < iLen; ++i)
    {
        arr.push_back(rb.buff.at(rb.readNum % rb.size));
        ++rb.readNum;
    }
    return 0;
}

2.2 QSemaphore实现环形缓冲区

在实际使用中,对环形缓冲区的读/写操作通常都是由不同线程进行的,不同线程对同一资源进行操作,使用信号量处理更加合适。Qt中使用QSemaphore类实现信号量,用2个信号量分别表示可读/可写资源数量,通过读写计数实现索引。

数据结构:

/*---------------------------global.h----------------------------*/
extern const quint32 BUFFERSIZE;
extern QByteArray g_szBuffer;
extern QSemaphore freeBytes;
extern QSemaphore usedBytes;
extern quint32 writeNum;
extern quint32 readNum;
extern QMutex g_mutex;

/*---------------------------global.cpp----------------------------*/
#include "global.h"

const quint32 BUFFERSIZE = 10;
QByteArray g_szBuffer(BUFFERSIZE, '\0');
QSemaphore freeBytes(BUFFERSIZE);
QSemaphore usedBytes;
quint32 writeNum = 0;
quint32 readNum = 0;
QMutex g_mutex;

缓冲区写入数据:

void writeToRingBuffer(char *szData, int iLen)
{
    if(szData == nullptr || iLen == 0)
    {
        return;
    }
    
	if(freeBytes.available() - iLen < 0)
    {
    	iLen = freeBytes.available();
    }
    freeBytes.acquire(iLen);

    g_mutex.lock();
    QByteArray arr(szData, iLen);
    for(auto it = arr.cbegin(); it != arr.cend(); ++it)
    {
        g_szBuffer[writeNum % BUFFERSIZE] = *it;
        ++writeNum;
    }
	usedBytes.release(iLen);
	g_mutex.unlock();
}

缓冲区读取数据:

void readFromRingBuffer(QByteArray &arr, int &iLen)
{
    if(usedBytes.available() - iLen < 0)
    {
    	iLen = (usedBytes.available() == 0)? 1 : usedBytes.available(); //保证acquire的个数不为0
    }
    usedBytes.acquire(iLen);
    
    mutex.lock();
    for (int i = 0; i < iLen; ++i)
    {
        arr.push_back(g_szBuffer.at(readNum % BUFFERSIZE));
        ++readNum;
    }
    freeBytes.release(iLen);
    mutex.unlock();
}

调用信号量void QSemaphore::acquire(int n = 1)函数,如果n > available(),则线程将一直阻塞直到该信号量获得足够资源。

Qt的信号量使用详见我的这篇博客:Qt学习笔记:多线程的使用


参考资料

Wiki - Circular buffer

环形缓冲区C语言实现

STM32进阶之串口环形缓冲区实现

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

上位机开发笔记:环形缓冲区 的相关文章

  • cnn-欠拟合(underfitting)

    模型不能很好拟合数据 称之为欠拟合 直白的说 xff1a 模型没有找到数据规律或不完整 xff0c 泛化能力不强 在训练和测试数据集上 xff0c 预测或训练结果都和真实结果相差很远 一般解决方法 增加新特征 xff0c 可以考虑加入进特征
  • ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full comm

    类似这种问题 xff0c 不一定是pip版本不对 xff0c 有可能是某个文件不存在 xff0c 例如 在python3 5环境中安装scikit image pip install scikit image 61 61 0 12 就出现
  • AI最新资讯,持续更新

    三星 人造人 项目曝光 xff01 效果太逼真 xff0c 可自主生成新表情 动作和对话 https mp weixin qq com s 417fL3oYVE1vOwsVHMmqow Det3D 首个通用 3D 目标检测框架 https
  • 二维码生成以及扫一扫解析二维码原理

    二维码生成以及扫一扫解析二维码原理 1 生成URL xff0c 确定要通过二维码传达的信息 xff0c 也就是通过扫一扫可以获得地址和数据信息 1 得到随机数 xff0c 用随机数得到签名 xff0c 签名验证身份 String ranSt
  • idea 不能生成target

    1 改module https blog csdn net qq 15304369 article details 93715206 2 pom配置文件 修改为 xff1a lt packaging gt jar lt packaging
  • mariadb 遇到的坑

    mariadb13 3 25 配置文件失效 xff08 折腾了很久 xff09 xff0c 当时我需要配置主从 xff0c 发现binlog无法打开 xff0c 配置了bin log项还是不行 xff01 当my cnf 文件权限过大时 x
  • CV资料汇总

    1 图像风格迁移 Neural Style 简史 https www sohu com a 221597595 236505 2 一文让你理解什么是卷积神经网络 https www jianshu com p 1ea2949c0056
  • skinmagic 对话框菜单展示

    我偶用skinmagic xff0c 在换对话框皮肤时候 xff0c 发现菜单不见了 xff0c 几经折腾 xff0c 发现SetWindowSkin m hWnd 34 Dialog 34 在iniInstance xff08 xff09
  • 系统如何支持高并发

    给个例子 xff0c 你的系统部署的机器是4核8G xff0c 数据库服务器是16核32G 此时假设你的系统用户量总共就10万 xff0c 用户量很少 xff0c 日活用户按照不同系统的场景有区别 xff0c 我们取一个较为客观的比例 xf
  • Firewalld防火墙基础

    目录 一 Firewalld 概述 1 1 Firewalld的简述 1 2 Firewalld 和 iptables的区别 1 3 firewalld的区域 1 3 1 firewalld的9个区域 1 3 2 firewalld的数据处
  • CentOS7安装Oracle JDK

    CentOS7默认安装的是OpenJDK 如果安装Oracle JDK xff0c 需要按如下方式操作 xff1a 1 登录http www oracle com technetwork java javase downloads inde
  • 百度2014校招笔试题(一)

    算法和程序设计题 xff1a 1 题意 xff1a 一幢大楼的底层有1001根电线 xff0c 这些电线一直延伸到大楼楼顶 xff0c 你需要确定底层的1001个线头和楼顶的1001次线头的对应关系 你有一个电池 xff0c 一个灯泡 xf
  • Acwing 1175.最大联通子图(tarjan缩点求scc)

    Acwing 1175 最大连通子图 题意 一个有向图 G 61 V E G 61 V
  • 用github搭建个人(博客网站

    x1f308 博客主页 xff1a 卿云阁 x1f48c 欢迎关注 x1f389 点赞 x1f44d 收藏 留言 x1f4dd x1f31f 本文由卿云阁原创 xff01 x1f64f 作者水平很有限 xff0c 如果发现错误 xff0c
  • 多线程下HashMap的死循环

    多线程下HashMap的死循环 Java的HashMap是非线程安全的 多线程下应该用ConcurrentHashMap 多线程下 HashMap 的问题 xff08 这里主要说死循环问题 xff09 xff1a 1 多线程put操作后 x
  • 找出一个图中所有的强连通子图

    如果一个有向图中的没对顶点都可以从通过路径可达 xff0c 那么就称这个图是强连通的 一个 strongly connected component就是一个有向图中最大的强连通子图 下图中就有三个强连通子图 xff1a 应用kosaraju
  • win7启动分区不存在,使用分区工具修正

    DiskGenius 分区右键 激活当前分区
  • getElementById获取不到td标签

    一次测试中发现 然后使用getElementById获取不到此标签 xff0c 将td改成div即可 不知道是不是单独使用td标签的问题 code
  • 应用宝YSDK支付接入技术细节

    前言 应用宝是出了名的坑 xff0c 主要体现在 xff1a 文档杂乱繁多信息不全或描述模糊文档格式不规范技术支持很不及时 并且可以明显察觉到为了兼容QQ和微信 xff0c 应用宝的接入规范有诸多不合理的地方 来来回回折腾了一周 xff0c
  • 用Word2007批量设置图片位置

    转自 xff1a http www ccw com cn college htm2010 20100727 877695 shtml Word2007的 查找和替换 功能并不仅仅可以对文字进行批量的查找替换 xff0c 还有很多神奇的功能

随机推荐

  • java-生产者消费者问题以及解决办法

    文章目录 1 生产者消费者问题概述2 生产者消费者问题的解决办法2 1 解决思路2 2 实现方法2 3 代码实现2 3 1 wait 和nofity 方法2 3 2 await signal 方法2 3 3 BlockingQueue阻塞队
  • 【Remote Development】VSCode 基于 SSH 进行远程开发

    系统需求 我们在 VSCode 下载由微软官方推出的 Remote SSH 插件 查看一下里面的描述 xff0c 对于远程机器的要求如下 xff1a Local A supported OpenSSH compatible SSH clie
  • git idea创建新分支,获取/合并主支代码的2个方法

    其他sql格式也在更新中 xff0c 可直接查看这个系列 xff0c 要是没有你需要的格式 xff0c 可在评论或私信我 个人目录 获取主支代码的2个方法 1 xff0c 创建一个分支 xff0c 获取主支的所有代码 xff08 场景 xf
  • spring手把手超详细讲解(基本配置,基于xml)

    spring教程 1 1 容器概述1 1 1 配置元数据1 1 2 容器的实例化1 1 3 容器的使用 1 2 bean的概述1 2 1 命名bean1 2 2 实例化Bean 1 3 依赖1 3 1 依赖注入1 3 2 使用 属性1 3
  • 18.5 重载全局new、delete、定位new及重载等

    一 xff1a 重载全局operator new和operator delete操作符 span class token macro property span class token directive hash span span cl
  • java进程占用CPU过高常见的两种情况及分析定位

    java进程爆cpu的快速定位 1 背景 在程序开发的过程中 xff0c 难免遇到进程占用cpu过高 xff08 现网居多 开发环境 xff09 的情况 xff0c 现网出现这种情况就需要及时的能定位到问题 xff0c 快速解决 xff0c
  • 【Android ViewBinding】内存泄露

    场景 在MainActivity中分别加载两个Fragment处理业务 首先触发加载SecondFragment xff1a MainActivity触发 supportFragmentManager commit add R id con
  • Shell小脚本实现一键关机/重启虚拟机

    利用Shell脚本实现一键关机 重启虚拟机 xff0c 解决每次虚拟机关机或重启都需要手动一个个关机或重启的烦恼 xff01 1 脚本一 xff1a shut sh span class token comment bin bash spa
  • LAMP环境搭建

    前言 一 在虚拟机上安装Linux系统 二 安装Apache 1 下载好后 xff0c 看了看版本 xff0c 不是太老 xff0c 就没有继续安装 2 开启Apache服务 3 设置Apache开机启动服务 4 尝试一下是否启动了服务 x
  • 小程序跳坑之安卓真机不能访问服务器的问题

    因为一项目 xff0c 有几个页面都需要访问服务器 xff0c 从服务器上下载数据 xff0c 在苹果和开发者工具上都运行完美 xff0c 唯独一款安卓手机 xff0c 访问不了 xff0c 经测试 xff0c 发现是汉字编码问题 xff0
  • python Tkinter 界面button调用多进程函数,弹出多个相同界面

    这是我的界面button command的函数start simulate 这是我的多进程函数 xff1a 点击之后 xff0c 弹出多个相同界面 把调用多进程的函数在 if name 61 61 39 main 39 这里调用就不会出现多
  • python入门之if-else语句

    文章目录 一 if语句二 elif语句三 if嵌套语句四 else语句1五 else语句2六 if else语句举例1七 if else语句举例2 一 if语句 span class token keyword if span False
  • Ubuntu 16.04 远程桌面

    1 安装xrdp sudo apt get install xrdp 2 安装vnc4server 我这里是安装xrdp的时候自动安装的 我看网上很多说是需要单独安装的 3 安装xfce4 sudo apt get install xubu
  • GitLab端口冲突 解决办法

    访问gitlab xff0c 出现 xff1a 502 GitLab在使用的过程中 xff0c 会开启80端口 xff0c 如果80端口被其他的应用程序占用 xff0c 则GitLab的该项服务不能使用 xff0c 所以访问GitLab会失
  • Android开发 之 确认凭证

    确认凭证 主要目的 xff1a 设置不用验证时间 设置为30秒 xff0c 当超过30秒后则需要重新验证身份才能操作 您的应用可以根据用户在多久之前最后一次解锁设备来验证其身份 此功能让用户不必费心记忆应用特定密码 xff0c 您也无需实现
  • inner join、outer join、right join、left join 之间的区别

    inner join outer join right join left join 之间的区别 一 sql的left join right join inner join之间的区别 left join 左联接 返回包括左表中的所有记录和右
  • "大泥球"仍然是最常见的软件设计

    大泥球 xff0c 是指杂乱无章 错综复杂 邋遢不堪 随意拼贴的大堆代码 这些年来 xff0c 为了对付这个泥球 xff0c 我们看到了多种指导方法 xff0c 比如SOLID GRASP 和KISS xff0c 与其他诸多年代久远的 提倡
  • 3322.org带来的麻烦

    大概是3322 org被短时间攻破 xff0c 下载他的动态域名客户端的时候下到一个病毒Trojandropper js adagent gd xff0c 把江民关了 xff0c 并且再也开不开 系统还原不行 xff0c 安全模式也进不去
  • Qt学习笔记:多线程的使用

    文章目录 前言1 何时使用线程2 QThread类实现多线程2 1 多线程的实现方法2 2 线程休眠2 3 正确结束线程 3 线程同步3 1 互斥量3 2 信号量3 3 条件变量 4 线程池参考资料 前言 程序中调用耗时的操作 xff08
  • 上位机开发笔记:环形缓冲区

    文章目录 前言1 环形缓冲区工作机制1 1 实现原理1 2 区分缓冲区满或者空1 总是保持一个存储单元为空2 使用计数数据3 镜像指示位 2 Qt实现环形缓冲区2 1 QByteArray环形缓冲区2 2 QSemaphore实现环形缓冲区