C++流的streambuf詳解及TCP流的實現


前言

streambuf是C++流(iostream)與流實體(或者叫原始流,文件、標准輸入輸出等)交互的橋梁

# 文件流
fstream <--> filebuf <--> file
# 字符串流
stringstream <--> stringbuf <--> string

文件流和字符串流是C++標准庫已經提供了的,現在我的目標是實現一個使用TCP協議通信的socket流,所以首先我要讀取STL關於‘流’的源代碼,然后就有了這篇文章

tstream <--> tcpbuf <--> socket(tcp)

streambuf內部實現

術語說明:

  • get 相當於 從流中讀取數據
  • put 相當於 寫入數據到流中
  • 字符,C/C++中的char,也可以理解為字節

streambuf內部持有三個用於get的指針gfirst,gnext,glast和三個用於put的指針pfirst,pnext,plast,這些指針分別可以使用eback(),gptr(),egptr()pbase(),pptr(),epptr()函數獲得,在代碼中需要使用這些函數獲取指針,為了方便描述,我直接使用這些指針變量名

下面是其他幾個受保護的成員函數的作用

  • gbump(n) : gnext+=n
  • setg : setg(gfirst, gnext, glast)
  • pbump(n) : pnext+=n
  • setp : setp(pfirst, pnext, plast)

小結:

  • get緩沖區通過setg()設置,setg的三個參數分別對應gfirst,gnext,glast
  • put緩沖區通過setp()設置,setp的兩個參數分別對應pfirst,plast
  • 如果繼承自streambuf的子類不通過setg和setp設置緩沖區,也就是讀寫緩沖區為空,那么這個流可以說是不帶讀緩沖和寫緩沖的流,這時gfirst = gnext = glast = pfirst = pnext = plast = NULL

子類需要override(覆寫)幾個虛函數來封裝具體的流的實現

虛函數(protected)

這些函數有些需要子類實現,來屏蔽不同的流的具體實現,向上提供統一的接口

緩沖區管理

  • setbuf ---------- 設置緩沖區
  • seekoff --------- 根據相對位置移動內部指針
  • seekpos --------- 根據絕對位置移動內部指針
  • sync ------------ 同步緩沖區數據(flush),默認什么都不做
  • showmanyc ------- 流中可獲取的字符數,默認返回0

輸入函數(get)

  • underflow(c) ---- 當get緩沖區不可用時調用,用於獲取流中當前的字符,注意獲取和讀入的區別,獲取並不使gnext指針前移,默認返回EOF
  • uflow() --------- 默認返回underflow(),並使gnext++
  • xsgetn(s, n) ---- 從流中讀取n個字符到緩沖區s中並返回讀到的字符數:默認從當前緩沖區中讀取n個字符,若當前緩沖區不可用,則調用一次uflow()
  • pbackfail ------- 回寫失敗時調用

輸出函數(put)

  • overflow(c) ----- 當put緩沖區不可用時調用,向流中寫入一個字符;當c==EOF時,流寫入結束;與輸入函數的uflow()相對
  • xsputn(s, n) ---- 將緩沖區s的n個字符寫入到流中並返回寫入的字符數;與輸入函數的xsputn相對

緩沖區不可用是指gnext(pnext) == NULL或者gnext(pnext) >= glast(plast)

public函數

緩沖區管理

  • pubsetbuf : setbuf()
  • pubseekoff : seekoff()
  • pubseekpos : seekpos()
  • pubsync : sync()

輸入函數(get)

  • in_avail : (用於get的)緩沖區內還有多少個字符可獲取,緩沖區可用時返回glast-gnext,否則返回showmanyc()
  • snextc : return sbumpc() == EOF ? EOF : sgetc()
  • sbumpc : 緩沖區不可用時返回uflow();否則返回(++gnext)[-1]
  • sgetc : 緩沖區不可用時返回underflow();否則返回*gnext
  • sgetn : xsgetn()
  • sputbackc : 緩沖區不可用時返回pbackfail(c);否則返回*(--gnext)
  • sungetc : 類似於sputbackc,不過默認調用pbackfail(EOF)

輸出函數(put)

  • sputc : (用於put操作的)緩沖區不可用時,返回overflow(c);否則*pnext++ = c,返回pnext
  • sputn : xsputn()

iostream與streambuf的調用關系

下面就iostream常用的幾個函數說明他們的調用關系

  • read(char *s, int n) -> buf.sgetn(s, n)
  • getline() -> buf.sgetc(), buf.snextc(); 首先調用一次sgetc()來判斷當前字符是否為EOF,然后不斷地調用snextc()讀取下一個字符,直到讀到\n
  • peek() -> buf.sgetc()
  • sync() -> buf.pubsync()

總結

  • 在istream對象中,除了read這種一次讀入多個字符的函數外,一般的讀取流的函數(operator>>())、get、getline都是調用snextc()一次讀入一個字符
  • istream的readsome(buf, size)函數本質還是調用了read,大致相當於read(buf, min(in_avail(), size))
  • snextc函數,當緩沖區不可用時會觸發uflow(),uflow()會調用underflow()觸發一次讀取原始流的操作,如果讀到了流的末尾,可以返回EOF;緩沖區可用時直接從緩沖區中讀取一個字符return *gnext++
  • underflow函數的作用是:當讀取緩沖區不足時,從原始流中讀取一段數據並調用setg重新設置gfirst gnext glast三個指針,將讀到的數據緩存起來,並返回當下的字符return *gnext;原始流中沒有數據時(或者說讀到了流的末尾時)返回EOF
  • 只要原始流還可訪問(讀取或寫入),xsgetn與xsputn就需要盡可能的從原始流中讀取(寫入)n個字符。因為有些流比如tcp socket一次可能接收不完所需要的字符數,這就需要循環接收直到收到n個字符為止。
  • [gfirst, glast)永遠是已經從流實體里讀到的數據如果他們不為空的話

TCP流的實現

tcpbuf不可用的特性

TCP流屬於網絡連接,不像讀取本地的文件那樣可以自由移動文件指針,所以有一些流的特性是不可用的

  • seekoff ----- pubseekoff
  • seekpos ----- pubseekpos
  • showmanyc --- in_avail
  • underflow --- sgetc

參考代碼

https://github.com/luzhlon/tstream/blob/master/src/tstream.h

參考資料

http://www.cplusplus.com/reference/streambuf/streambuf/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM