1.什么是环形缓冲区?
环形缓冲区一般是一段连续的buffer空间,空间可以是new或者使用vector<char>分配,这些空间对计算机来说是线性的,但我们在读取或者写入的时候需要%来计算读写的位置,
这样就抽象成环了。
2.什么时候用环形缓冲区
game的后台通常是几个程序来组成一个集群。这样几个后台进程必然需要通信。
比如下面一个简单的结构
client--------- 接入层svr------logicsvr---------dbsvr
这样接入层在收到客户端数据包后需要把数据在发送给logicsvr,这样2个进程就需要通信 。进程间通信有多种方式,比如共享内存,socket方式等。
假如使用共享内存,那么2个进程如果读写而且还不用加锁(这里的读写是 一个只会去读数据,另一个只回去写数据)这个时候使用环形缓冲区就比较适合
下面是一个简单的实现
#pragma once
#include <vector>
#define QUEUERESERVELENGTH 8 //保留8个字节不用
#define MAX_BUF_LEN 0x7FF0
class CCricleBuffer
{
public:
CCricleBuffer(unsigned int MaxLen);
~CCricleBuffer(void);
//缓存区是否已满
bool IsFull();
//写入数据
bool AppendBuffer(char* pBuffer, short len);
//获得一个完整的packet
bool GetPacket(char* pBuffer, short& TranLen, short MaxLen);
private:
//获得缓存区剩余大小
unsigned int GetRemain();
private:
std::vector<char> m_CricleBuf;
unsigned int m_Len;
unsigned int m_ActiveLen;
unsigned int m_ReadPos;
unsigned int m_WritePos;
};
#include "StdAfx.h"
#include "CricleBuffer.h"
CCricleBuffer::CCricleBuffer(unsigned int MaxLen)
{
m_Len = MaxLen;
m_ActiveLen = MaxLen - QUEUERESERVELENGTH;
m_ReadPos = 0;
m_WritePos = 0;
m_CricleBuf.resize(MaxLen);
}
CCricleBuffer::~CCricleBuffer(void)
{
}
unsigned int CCricleBuffer::GetRemain()
{
unsigned int RemainLen = 0;
if (m_ReadPos == m_WritePos)
{
RemainLen = m_Len;
}
else if (m_WritePos < m_ReadPos)
{
RemainLen = m_ReadPos - m_WritePos;
}
else
{
RemainLen = (m_Len - m_WritePos) + m_ReadPos;
}
return RemainLen;
}
bool CCricleBuffer::IsFull()
{
unsigned int RemainLen = GetRemain();
if (RemainLen - QUEUERESERVELENGTH > 0)
{
return false;
}
return true;
}
bool CCricleBuffer::AppendBuffer(char* pBuffer, short len)
{
if (pBuffer == NULL || len == 0)
{
//print log
return false;
}
if (m_ReadPos < 0 || m_ReadPos > m_Len ||
m_WritePos <0 || m_WritePos > m_Len)
{
//print log
return false;
}
if (IsFull())
{
//print log
return false;
}
unsigned int RemainLen = GetRemain() - QUEUERESERVELENGTH;
if (len + sizeof(len) > RemainLen)
{
//print log
return false;
}
char* pDest = (char*)&len;
int shortlen = sizeof(len);
for (int i = 0; i < shortlen; ++i)
{
m_CricleBuf[m_WritePos] = pDest[i];
m_WritePos = (m_WritePos + 1) % m_Len;
}
if (m_WritePos < m_ReadPos)
{
memcpy((void*)&m_CricleBuf[m_WritePos],pBuffer,len);
}
else
{
if (m_Len - m_WritePos > len)
{
memcpy((void*)&m_CricleBuf[m_WritePos],(const void*)pBuffer,len);
}
else
{
//需要做2次copy
unsigned int half = m_Len - m_WritePos;
memcpy((void*)&m_CricleBuf[m_WritePos],(const void*)pBuffer,half);
memcpy((void*)&m_CricleBuf[0],(const void*)pBuffer[half], len - half);
}
}
m_WritePos = (m_WritePos + m_Len) % m_Len;
return true;
}
//pBuffer 输出缓存区瘦地址
//TranLen 实际的读取buffer长度
bool CCricleBuffer::GetPacket(char* pBuffer, short& TranLen, short MaxLen)
{
if (NULL== pBuffer || MaxLen > MAX_BUF_LEN)
{
//print log
return false;
}
if (m_ReadPos < 0 || m_ReadPos > m_Len ||
m_WritePos <0 || m_WritePos > m_Len)
{
//print log
return false;
}
if (IsFull())
{
//print log
return false;
}
unsigned int RemainLen = GetRemain() - QUEUERESERVELENGTH;
if (RemainLen <= sizeof(short))
{
//print log
return false;
}
//读short长度,为后面一个完整的packet的长度
unsigned int shortlen = sizeof(short);
char* buff = (char*)&TranLen;
for (int i = 0;i < shortlen; ++i)
{
buff[i] = m_CricleBuf[m_ReadPos];
m_ReadPos = (m_ReadPos + 1) % m_Len;
}
//判断后面的长度是否有一个完整的packet
if (RemainLen - sizeof(short) < TranLen || TranLen <=0)
{
//print log
return false;
}
if (m_ReadPos < m_WritePos)
{
memcpy((void*)pBuffer,(const void*)&m_CricleBuf[m_ReadPos],TranLen);
}
else
{
unsigned int half = m_Len - m_ReadPos;
if (half > TranLen)
{
memcpy((void*)pBuffer,(const void*)&m_CricleBuf[m_ReadPos],TranLen);
}
else
{
memcpy((void*)pBuffer,(const void*)&m_CricleBuf[m_ReadPos],half);
memcpy((void*)pBuffer[half],(const void*)&m_CricleBuf[0],TranLen-half);
}
}
m_ReadPos = (m_ReadPos + TranLen) % m_Len;
return true;
}