关键在于写指针不能追上读指针,让读指针和写指针保留一个字节的距离来区分2个指针重叠的情况。
如何保证线程安全的?
其实就是某个线程在读取或者写入的时候取的某个时间点的指针来判断时候符合条件,条件只能是越来越好,而不可能是越来越差。比如判断能够写入的时候,空间肯定是越来越多的
//实际的缓冲区大小要多一个字节
struct RingBuffer
{
//缓冲区头指针
char* m_pBuffer;
//缓冲区大小
int m_iBufferSize;
//读指针
char* m_pRead;
//写指针
char* m_pWrite;
//缓冲区指针头
char* m_pHead;
//缓冲区指针尾
char* m_pTail;
};
//实际的缓冲区大小要多一个字节
void ip_reset_ringbuffer(RingBuffer* pRingBuffer)
{
assert(NULL != pRingBuffer);
pRingBuffer ->m_pWrite = pRingBuffer ->m_pRead;
}
RingBuffer* ip_create_ringbuffer(int bufferSize)
{
RingBuffer* pRingBuffer = (RingBuffer*)malloc(sizeof(RingBuffer));
if (NULL == pRingBuffer)
{
#ifdef _LOG
printf("%s : %d get memory is NULL", __FILE__, __LINE__);
#else
WRITELOG(ErrLog, "%s : %d get memory is NULL", __FILE__, __LINE__);
#endif
return NULL;
}
else
{
pRingBuffer ->m_iBufferSize = bufferSize;
pRingBuffer ->m_pBuffer = (char*)malloc(sizeof(char) * bufferSize);
if (NULL == pRingBuffer ->m_pBuffer)
{
#ifdef _LOG
printf("%s : %d get memory is NULL", __FILE__, __LINE__);
#else
WRITELOG(ErrLog, "%s : %d get memory is NULL", __FILE__, __LINE__);
#endif
free(pRingBuffer);
return NULL;
}
memset( pRingBuffer ->m_pBuffer, 0, bufferSize);
pRingBuffer ->m_pHead = pRingBuffer ->m_pBuffer;
pRingBuffer ->m_pTail = pRingBuffer ->m_pBuffer + bufferSize;
pRingBuffer ->m_pRead = pRingBuffer ->m_pWrite = pRingBuffer ->m_pHead;
return pRingBuffer;
}
}
//在某个时间点由写线程先判断缓冲区是否有足够的空间进行写操作
//写指针在追赶读指针的时候, 必须和读指针保持1个字节的间距,
//不能赶上读指针
bool ip_checkcanwrite_ringbuffer(RingBuffer* pRingBuffer, int checkSize)
{
assert(NULL != pRingBuffer);
//保存读指针,有可能读线程正在读取数据
char* pSaveRead = pRingBuffer ->m_pRead;
//剩余空间大小
int left = 0;
//写指针在读指针前面, 或者读指针赶上写指针
if (pRingBuffer ->m_pWrite >= pSaveRead)
{
//判断剩余部分是否够1500字节
left = pRingBuffer ->m_pTail - pRingBuffer ->m_pWrite;
if (left >= checkSize)
{
return true;
}
//需要再加上从缓冲头部到读指针的距离, 这个时候要算加起来的长度
//且需要多算1个字节, 防止写指针在追赶读指针的时候重叠
//由于读指针在写指针后面,必然不会操作写指针, 这里可以实时取读指针
//这个时候可能读线程又读了数据
else
{
left = left + pRingBuffer ->m_pRead - pRingBuffer ->m_pHead;
if (left >= checkSize + 1)
{
return true;
}
else
{
return false;
}
}
}
//写指针跑的较快
//这里不能取实时的读指针, 因为可能读线程导致读指针又跑到了写指针的后面
else
{
left = pSaveRead - pRingBuffer ->m_pWrite;
if (left >= checkSize + 1)
{
return true;
}
else
{
return false;
}
}
}
//该函数必须先进行剩余空间检查后才能进行写操作
//check函数已经保证了必然有足够的1500字节
void ip_pushdata_ringbuffer(RingBuffer* pRingBuffer, char* pData, int length, int maxfree)
{
assert(NULL != pRingBuffer);
assert(NULL != pData);
assert(length <= maxfree);
//保存读指针,有可能读线程正在读取数据
char* pSaveRead = pRingBuffer ->m_pRead;
//写指针大于读指针
if (pRingBuffer ->m_pWrite >= pSaveRead)
{
int part = pRingBuffer ->m_pTail - pRingBuffer ->m_pWrite;
//数组末尾的空间就足够了
if (part >= length)
{
//拷贝数据到末尾
memcpy(pRingBuffer ->m_pWrite, pData, length);
if (part > length)
{
//修改写指针指向
pRingBuffer ->m_pWrite = pRingBuffer ->m_pWrite + length;
}
else
{
//写指针指向数组头
pRingBuffer ->m_pWrite = pRingBuffer ->m_pHead;
}
}
//末尾的空间不够,还要拷贝到数组头
else
{
//先拷贝到数组尾
memcpy(pRingBuffer ->m_pWrite, pData, part);
//移动源数据指针
pData = pData + part;
length = length - part;
//然后拷贝到数组头
memcpy(pRingBuffer ->m_pHead, pData, length);
//修改写指针指向
pRingBuffer ->m_pWrite = pRingBuffer ->m_pHead + length;
}
}
//写指针小于读指针
else
{
int left = pSaveRead - pRingBuffer ->m_pWrite;
assert(left >= maxfree + 1);
memcpy(pRingBuffer ->m_pWrite, pData, length);
pRingBuffer ->m_pWrite = pRingBuffer ->m_pWrite + length;
}
}
//在缓冲区上试图找到一个完整的IP包然后发送
static void sendippacket(TunWriter* pTunWriter, RingBuffer* pRingBuffer, int tunDeviceFd)
{
assert(NULL != pTunWriter);
assert(NULL != pRingBuffer);
again:
char pSendBuffer[SEND_BUFFER];
char *pBegin = pSendBuffer;
memset(pSendBuffer, 0, SEND_BUFFER);
int iPacketLength;
int iTotalLength;
//先保存写指针
char* pWriteSave = pRingBuffer ->m_pWrite;
//写指针在读指针前面
if (pWriteSave >= pRingBuffer ->m_pRead)
{
//从当前读指针判断该IP包的长度
int left = pWriteSave -pRingBuffer ->m_pRead;
//不足一个IP包的首部
if (left < 20)
{
return;
}
//够一个IP包的首部
else if (left >= 20)
{
//取该IP包的长度
iPacketLength = ntohs(*((unsigned short *)(pRingBuffer ->m_pRead + 2)));
//足够一个完整的IP包
if (left >= iPacketLength)
{
//将完整的IP包先拷贝出来
memcpy(pBegin, pRingBuffer ->m_pRead, iPacketLength);
//读指针++
pRingBuffer ->m_pRead = pRingBuffer ->m_pRead + iPacketLength;
//进入发送环节
goto Send;
}
//不足一个完整的IP包
else
{
return;
}
}
}
//读指针在写指针的前面, 写指针肯定不会赶上读指针,这里可以取实时的写指针
else
{
//判断整个剩余长度
int left = pRingBuffer ->m_pTail - pRingBuffer ->m_pRead + pRingBuffer ->m_pWrite - pRingBuffer ->m_pHead;
//不足一个IP包的首部
if (left < 20)
{
return;
}
//够一个IP包的首部
else if (left >= 20)
{
//先将首部的数据拷出来
//全部在缓冲末尾
int part = pRingBuffer ->m_pTail - pRingBuffer ->m_pRead;
if (part >= 20)
{
memcpy(pBegin, pRingBuffer ->m_pRead, 20);
}
//有一部分在末尾, 一部分在头部
else
{
memcpy(pBegin, pRingBuffer ->m_pRead, part);
pBegin = pBegin + part;
part = 20 -part;
memcpy(pBegin, pRingBuffer ->m_pHead, part);
}
//取该IP包的长度
pBegin = pSendBuffer;
//末尾剩余数据包长度
part = pRingBuffer ->m_pTail - pRingBuffer ->m_pRead;
iPacketLength = ntohs(*((unsigned short *)(pBegin + 2)));
iTotalLength = iPacketLength;
//足够一个完整的IP包
if (left >= iPacketLength)
{
//判断缓冲末尾是否就足够
if (part >= iPacketLength)
{
pBegin = pSendBuffer;
//从头进行拷贝
memcpy(pBegin, pRingBuffer ->m_pRead, iPacketLength);
//修改读指针指向
pRingBuffer ->m_pRead = pRingBuffer ->m_pRead + iPacketLength;
}
//不够,要再从缓冲头部拷贝
else
{
memcpy(pBegin, pRingBuffer ->m_pRead, part);
iTotalLength = iTotalLength -part;
pBegin = pBegin + part;
memcpy(pBegin, pRingBuffer ->m_pHead, iTotalLength);
//修改读指针指向
pRingBuffer ->m_pRead = pRingBuffer ->m_pHead + iTotalLength;
}
//进入发送环节
goto Send;
}
//不足一个完整的IP包
else
{
return;
}
}
}
Send:
int iSendTun = write(tunDeviceFd, pSendBuffer, iPacketLength);
if ((-1 == iSendTun) && (errno == EINTR))
{
goto Send;
}
//出现下面两种情况,表示Tun设备不可写入,就不需要读取操作了
else if ((-1 == iSendTun) && (errno == EWOULDBLOCK))
{
return;
}
else if ((-1 == iSendTun) && (errno != EINTR) && (errno != EWOULDBLOCK))
{
#ifdef _LOG
ip_writeLog_threadlog(pTunWriter ->m_pLog, LOG_EMERG, "sendippacket()", "write tun fail with errno %d", errno);
#else
WRITELOG(ErrLog, "TunWriter:%d write tun fail with errno %d", pTunWriter->m_iThreadID, errno);
#endif
return;
}
//发送成功
else
{
//向Tun设备写入的必然是一个完整的包
assert(iSendTun == iPacketLength);
goto again;
}
}