天天看點

linux網絡程式設計--Circular Buffer(Ring Buffer) 環形緩沖區的設計與實作【轉】

1. 應用場景

      網絡程式設計中有這樣一種場景:需要應用程式代碼一邊從TCP/IP協定棧接收資料(reading data from socket),一邊解析接收的資料。具體場景例如:使用者點選Youtube或優酷網站上的視訊内容,這時使用者PC上的播放軟體就是一邊接收資料一邊對資料進行解碼并播放的。這樣的場景的存在如下限制:

1. 必須邊接收資料,邊對資料進行解析,不能等待到資料全部接收完整後才解析(使用者等待的時間與體驗成反比)。

2. 資料為流式資料(如TCP承載),需對接收到的資料進行定界分析,将資料轉化為可被應用程式解析的結構化資料。

3. 資料的解析需要兼顧性能和記憶體空間的利用效率(如果減少記憶體拷貝,配置設定适當大小的緩存空間)。

linux網絡程式設計--Circular Buffer(Ring Buffer) 環形緩沖區的設計與實作【轉】

     本文将設計一個适合上述場景的環形緩沖元件,提供友善的資料緩存與讀取接口,讓編碼專注于資料解析的邏輯,而不是将過多的精力消耗在緩沖區本身的處理上。本文讨論POSIX的一種優化的環形緩沖實作方式,并提出了進一步優化:

1. 高效的資料寫入與讀取接口,如應用程式可能對某段資料不感興趣,則可将其直接忽略掉。

2. 封裝了常見的整形資料讀取接口,解析程式可以直接讀數1~4位元組的整形資料。

  1. #ifndef _CIRCULAR_BUFFER_H
  2. #define _CIRCULAR_BUFFER_H
  3. typedef struct CircularBuffer {
  4. void *ptr;
  5. /* 必須為整數倍記憶體頁面大小*/
  6. unsigned long count;
  7. unsigned long read_offset;
  8. unsigned long write_offset;
  9. } CircularBuffer;
  10. /* 建立環形緩沖區 */
  11. CircularBuffer *cbCreate(unsigned long order);
  12. /* 銷毀環形緩沖區 */
  13. void cbFree(CircularBuffer *cb);
  14. /* 重置緩沖區,使之可用于新的業務資料緩存 */
  15. void cbClear(CircularBuffer *cb);
  16. int cbIsEmpty(CircularBuffer *cb);
  17. unsigned long cbUsedSpaceSize(CircularBuffer *cb);
  18. unsigned long cbFreeSpaceSize(CircularBuffer *cb);
  19. /* 向環形緩沖寫入len 位元組資料 */
  20. unsigned long cbPushBuffer(CircularBuffer *cb, void *buffer, unsigned long len);
  21. /* 從環形緩沖讀取len位元組存放到buffer中,
  22. buffer可以為NULL,忽略len位元組的資料*/
  23. void *cbReadBuffer(CircularBuffer *cb, void *buffer, unsigned long len);
  24. /* 從環形緩沖區讀取1個位元組 */
  25. unsigned char cbReadUINT8(CircularBuffer *cb);
  26. /* 從環形緩沖區讀取1個短整形數 */
  27. unsigned short cbReadUINT16(CircularBuffer *cb);
  28. short cbReadSINT16(CircularBuffer *cb);
  29. unsigned int cbReadUINT24(CircularBuffer *cb);
  30. int cbReadSINT24(CircularBuffer *cb);
  31. unsigned int cbReadUINT32(CircularBuffer *cb);
  32. int cbReadSINT32(CircularBuffer *cb);
  33. #endif

cbCreate接口建立并初始化一個環形緩沖區,實作如下:

  1. CircularBuffer *cbCreate(unsigned long order)
  2. {
  3. int fd = 0, status = 0;
  4. void *address = NULL;
  5. char path[] = "/dev/shm/circular_buffer_XXXXXX";
  6. CircularBuffer *cb = (CircularBuffer *)malloc(sizeof(CircularBuffer));
  7. if (NULL == cb) {
  8. return NULL;
  9. }
  10. order = (order <= 12 ? 12 : order);
  11. cb->count = 1UL << order;
  12. cb->read_offset = 0;
  13. cb->write_offset = 0;
  14. /* 配置設定2倍指定的緩沖空間 */
  15. cb->ptr = mmap(NULL, cb->count << 1, PROT_NONE, MAP_ANONYMOUS |MAP_PRIVATE, -1, 0);
  16. if (MAP_FAILED == cb->ptr) {
  17. abort(); |
  18. /* 根據path子產品建立一個唯一的臨時檔案 */
  19. fd = mkstemp(path);
  20. if (0 > fd) {
  21. abort();
  22. /* 删除檔案通路的目錄入口,程序仍可使用該檔案 */
  23. status = unlink(path);
  24. if (0 != status) {
  25. /* 将檔案大小精确指定為count位元組 */
  26. status = ftruncate(fd, cb->count);
  27. /* 将[ cb->ptr, cb->ptr + cb->count)位址空間映射到臨時檔案*/
  28. address = mmap(cb->ptr, cb->count, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0);
  29. if (address != cb->ptr) {
  30. /* 将[ cb->ptr + cb->count, cb->ptr + 2 * cb->count)位址空間映射到臨時檔案*/
  31. address = mmap(cb->ptr + cb->count, cb->count, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0);
  32. if (address != cb->ptr + cb->count) {
  33. status = close(fd);
  34. return cb;

該實作采用了一種精妙的處理方式,用2倍的緩存空間簡化資料的讀寫操作。

    第1個mmap采用私有匿名的方式配置設定了一塊為指定緩沖區大小2倍的記憶體空間;第2個mmap将mkstemp建立的臨時檔案映射到[ptr,

ptr + count)位址,第3個mmap将mkstemp建立的臨時檔案映射到[ptr + count, ptr + 2 *

count)位址,這樣對ptr[i]的讀寫操作将等同于對ptr[i + count]的讀寫操作,進而達到簡化了環形緩沖區對于資料回繞的邏輯。

linux網絡程式設計--Circular Buffer(Ring Buffer) 環形緩沖區的設計與實作【轉】

     如下代碼為讀寫環形緩沖區及計算緩沖區已使用空間大小的例程。cbUsedSpaceSize函數可用于cbIsEmpty及cbFreeSpaceSize函數的實作。cbReadBuffer函數則可用于實作cbReadUINT8、cbReadUINT16、cbReadSINT16、cbReadUINT24、cbReadSINT24、cbReadUINT32及cbReadSINT32。cbReadBuffer函數的buffer參數若傳人為空,則忽略len指定長度位元組的資料。

  1. unsigned long cbPushBuffer(CircularBuffer *cb, void *buffer, unsigned long len)
  2. unsigned long write_offset = cb->write_offset;
  3. cb->write_offset += len;
  4. memmove(cb->ptr + write_offset, buffer, len);
  5. return len;
  6. void *cbReadBuffer(CircularBuffer *cb, void *buffer, unsigned long len)
  7. /* 忽略len位元組資料 */
  8. if (NULL != buffer) {
  9. address = memmove(buffer, cb->ptr + cb->read_offset, len);
  10. cb->read_offset += len;
  11. if (cb->read_offset > cb->count) {
  12. cb->read_offset -= cb->count;
  13. cb->write_offset -= cb->count;
  14. return address;
  15. unsigned long cbUsedSpaceSize(CircularBuffer *cb)
  16. return cb->write_offset - cb->read_offset;

3. 分析與讨論

1. 環形緩沖區特别适合于FIFO類型資料的處理,利用它可以不拷貝記憶體完成緩沖上資料的解析,提高資料解析效率。

2. 若資料讀取函數采用單位元組讀、取模數計算偏移的方式,則可能帶來性能上的損耗,該問題可以通過增加判斷或以做位運算等機制來解決,但同時也增加了實作邏輯的複雜度。

3. 其不足之處在于需要預先估計資料緩沖的大小,并配置設定比預估大小大一個數量級的緩存空間。一種可能的解決辦法是增加檢測機制,若發現緩沖太小,則動态調大緩沖的大小,但這同時又可能導緻頻繁的調整記憶體大小,帶來性能的下降。

(總結:根絕這樣寫的ringbuf 确實有點麻煩,暫時還沒有體會其中的要義,自己也嘗試着寫一個簡單的ringbuf,就是往這裡放東西,然後取定長資料)

【作者】張昺華

歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀