tcp の iostream化 †前節までで、socketのclass化ができた。実際のnetworkの 応用ではsocketそのものを使うというよりは、tcp接続で stream型の入出力として扱う場合が多い。C++では標準 入出力はiostreamとして扱われている。そこで、ここでは tcp接続をiostreamの派生class化することを考察する。 iostreamとstreambuf †iostream(及びistreamとostream)は直接入出力を扱う わけではなく、streambufと協調して入出力機能、特に 整形入出力機能を提供する。実際に入出力を扱うのは streambufの方である。 streambufでは入出力用のbufferを確保し、 入出力が必要になった時にbufferの内容を更新する。 大部分の機能はstreambuf本体でやってくれる。 最低限実装が必要なstreambuf側の関数は
である。 これだけだとseek系の関数や書き戻し系の関数は 使えないが、我々の目的にはこれで十分だろう。 というわけで、まずはstreambufの派生classとして mytcpbufなるものを作ろう。 前節のmysocket classはmysocket.hに宣言されて いるものとして、 #ifndef MYTCPBUF_H_INCLUDED #define MYTCPBUF_H_INCLUDED #include <streambuf> #include "mysocket.h" class mytcpbuf : public std::streambuf { protected: virtual int underflow(); virtual int overflow(int c=std::char_traits<char>::eof()); virtual int sync(); protected: mysocket m_socket; }; #endif /* MYTCPBUF_H_INCLUDED */ さて、肝心のbufferをどうするかという問題がある。 streambuf は istream にも ostream にも iostream にも使う ので、istreamの時はget用のみ、ostreamの時はput用のみ、 iostreamの時は両方を用意するようにしたい。つまり streamの型に 応じて確保するbufferを変えたい。 これを実現するにはmytcpbuf の constructor で get用、put用のsizeを指定できるように しておく。istreamの時はput用を0に、ostreamの時はget用を0に 指定する。 この buffer は constructor の中で引数を使ってnewするのが 簡単そうであるが、 newに失敗して例外を throw した時(あるいは他に例外を throw する ものがあった時)すべてのメンバーをconstructor 開始前の状態にまできれいに戻す必要がある。 (constructor中での失敗なので、destructorが呼ばれない。 つまりconstructor中ですべてきれいになるように処理しなければ ならない)。 bufferを独自に用意する代わりに標準コンテナclassのvectorを 使うことも考えられる。この時必要な関数は begin()、end()ぐらいで あろう。 どれがよいかわからないが、 とりあえずメンバー関数名だけ合わせて独自class、mybuffer を作る。 こうしておけば、vectorにすることは簡単であろう。 と思ったが、vector の begin()、end() は iterator を返す。 これが vector<char> だからと言って、char* と同じとは限らない (実装依存)。istream_iterator や ostream_iterator あるいは istreambuf_iterator や ostreambuf_iterator と協調させる ようにできるかも知れないが、よくわからん。 というわけで独自class を作る。 と書いたが、vector の格納領域は連続であることが 保証されており、かつ C のメモリーモデルと互換なので、 pointer を得るには &*iterator で可能ということだ。 なるほど。ということは例えば vector<char> v に対し (char*)&*v.begin()で先頭 pointer へ変換できる。 まあ、でもやってしまったのでとりあえずこのまま書き 進める。 一応、他と衝突しないように mytcpbuf 内のclassとしておく。 またcopy constructorと 代入演算子をprivateでdummyに宣言してmybuffer objectを コピーすることは禁止しておく。また size() で確保した要素数 (今の場合バイト数)を返すようにしておく。 なお、mysocket を引数とする constructor も用意する。 これは accept() で新しい stream を生成するのに必要となる。 #ifndef MYTCPBUF_H_INCLUDED #define MYTCPBUF_H_INCLUDED #include <streambuf> #include "mysocket.h" class mytcpbuf : public std::streambuf { public: mytcpbuf(size_t rbufsize, size_t sbufsize); mytcpbuf(const mysocket& sock, size_t rbufsize, size_t sbufsize); virtual ~mytcpbuf(); protected: virtual int underflow(); virtual int overflow(int c=std::char_traits<char>::eof()); virtual int sync(); protected: class mybuffer { public: mybuffer(size_t n); virtual ~mybuffer(); size_t size() const; char* begin() const; char* end() const; private: mybuffer(const mybuffer& src); mybuffer& operator=(const mybuffer& src); private: size_t m_size; char* m_buf; }; protected: mysocket m_socket; mybuffer m_rcvbuf; mybuffer m_sndbuf; }; #endif /* MYTCPBUF_H_INCLUDED */ とりあえず、mybuffer class を書いてしまうと、 (ここでは n で new しているが、end() が返す pointer が確実に有効アドレスになるようにするためには n+1 で new した方がよいかも知れない) mytcpbuf::mybuffer::mybuffer(size_t n) { m_size = 0; m_buf = 0; if( n > 0 ) m_buf = new char [n]; if( m_buf ) m_size = n; } mytcpbuf::mybuffer::~mybuffer() { delete [] m_buf; } size_t mytcpbuf::mybuffer::size() const { return m_size; } char* mytcpbuf::mybuffer::begin() const { return m_buf; } char* mytcpbuf::mybuffer::end() const { return (m_buf + m_size); } さて、ここから実装(mytcpbuf の実装)に移ろう。 |