環形資料緩衝的實現

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.