關鍵在於寫指標不能追上讀指標,讓讀指標和寫指標保留一個位元組的距離來區分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;
}
}