前言
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
