亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang中ringbuffer的實(shí)現(xiàn)與應(yīng)用場景詳解

 更新時(shí)間:2023年06月05日 15:30:28   作者:藍(lán)胖子的編程夢  
ringbuffer因?yàn)樗軓?fù)用緩沖空間,通常用于網(wǎng)絡(luò)通信連接的讀寫,雖然市面上已經(jīng)有了go寫的諸多版本的ringbuffer組件,但還是自己造一個(gè)吧

ringbuffer因?yàn)樗軓?fù)用緩沖空間,通常用于網(wǎng)絡(luò)通信連接的讀寫,雖然市面上已經(jīng)有了go寫的諸多版本的ringbuffer組件,雖然諸多版本,實(shí)現(xiàn)ringbuffer的核心邏輯卻是不變的。但發(fā)現(xiàn)其內(nèi)部提供的方法并不能滿足我當(dāng)下的需求,所以還是自己造一個(gè)吧。

源碼已經(jīng)上傳到github:https://github.com/HobbyBear/ringbuffer

需求分析

我在基于epoll實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)框架時(shí),需要預(yù)先定義好的和客戶端的通信協(xié)議,當(dāng)從連接讀取數(shù)據(jù)時(shí)需要判讀當(dāng)前連接是否擁有完整的協(xié)議(實(shí)際網(wǎng)絡(luò)環(huán)境中可能完整的協(xié)議字節(jié)只到達(dá)了部分),有才會(huì)將數(shù)據(jù)全部讀取出來,然后進(jìn)行處理,否則就等待下次連接可讀時(shí),再判斷連接是否具有完整的協(xié)議。

由于在讀取時(shí)需要先判斷當(dāng)前連接是否有完整協(xié)議,所以讀取時(shí)不能移動(dòng)讀指針的位置,因?yàn)槿f一協(xié)議不完整的話,下次讀取還要從當(dāng)前的讀指針位置開始讀取。

所以對于ringbuffer 組件我會(huì)實(shí)現(xiàn)一個(gè)peek方法

func (r *RingBuffer) Peek(readOffsetBack, n int) ([]byte, error)

peek方法兩個(gè)參數(shù),n代表要讀取的字節(jié)數(shù), readOffsetBack 代表讀取是要在當(dāng)前讀位置偏移的字節(jié)數(shù),因?yàn)樵谠O(shè)計(jì)協(xié)議時(shí),往往協(xié)議不是那么簡單(可能是由多個(gè)固定長度的數(shù)據(jù)構(gòu)成) ,比如下面這樣的協(xié)議格式。

完整的協(xié)議有三段構(gòu)成,每段開頭都會(huì)有一個(gè)4字節(jié)的大小代表每段的長度,在判斷協(xié)議是否完整時(shí),就必須看著3段的數(shù)據(jù)是否都全部到達(dá)。 所以在判斷第二段數(shù)據(jù)是否完整時(shí),會(huì)跳過前面3個(gè)字節(jié)去判斷,此時(shí)readOffsetBack 將會(huì)是3。

此外我還需要一個(gè)通過分割符獲取字節(jié)的方法,因?yàn)橛袝r(shí)候協(xié)議不是固定長度的數(shù)組了,而是通過某個(gè)分割符判斷某段協(xié)議是否結(jié)束,比如換行符。

func (r *RingBuffer) PeekBytes(readOffsetBack int, delim byte) ([]byte, error) 

接著,還需要提供一個(gè)更新讀位置的方法,因?yàn)橐坏┡袛嗍且粋€(gè)完整的協(xié)議后,我會(huì)將協(xié)議數(shù)據(jù)全部讀取出來,此時(shí)應(yīng)該要更新讀指針的位置,以便下次讀取新的請求。

func (r *RingBuffer) AddReadPosition(n int) 

n 便是代表需要將讀指針往后偏移的n個(gè)字節(jié)。

ringbuffer 原理解析

接著,我們再來看看實(shí)際上ringbuffer的實(shí)現(xiàn)原理是什么。

首先來看下一個(gè)ringbuffer應(yīng)該有的屬性

type RingBuffer struct {  
   buf             []byte  
   reader          io.Reader  
   r               int // 標(biāo)記下次讀取開始的位置  
   unReadSize      int // 緩沖區(qū)中未讀數(shù)據(jù)大小  
}

buf 用作連接讀取的緩沖區(qū),reader 代表了原鏈接,r代表讀取ringbuffer時(shí)應(yīng)該從字節(jié)數(shù)組的哪個(gè)位置開始讀取,unReadSize 代表緩沖區(qū)當(dāng)中還有多少數(shù)據(jù)沒有讀取,因?yàn)槟憧赡芤淮涡詮膔eader里讀取了很多數(shù)據(jù)到buf里,但是上層應(yīng)用只取buf里的部分?jǐn)?shù)據(jù),剩余的未讀數(shù)據(jù)就留在了buf里,等待下次被應(yīng)用層繼續(xù)讀取。

我們用一個(gè)5字節(jié)的字節(jié)數(shù)組當(dāng)做緩沖區(qū), 首先從ringbuffer讀取數(shù)據(jù)時(shí),由于ringbuffer內(nèi)部沒有數(shù)據(jù),所以需要從連接中讀取數(shù)據(jù)然后寫到ringbuffer里。

如下圖所示:

假設(shè)ringBuffer規(guī)定每次向原網(wǎng)絡(luò)連接讀取時(shí) 按4字節(jié)讀取到緩沖區(qū)中(實(shí)際情況為了減少系統(tǒng)調(diào)用開銷,這個(gè)值會(huì)更多,盡可能會(huì)一次性讀取更多數(shù)據(jù)到緩沖區(qū)) write pos 指向的位置則代表從reader讀取的數(shù)據(jù)應(yīng)該從哪個(gè)位置開始寫入到buf字節(jié)數(shù)組里。

writePos = (r + unReadSize) % len(buf)

接著,上層應(yīng)用只讀取了3個(gè)字節(jié),緩沖區(qū)中的讀指針r和未讀空間就會(huì)變成下面這樣

如果此時(shí)上層應(yīng)用還想再讀取3個(gè)字節(jié),那么ringbuffer就必須再向reader讀取字節(jié)填充到緩沖區(qū)上,我們假設(shè)這次向reader索取3個(gè)字節(jié)。緩沖區(qū)的空間就會(huì)變成下面這樣

此時(shí)已經(jīng)復(fù)用了首次向reader讀取數(shù)據(jù)時(shí)占據(jù)的緩沖空間了。

當(dāng)填充上字節(jié)后,應(yīng)用層繼續(xù)讀取3個(gè)字節(jié),那么ringBuffer會(huì)變成這樣

讀指針又指向了數(shù)組的開頭了,可以得出讀指針的計(jì)算公式

r = (r + n)% len(buf)

ringBuffer 代碼解析

有了前面的演示后,再來看代碼就比較容易了。用peek 方法舉例進(jìn)行分析,

func (r *RingBuffer) Peek(readOffsetBack, n int) ([]byte, error) { 
   // 由于目前實(shí)現(xiàn)的ringBuffer還不具備自動(dòng)擴(kuò)容,所以不支持讀取的字節(jié)數(shù)大于緩沖區(qū)的長度
   if n > len(r.buf) {  
      return nil, fmt.Errorf("the unReadSize is over range the buffer len")  
   }  
peek:  
   if n <= r.UnReadSize()-readOffsetBack {  
      // 說明緩沖區(qū)中的未讀字節(jié)數(shù)有足夠長的n個(gè)字節(jié),從buf緩沖區(qū)直接讀取
      readPos := (r.r + readOffsetBack) % len(r.buf)  
      return r.dataByPos(readPos, (r.r+readOffsetBack+n-1)%len(r.buf)), nil  
   }  
   // 說明緩沖區(qū)中未讀字節(jié)數(shù)不夠n個(gè)字節(jié)那么長,還需要從reader里讀取數(shù)據(jù)到緩沖區(qū)中
   err := r.fill()  
   if err != nil {  
      return nil, err  
   }  
   goto peek  
}

peek方法的大致邏輯是首先判斷要讀取的n個(gè)字節(jié)能不能從緩沖區(qū)buf里直接讀取,如果能則直接返回,如果不能,則需要從reader里繼續(xù)讀取數(shù)據(jù),直到buf緩沖區(qū)數(shù)據(jù)夠n個(gè)字節(jié)那么長。

dataByPos 方法是根據(jù)傳入的元素位置,從buf中讀取在這個(gè)位置區(qū)間內(nèi)的數(shù)據(jù)。

// dataByPos 返回索引值在start和end之間的數(shù)據(jù),閉區(qū)間  
func (r *RingBuffer) dataByPos(start int, end int) []byte {  
   // 因?yàn)榄h(huán)形緩沖區(qū)原因,所以末位置索引值有可能小于開始位置索引  
   if end < start {  
      return append(r.buf[start:], r.buf[:end+1]...)  
   }  
   return r.buf[start : end+1]  
}

fill() 方法則是從reader中讀取數(shù)據(jù)到buf里。

fill 情況分析

reader填充新數(shù)據(jù)到buf后,未讀空間未跨越buf末尾

當(dāng)從reader讀取完數(shù)據(jù)后,如果 end := r.r + r.unReadSize + readBytes end指向了未讀空間的末尾,如果沒有超過buf的長度,那么將數(shù)據(jù)復(fù)制到buf里的邏輯很簡單,直接在當(dāng)前write pos的位置追加讀取到的字節(jié)就行。

// 此時(shí)writePos 沒有超過 len(buf)
writePos = (r + unReadSize)

未讀 空間 本來就 已經(jīng)從頭覆蓋

當(dāng)未讀空間本來就重新覆蓋了buf頭部,和上面類似,這種情況也是直接在write pos 位置追加數(shù)據(jù)即可。

未讀空間未跨越buf末尾,當(dāng)從reader追加數(shù)據(jù)到buf后發(fā)現(xiàn)需要覆蓋buf頭部

這種情況需要將讀取的數(shù)據(jù)一部分覆蓋到buf的末尾

 writePos := (r.r + r.unReadSize) % len(r.buf)  
 n := copy(r.buf[writePos:], buf[:readBytes])  

一部分覆蓋到buf的頭部

end := r.r + r.unReadSize + readBytes  
copy(r.buf[:end%len(r.buf)], buf[len(r.buf)-writePos:])  

現(xiàn)在再來看fill的源碼就比較容易理解了。

func (r *RingBuffer) fill() error {  
   if r.unReadSize == len(r.buf) {  
      // 當(dāng)未讀數(shù)據(jù)填滿buf后 ,就應(yīng)該等待上層應(yīng)用把未讀數(shù)據(jù)讀取一部分再來填充緩沖區(qū)
      return fmt.Errorf("the unReadSize is over range the buffer len")  
   }  
   // batchFetchBytes 為每次向reader里讀取多少個(gè)字節(jié),如果此時(shí)buf的剩余空間比batchFetchBytes小,則應(yīng)該只向reader讀取剩余空間的字節(jié)數(shù)
   readLen := int(math.Min(float64(r.batchFetchBytes), float64(len(r.buf)-r.unReadSize)))  
   buf := make([]byte, readLen)  
   readBytes, err := r.reader.Read(buf)  
   if readBytes > 0 {  
     // 查看讀取readBytes個(gè)字節(jié)后,未讀空間有沒有超過buf末尾指針,如果超過了,在復(fù)制數(shù)據(jù)時(shí)需要特殊處理
      end := r.r + r.unReadSize + readBytes  
      if end < len(r.buf) {
        // 沒有超過末尾指針,直接將數(shù)據(jù)copy到writePos后面  
         copy(r.buf[r.r+r.unReadSize:], buf[:readBytes])  
      } else {  
        // 超過了末尾指針,有兩種情況,看下圖分析
         writePos := (r.r + r.unReadSize) % len(r.buf)  
         n := copy(r.buf[writePos:], buf[:readBytes])  
         if n < readBytes {  
            copy(r.buf[:end%len(r.buf)], buf[len(r.buf)-writePos:])  
         }  
      }  
      r.unReadSize += readBytes  
      return nil  
   }  
   if err != nil {  
      return err  
   }  
   return nil  
}

以上就是Golang中ringbuffer的實(shí)現(xiàn)與應(yīng)用場景詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang ringbuffer的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go語言channel實(shí)現(xiàn)多核并行化運(yùn)行的方法

    go語言channel實(shí)現(xiàn)多核并行化運(yùn)行的方法

    這篇文章主要介紹了go語言channel實(shí)現(xiàn)多核并行化運(yùn)行的方法,實(shí)例分析了channel實(shí)現(xiàn)多核并行化運(yùn)行的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • GO語言原生實(shí)現(xiàn)文件上傳功能

    GO語言原生實(shí)現(xiàn)文件上傳功能

    這篇文章主要為大家詳細(xì)介紹了GO語言原生實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • Go語言接口定義與用法示例

    Go語言接口定義與用法示例

    這篇文章主要介紹了Go語言接口定義與用法,較為詳細(xì)的分析了Go語言中接口的概念、定義、用法,需要的朋友可以參考下
    2016-07-07
  • GoFrame框架Scan類型轉(zhuǎn)換實(shí)例

    GoFrame框架Scan類型轉(zhuǎn)換實(shí)例

    這篇文章主要為大家介紹了GoFrame框架Scan類型轉(zhuǎn)換的實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • golang實(shí)現(xiàn)協(xié)程池的方法示例

    golang實(shí)現(xiàn)協(xié)程池的方法示例

    本文主要介紹了golang實(shí)現(xiàn)協(xié)程池的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-02-02
  • Golang并發(fā)編程之調(diào)度器初始化詳解

    Golang并發(fā)編程之調(diào)度器初始化詳解

    這篇文章主要為大家詳細(xì)介紹了Golang并發(fā)編程中關(guān)于調(diào)度器初始化的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-03-03
  • Golang實(shí)現(xiàn)JWT身份驗(yàn)證的示例詳解

    Golang實(shí)現(xiàn)JWT身份驗(yàn)證的示例詳解

    JWT(JSON Web Token)是一種開放標(biāo)準(zhǔn)(RFC 7519),用于在網(wǎng)絡(luò)應(yīng)用間安全地傳輸聲明,本文主要為大家詳細(xì)介紹了Golang實(shí)現(xiàn)JWT身份驗(yàn)證的相關(guān)方法,希望對大家有所幫助
    2024-03-03
  • 一文詳解go閉包(Closure)使用教程

    一文詳解go閉包(Closure)使用教程

    在Go語言中,閉包(Closure)是一種特殊的函數(shù),它可以捕獲其創(chuàng)建時(shí)所在作用域中的變量,本文給大家詳細(xì)介紹了go閉包(Closure)使用教程,感興趣的朋友可以參考下
    2024-01-01
  • Go FX框架用法小結(jié)

    Go FX框架用法小結(jié)

    Go FX是一個(gè)功能強(qiáng)大的框架,通過依賴注入和生命周期管理來簡化應(yīng)用的構(gòu)建與維護(hù),本文就來介紹一下Go FX框架用法小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • 淺析Golang中float64的精度問題

    淺析Golang中float64的精度問題

    這篇文章主要來和大家一起探討一下Golang中關(guān)于float64的精度問題,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下
    2023-08-08

最新評論