閱讀754 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

  上一篇:go  C2B前還有S2b,阿裏攜手產學研探索新零售時代的供應鏈未來
  下一篇:go  《Servlet、JSP和Spring MVC初學指南》——2.5 小結