分享一个在工作中用到的简单的环形缓冲区。构造简单,可以改造到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版权协议,转载请附上原文出处链接和本声明。