C++とnetwork programming

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側の関数は

  • int sync()
  • int underflow()
  • int overflow(int c = char_traits<char>::eof())

である。 これだけだと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 の実装)に移ろう。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2006-06-08 (木) 17:10:33