stream 型入出力 †Stream 型入出力に対する streambuf を考える。 入力 †入力は backup sequence を持たせなければ 比較的容易であると思われるが、backup sequence が無いと delimter 付きの 関数が失敗する可能性がある。そこで、ここでは backup sequence を持つ実装を考える。
出力 †出力は overflow() を上書きする。ただし、残余がある場合も想定する。出力文字が traits::eof() 以外に指定された場合、バッファ出力後にその文字をバッファに入れる。 従って、この場合、完全には空にならない。 完全にバッファを出力したい場合は、 ostream で flush() を呼ぶ。ostream の flush() は省略時設定で、streambuf の sync() を呼ぶことになっているので、sync() を実装する。 実装例 †Socket descriptor s に対する入力 stream 用の streambuf 派生 class、Sockbuf を 作ってみる。 この class が s に対して行う操作は recv() のみとする。 Socket descriptor の(dup() を使った)複製も行わない。 すなわち、connect()、close()、accept() など recv() 以外の操作は、 すべてこの class の外で行うものとする。 ヘッダファイル sockbuf.h #ifndef SOCKBUF_H_INCLUDED #define SOCKBUF_H_INCLUDED #include <iostream> class Sockbuf : public std::streambuf { public: Sockbuf(int s); ~Sockbuf(); protected: virtual int sync(); virtual int underflow(); virtual int overflow(int c = std::char_traits<char>::eof()); private: static const int minback = 256; static const int rbufsiz = 768; static const int wbufsiz = 512; int m_sock; char m_rbuf[rbufsiz]; char m_wbuf[wbufsiz]; }; #endif /* SOCKBUF_H_INCLUDED */ 本体 sockbuf.cpp #include "sockbuf.h" #include <winsock2.h> using namespace std; Sockbuf::Sockbuf(int s) { m_sock = s; setg(m_rbuf,m_rbuf,m_rbuf); setp(m_wbuf,m_wbuf + wbufsiz); } Sockbuf::~Sockbuf() { } int Sockbuf::underflow() { if(egptr() > m_rbuf + minback) { int d = egptr() - m_rbuf - minback; char* s = m_rbuf; while(s < (egptr() - d)) { *s = *(s + d); ++s; } setg(m_rbuf, gptr() - d, egptr() - d); } int m = rbufsiz - (egptr() - m_rbuf); int n = recv(m_sock, egptr(), m, 0); if(n <= 0) return (int)char_traits<char>::eof(); setg(m_rbuf, gptr(), egptr() + n); return (int)*gptr(); } int Sockbuf::sync() { if(pbase()) { int nleft = pptr() - pbase(); char* buf = pbase(); while(nleft > 0) { int nsent = send(m_sock, buf, nleft, 0); if(nsent <= 0) return (-1); nleft -= nsent; buf += nsent; } } setp(m_wbuf, m_wbuf + wbufsiz); return 0; } int Sockbuf::overflow(int c) { if(pbase()) { int d = pptr() - pbase(); if(d > 0) { int n = send(m_sock, pbase(), d, 0); if(n <= 0) return (int)char_traits<char>::eof(); char* s = pbase(); while((s + n) < pptr()) { *s = *(s + n); ++s; } pbump(-n); } else setp(m_wbuf, m_wbuf + wbufsiz); if(c != char_traits<char>::eof()) { *pptr() = (char)c; pbump(1); return c; } return (int)char_traits<char>::not_eof(c); } return (int)char_traits<char>::eof(); } 使用例 †TCP接続用 C library と組み合わせた例を示す。Source file 名を exam.cpp とする時、Windows の場合 gcc -c -DWIN32 tcplib.c g++ -DWIN32 exam.cpp sockbuf.cpp tcplib.o -lws2_32 -oexam.exe 使用例1 †サーバに接続して、文字を読み出し表示する。切断はサーバ側から行うものとする。 #include "sockbuf.h" #include "tcplib.h" using namespace std; int main(int argc, char* argv[]) { int port; SOCKET s; if(argc != 3) { cerr << "Usage " << argv[0] << " host port" << endl; return (-1); } port = atoi(argv[2]); netlibstart(); s = tcpsocket(); tcpopen(s, argv[1], port); Sockbuf mybuf(s); std::istream mystream(&mybuf); char c; while(mystream.get(c)) cout << c; tcpclose(s); netlibstop(); return 0; } 使用例2 †Http クライアントもどき。Host port を指定すると、そのルートドキュメントを 要求し、応答を表示する。 #include "sockbuf.h" #include "tcplib.h" using namespace std; int main(int argc, char* argv[]) { int port; SOCKET s; if(argc != 3) { cerr << "Usage: " << argv[0] << " host port" << endl; return (-1); } port = atoi(argv[2]); netlibstart(); s = tcpsocket(); tcpopen(s, argv[1], port); Sockbuf mybuf(s); std::iostream mystream(&mybuf); mystream << "GET / HTTP/1.0"; mystream.put(0x0d); // CR mystream.put(0x0a); // LF mystream.put(0x0d); // CR mystream.put(0x0a); // LF mystream.flush(); char c; while(mystream.get(c)) cout << c; tcpclose(s); netlibstop(); return 0; } |