C++环形缓冲区的简单实现

分享一个在工作中用到的简单的环形缓冲区。构造简单,可以改造到Qt中使用,也可以直接在VS下使用。

所谓环形缓冲区,其实就是带有标识缓冲区中数据头、尾位置的缓冲区,这个缓冲区根据业务的不同,要设置的稍微大一点,不能一有数据过来就填满了,这样就失去使用的意义了。

在实际工作中,我们使用TCP和设备进行通信,如果数据流量过大,可以先把数据接收到数据缓冲区中,处理之后再取出。我们定义的包协议可以采用定长包,可以采用不定长度的包,环形缓冲区都能处理。

//头文件定义
class CircleBuffer
{
public:
    CircleBuffer();
    ~CircleBuffer();

private:
    char *buf;                 //指向环形缓冲区的指针
    int head;                  //缓冲区头部索引
    int rear;                  //缓冲区尾部索引
    int maxBuf;                //缓冲区的最大值(根据业务不同,设置合适的值)
    int packHeadLength;        //数据包头长度(如果是定长包,那么这里可以是整个数据包。如果是不
                               //定长的,那么这里就是包头的长度)
    char processBuf[1024];     //缓冲区中的数据就放在这里进行处理,然后通过回调或者信号槽的方 
                               //式传递出去
protected:
    inline int  getDataLength();               //缓冲区中数据的长度
    inline int  getFreeBufferLength();         //获取缓冲区中剩余的长度   
    inline bool Empty();                       //缓冲区是否为空
    inline void copyData(char *data,int len);  //把接收到的数据拷贝到缓冲区中
    inline void getData(int len);              //把缓冲区中的数据拷贝到processBuf中
    inline void moveHead(int len);             //处理完成后要移动头部索引的位置
    void processData();                        //实际处理数据的过程

//外部调用接口
public:
     void pushData(char *data,int len);        //从TCP收到的数据放入环形缓冲区中   
     void resetBuffer();                       //重置缓冲区      
};
//源文件实现
CircleBuffer::CircleBuffer()
{
    rear           = 0;
    head           = 0;
    maxBuf         = 1024 * 5;
    buf            = new char[maxBuf];
    packHeadLength = sizeof("你自己定义的数据包头");
}

CircleBuffer::~CircleBuffer()
{
    if(buf)
    {
        delete[] buf;
        buf = NULL;
    }
}

int CircleBuffer::getDataLength()
{
    if(rear >= head)
    {
        return rear - head;
    }
    else //多次存取之后尾部索引有可能比头部索引要小
    {
        return rear-head + maxBuf;
    }
}

qint16 CircleBuffer::getFreeBufferLength()
{
    return maxBuf-getDataLength();
}

bool CircleBuffer::Empty()
{
    return head==rear;
}

void CircleBuffer::copyData(char *data, int len)
{
    //按字节拷贝
    for(int i=0;i<len;i++)
    {
        buf[rear] = data[i];
        //注意这里不能用rear++,既然是环形缓冲区,如果填充到底部之后,底部的索引要回到头部
        rear = (rear + 1)% maxBuf;
    }
}

void CircleBuffer::getData(int len)
{
    int temp = head;//获取数据的时候不能改变头部索引的值,全部处理完成之后才改变头部索引的位置
    for(int i=0;i<len ;i++)
    {
        processBuf[i] = buf[temp];
        //这里和上面一样,头部的索引也有可能跑到底部,得让它能跑回来
        temp = (temp + 1)% maxBuf;
    }
}

void CircleBuffer::moveHead(int len)
{
    //看到了吧,这里用到的最多的就是这个%(取余符号了),这样就可以使头尾两个索引像一个环形一样
    head = (head + len)% maxBuf;
}

//实际处理数据的过程,这里可以根据需要进行修改
//这里的packetHeader对应的为你的包头类
void CircleBuffer::processData()
{
    while(getDataLength() >= packHeadLength)                     //当数据不少于一个包头时
    {
      getData(packHeadLengh);                                    //获得包头数据
      packetHeader *phead=(packetHeader *)processBuf;            //解出包头
      //数据是否合法(包头类提供一个检查数据是否合法的函数)
      if(phead->isVaild())                            
      {
          //这里可以获得整个数据包的长度
          int data_len = phead->getLength();
          if(data_len == 0)                                      //只有包头,没有数据
          {
              //1,通过回调或者信号槽通知调用者来处理
              //......
              //2, 移动头部索引位置
              moveHead(packHeadLength);
          }
          else
          {
              if(getDataLength() >= (data_len+packHeadLength))    //有包头,有数据
              {
                  getData(data_len+packHeadLength);
                  //1,通过回调或者信号槽通知调用者来处理
                  //......
                  //2, 移动头部索引位置
                  moveHead(data_len+packHeadLength);
              }
              else
                  break;                                          //不处理
          }
      }
      else
      {
          rear = 0;
          head = 0;
          //出错了,通过回调或者信号槽通知调用者来处理
          //..............
      }

    }
}


//提供给使用者的接口
//重置缓冲区
void CircleBuffer::resetBuffer()
{
    rear   = 0;
    head   = 0;
}

//处理数据
void CircleBuffer::pushData(char *data, int len)
{
    //1,拷贝数据到处理缓存,要考虑缓冲区剩余空间不够的情况
    if(getFreeBuflength() > len)
    {
        copyData(data,len);
    }
    else
    {
        //开辟更大空间,将原有数据复制到新的缓存
        char *p = new char[maxBuf + len];
        int  data_len = getDataLength();
        for(int i=0 ;i<data_len ;i++)
        {
            p[i] = buf[head];
            head = (head + 1)% maxBuf;
        }

        //更新各种数据
        head = 0;
        rear = data_len;
        maxBuf = maxBuf +len;
        delete[] buf;
        buf = p;
        //拷贝数据
        copyData(data,len);
    }

    //2, 最后再处理数据
    processData();
}

 


版权声明:本文为zouyang85457013原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。