環形緩衝區應用實例

一、環形緩衝區的介紹

如果您對CPU緩衝區或者對環形緩衝區有一定了解,您可以直接跳到第二章閱讀使用場景

來自維基百科
(圖來自:來自維基百科)

概念(來自維基百科):圓形緩衝區(circular buffer),也稱作圓形隊列(circular queue),循環緩衝區(cyclic buffer),環形緩衝區(ring buffer),是一種用於表示一個固定尺寸、頭尾相連的緩衝區的數據結構,適合緩存數據流

用法(來自維基百科):圓形緩衝區的一個有用特性是:當一個數據元素被用掉後,其餘數據元素不需要移動其存儲位置。相反,一個非圓形緩衝區(例如一個普通的隊列)在用掉一個數據元素後,其餘數據元素需要向前搬移。換句話說,圓形緩衝區適合實現先進先出緩衝區,而非圓形緩衝區適合後進先出緩衝區。
圓形緩衝區適合於事先明確了緩衝區的最大容量的情形。擴展一個圓形緩衝區的容量,需要搬移其中的數據。因此一個緩衝區如果需要經常調整其容量,用鏈表實現更爲合適。
寫操作覆蓋圓形緩衝區中未被處理的數據在某些情況下是允許的。特別是在多媒體處理時。例如,音頻的生產者可以覆蓋掉聲卡尚未來得及處理的音頻數據。

工作過程(來自維基百科):圓形緩衝區(circular buffer),也稱作圓形隊列(circular queue),循環緩衝區(cyclic buffer),環形緩衝區(ring buffer),是一種用於表示一個固定尺寸、頭尾相連的緩衝區的數據結構,適合緩存數據流

圓形緩衝區工作機制(來自維基百科):

二、業務場景

1. 系統功能說明

我們的系統需要操作多臺終端並且實時監聽終端的健康狀態

2. 如何實現

我們讓服務端與終端都建立了一個socket通訊,讓服務端和各個終端保持了長連接,這讓我們能實現服務端能夠操作終端的同時能夠接收到終端的健康狀態報文

3.生產過程中的問題

1.終端的健康報文數據過大,socket自動的分包,拆包,服務端常常出現異常,無法及時正確獲取終端的健康狀態

三、解決方案

1. 問題分析

服務端與終端之間其實是一個長時間的數據傳輸,這讓我們想到了流,在我們面臨大文件處理時,我們首先想到的就是緩衝,這和cpu的緩衝層有些相似,cpu的緩衝層會默認一個大小,一邊一直讀取,一邊一直寫入,如果緩衝層到達最大限制,那麼就會進入阻塞。這個思想和我們的業務相似,終端一直髮起,服務端一直接受,換個說法就是,終端一直寫入,服務端一直讀取。

2. 如何實現

我們借鑑了環形緩衝區的思想,使用了一個1024大小的數組,一個線程一直從數組中讀取,另外一個線程一直寫入。從而保證無論socket是否把我們的報文拆分,我們都能確保這個報文是完整的可使用的。

環形緩衝區代碼如下(go語言):


import (
	"errors"
	"io"
	"time"
)

type CircleByteBuffer struct {
	io.Reader
	io.Writer
	io.Closer
	datas []byte

	start   int
	end     int
	size    int
	isClose bool
	isEnd   bool
}

func NewCircleByteBuffer(len int) *CircleByteBuffer {
	var e = new(CircleByteBuffer)
	e.datas = make([]byte, len)
	e.start = 0
	e.end = 0
	e.size = len
	e.isClose = false
	e.isEnd = false
	return e
}

func (e *CircleByteBuffer) GetLen() int {
	if e.start == e.end {
		return 0
	} else if e.start < e.end {
		return e.end - e.start
	} else {
		return e.size - (e.start - e.end)
	}
}
func (e *CircleByteBuffer) GetFree() int {
	return e.size - e.GetLen()
}
func (e *CircleByteBuffer) Clear() {
	e.start = 0
	e.end = 0
}
func (e *CircleByteBuffer) PutByte(b byte) error {
	if e.isClose {
		return io.EOF
	}
	e.datas[e.end] = b
	var pos = e.end + 1
	if pos == e.size {
		pos = 0
	}
	for pos == e.start {
		if e.isClose {
			return io.EOF
		}
		time.Sleep(time.Millisecond)
	}
	e.end = pos
	return nil
}

func (e *CircleByteBuffer) GetByte() (byte, error) {
	if e.isClose {
		return 0, io.EOF
	}
	for e.GetLen() <= 0 {
		if e.isClose || e.isEnd {
			return 0, io.EOF
		}
		time.Sleep(time.Millisecond)
	}
	var ret = e.datas[e.start]
	pos := e.start + 1
	if pos == e.size {
		pos = 0
	}
	e.start = pos
	return ret, nil
}
func (e *CircleByteBuffer) Geti(i int) byte {
	if i >= e.GetLen() {
		panic("out buffer")
	}
	var pos = e.start + i
	if pos >= e.size {
		pos -= e.size
	}
	return e.datas[pos]
}

/*func (e*CircleByteBuffer)puts(bts []byte){
	for i:=0;i<len(bts);i++{
		e.put(bts[i])
	}
}
func (e*CircleByteBuffer)gets(bts []byte)int{
	if bts==nil {return 0}
	var ret=0
	for i:=0;i<len(bts);i++{
		if e.GetLen()<=0{break}
		bts[i]=e.get()
		ret++
	}
	return ret
}*/
func (e *CircleByteBuffer) Close() error {
	e.isClose = true
	return nil
}
func (e *CircleByteBuffer) Read(bts []byte) (int, error) {
	if e.isClose {
		return 0, io.EOF
	}
	if bts == nil {
		return 0, errors.New("bts is nil")
	}
	var ret = 0
	for i := 0; i < len(bts); i++ {
		b, err := e.GetByte()
		if err != nil {
			return ret, err
		}
		bts[i] = b
		ret++
	}
	if e.isClose {
		return ret, io.EOF
	}
	return ret, nil
}
func (e *CircleByteBuffer) Write(bts []byte) (int, error) {
	if e.isClose {
		return 0, io.EOF
	}
	if bts == nil {
		e.isEnd = true
		return 0, io.EOF
	}
	var ret = 0
	for i := 0; i < len(bts); i++ {
		err := e.PutByte(bts[i])
		if err != nil {
			return ret, err
		}
		ret++
	}
	if e.isClose {
		return ret, io.EOF
	}
	return ret, nil
} 

3.總結

我們在解決業務需求時,可能會受限於當時的技術水平或者其他因素,做出的選擇在現在看來並不是最優,但想想看,在這個輪子橫飛的年代,我相信一個會造輪子的人定會比只能使用輪子的人更加優秀,他們敲下的每一行代碼對程序的影響他們都瞭然於心,他們會更加及時的發現問題,更加完善的解決問題。

我們專注於真實案例挖掘並寫下這系列的文章,我希望能讓大家保持思考,保持一顆熱愛編碼的心,在借鑑這些案例時能夠蹦出靈感的火花,那是我最開心的事

文章首發於微信公衆號:java企業級應用(WaXiData)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章