単純なserver program

CのプログラムをC++にするには、、、ファイル名の拡張子を .c から .cxx に変えればよい、、などと言うと殴られそうなので、もう少し真面目に 考えよう。

前の単純なserver programを見ると、socket 関数で descriptor を 作成した後、それを引数とする一連の関数を使って作業を行っている。 ということは、この descriptor をメンバー変数とする class を 作ってやれば、これら一連の関数から socket descriptor の部分を 無くすことができるということである(データの隠蔽戦略)。

というわけで、この socket descriptor を隠すという戦略だけで class 化してみよう。

class 名は mysocket にしよう (namespace を使って切り分けるなら socket のままでもよい のだが、ユーザが混乱をきたさないよう念のため)。 でもって、単純なserver programで使った socket 関連の 関数を socket descriptor だけ無くして全部そのまま(C の関数の形のまま) メンバー関数にしてしまおう。なお、前述の単純なserver program には必要ないが、client programで必要となる connect を付け加えて おく。

#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

class mysocket
{
public:
  mysocket();
  mysocket(int domain, int type, int protocol=0);
  virtual ~mysocket();
  virtual int close();
  virtual int connect(const struct sockaddr* addr, socklen_t len);
  virtual int bind(const struct sockaddr* addr, socklen_t len);
  virtual int listen(int backlog);
  virtual mysocket accept(struct sockaddr* addr, socklen_t* addrlen);
  virtual int send(const void* msg, size_t len, int flags=0);
  virtual int recv(void* msg, size_t len, int flags=0);
 
protected:
  int m_sd;
};

こういう定義であれば、関数の本体は掲載しなくても、ほとんど想像がつくだろう。 インラインでよいぐらいだ。まあ、一応上のファイルを(多重読み込み防止を 付け加えた上で)mysocket.h として、本体を書いておくと、 (後から述べるが、実は資源割り当てを行う class を作る時の 典型的な誤りを犯しているのだが、、)

#include "mysocket.h"

mysocket::mysocket()
{
  m_sd = -1;
}

mysocket::mysocket(int domain, int type, int protocol)
{
  m_sd = ::socket(domain, type, protocol);
}

mysocket::~mysocket()
{
  close();
}

int mysocket::close()
{
  int retval = -1;
  if(m_sd != -1)
  {
    retval = ::close(m_sd);
    m_sd = -1;
  }
  return retval;
}

int mysocket::connect(const struct sockaddr* addr, socklen_t len)
{
  return ::connect(m_sd, addr, len);
}
  
int mysocket::bind(const struct sockaddr* addr, socklen_t len)
{
  return ::bind(m_sd, addr, len);
}

int mysocket::listen(int backlog)
{
  return ::listen(m_sd, backlog);
}

mysocket mysocket::accept(struct sockaddr* addr, socklen_t* addrlen)
{
  mysocket tmpsock;
  tmpsock.m_sd = ::accept(m_sd, addr, addrlen);
  return tmpsock;
}

int mysocket::send(const void* msg, size_t len, int flags)
{
  return ::send(m_sd, msg, len, flags);
}

int mysocket::recv(void* msg, size_t len, int flags)
{
  return ::recv(m_sd, msg, len, flags);
}

簡単に解説を加えておこう。

  • default constructor では何もせず socket descriptor に不正を示す -1 を 設定する。
  • close() では、socket descriptor が不正でなければ 大域関数の close() を呼び、socket descriptor を不正に設定する。
  • destructor では close() が不正 descriptor であるかどうかチェックして くれるので、これを呼び出す。こうして、close() し忘れても destructor が ちゃんと close() してくれる。
  • その他の関数については大域関数がエラーを返してくれるだろうという前提 のもと socket descriptor のチェックもせずにそのまま 大域関数を呼び出す。

さてこれで準備は出来た!、前の単純なserver programを書き換えてみよう!

#include <string.h>
#include "mysocket.h"

#define BACKLOG 3
#define SERVICE_PORT 8888

int main()
{
  mysocket s(PF_INET, SOCK_STREAM);
  mysocket t;
  struct sockaddr_in sa;
  struct sockaddr_in ca;
  socklen_t calen;

  memset((char*)&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_port = htons( SERVICE_PORT );
  sa.sin_addr.s_addr = htonl( INADDR_ANY );

  if(s.bind((const struct sockaddr*)&sa,sizeof(sa)) == -1)
    return 1;

  if(s.listen(BACKLOG) == -1)
    return 1;
 
  while(1)
  {
    calen = sizeof(ca);
    t = s.accept((struct sockaddr*)&ca, &calen);
    t.send("Hello\r\n", 7, 0);
    t.close();
  }
  return 0;
}

しかし、しかし、しかし、しかし、しかし、、、 コンパイルも通り実行もできるのに、、、 "メッセージが送られない!!"

これは先に書いたように資源(今の場合 socket descriptor)割り当て を行う class を作る際の"典型的な誤り例"である。 これを次節"Copy constructorと代入演算子"で述べよう。


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