C++流的streambuf詳解及TCP流的實現
streambuf是C++流(iostream)與流實體(文件、標準輸入輸出等)交互的橋梁
# 文件流
fstream <--> filebuf <--> file
# 字符串流
stringstream <--> stringbuf <--> string
上麵的文件流和字符串流是C++標準庫已經提供了的,現在我的目標是實現一個使用TCP協議通信的socket流
tstream <--> tcpbuf <--> socket(tcp)
首先來分析一下streambuf的內部實現
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)
- xsputn(s, n) ---- 將緩衝區s的n個字符寫入到流中並返回寫入的字符數
- overflow(c) ----- 當put緩衝區不可用時調用,向流中寫入一個字符;當c==EOF時,流寫入結束
緩衝區不可用是指gnext(pnext) == NULL或者gnext(pnext) >= glast(plast)
public函數
緩衝區管理
- pubsetbuf : setbuf()
- pubseekoff : seekoff()
- pubseekpos : seekpos()
- pubsync : sync()
輸入函數(get)
NOTE: 下麵的緩衝區指的是**用於get操作的緩衝區**
- in_avail : get緩衝區內還有多少個字符可獲取
- snextc :
return sbumpc() == EOF ? EOF : sgetc()
- sbumpc : 緩衝區不可用時返回uflow();否則返回(++gnext)[-1]
- sgetc : 緩衝區不可用時返回underflow();否則返回*gnext
- sgetn : xsgetn()
- sputbackc : 緩衝區不可用時返回pbackfail(c);否則返回*(--gnext)
- sungetc : 類似於sputbackc,不過默認調用pbackfail(EOF)
輸出函數(put)
NOTE: 下麵的緩衝區指的是**用於put操作的緩衝區**
- sputc : 緩衝區不可用時,返回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()
總結
- 除了read這種一次讀入多個字符的函數外,一般的函數都是調用snextc()一次讀入一個字符
- snextc()當緩衝區不可用時會觸發uflow(),uflow()會調用underflow()觸發一次讀取的操作,如果讀到了流的末尾,可以返回EOF
- 我們可以在underflow()函數裏重新設置gfirst gnext glast,使得snextc()不會不斷的調用uflow(),而可以先讀取緩衝區裏的數據
- 若緩衝區不為空,此函數需要重新移動gnext到新的位置並返回*gnext
- 流中沒有數據時(或者說讀到了流的末尾時)返回EOF
-
[gfirst, glast)
永遠是已經從流實體裏讀到的數據如果他們不為空的話
TCP流的實現
class tcpbuf : public std::streambuf {
void initsocklib() {
#ifdef WIN32
static bool inited = false;
WSADATA wsaData;
if (!inited)
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}
public:
enum { BUFSIZE = 1 << 5 };
tcpbuf(SOCKET s) { initsocklib(); sock = s; }
tcpbuf() {
initsocklib();
sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}
~tcpbuf() override { close(); }
bool connect(char *ip, unsigned short port) {
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_family = AF_INET;
addr.sin_port = ::htons(port);
return !::connect(sock, (sockaddr *)&addr, sizeof(addr));
}
int close() {
#ifdef WIN32
return ::closesocket(sock);
#else
return ::close(sock);
#endif
}
protected:
// Buffered get
int underflow() override {
auto n = ::recv(sock, buf, BUFSIZE, 0);
return n > 0 ? (setg(buf, buf, buf + n), *gptr()) : EOF;
}
// Unbuffered put
int overflow(int c) override {
if (c == EOF) return close();
char b = c;
return ::send(sock, &b, 1, 0) > 0 ? c : EOF;
}
std::streamsize xsputn(const char *s, std::streamsize n) override {
auto x = ::send(sock, s, n, 0);
return x > 0 ? x : 0;
}
// flush
int sync() override {
#ifdef WIN32
return 0;
#else
return flush(sock);
#endif // WIN32
}
//streamsize showmanyc() override { return 1; }
private:
SOCKET sock;
char buf[BUFSIZE];
};
class tstream : public std::iostream {
public:
tstream() : std::iostream(&_buf) {}
tstream(SOCKET sock) : _buf(sock), std::iostream(&_buf) {}
tstream(char *ip, unsigned short port)
: tstream() { connect(ip, port); }
bool connect(char *ip, unsigned short port) {
return _buf.connect(ip, port);
}
private:
tcpbuf _buf;
};
參考資料
https://www.cplusplus.com/reference/streambuf/streambuf/
最後更新:2017-05-28 00:43:16