NeiNeigh's blog

覚書のようなブログ.PC・アプリケーション・プログラミング・オーディオ・アニメ

C言語のソケットプログラミング

ソケットプログラミングについて調べた.

ソケット通信

ソケット通信の流れ

おおまかな流れは

  • 接続をリスニングするソケットを作成.socket()
  • ポート番号・IPアドレスなどを指定するための構造体を設定.struct sockaddr_in
  • その構造体を使ってソケットにポート番号・IPアドレスを設定. bind()
  • ソケットの接続準備.listen()
  • ソケットの接続待機.accept()
  • データの送受信.read(), write()
  • ソケット切断.close()

このようになっている.
次から順番に詳しく見ていく.

socket()

  int listener;
  listener = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(listener < 0){
    perror("socket");
    exit(1);
  }

TCP/IP(IPv4)ならsocket()の引数はこうなる.
第1引数は通信ドメインプロトコルファミリ)を指定する.PF_INET*1IPv4を表す.
第2引数は通信形式を指定する.SOCK_STREAMは順双方向のバイトストリーム.
第3引数は使用プロトコルを指定する.0で自動設定のはずだが,なぜかIPPROTO_TCPじゃないとだめだった.

struct sockaddr_in

  struct sockaddr_in sin;
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  sin.sin_port = htons(PORT);
  • 最初に構造体を宣言.
  • memset()で構造体を0で埋める.bzero()という関数でも同様のことを行えるが,この関数は廃止予定でありmemset()を使うべき.なんでこれをやるのかはよくわかんない.大学の授業では,「まぁこういう作法なんだよ」みたいな酷い説明がなされた気がする.
  • sin.sin_familyにAF_INET(IPv4を表す)を代入する.
  • sin.sin_addr.s_addrは,通常INADDR_ANYで指定する.特定のIPアドレスを指定するとそのアドレスにきた要求だけを受け付けるようになる.INADDR_ANYだとどれでも要求を受け付ける.htonl()*2でネットワークバイトオーダに変換してから代入.
  • sin.sin_portを使用するポート番号で初期化.htons()で同じく変換してから代入.htonl()との違いは,引数と返り値がlの方はu_longで,sの方はs_short.

下記リンクを参照した.
http://www.coins.tsukuba.ac.jp/~yas/coins/syspro-2004/2004-05-10/
http://www-cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/NETWORK/struct.html

bind()

if(bind(listener, (struct sockaddr*)&sin, sizeof(struct sockaddr)) < 0){
  perror("bind");
  exit(1);
}

サーバ側で利用するアドレス(IPアドレスとポート番号)を設定する.
これでクライアントとの接続準備完了.

listen()

if(listen(listener, 5) < 0){
  perror("listen");
  exit(1);
}

要求受付を開始する.第2引数は最大何個のクライアントを接続要求で待たせることができるか(待ち行列の長さ)を指定している.5が一般的だが,Apacheなどでは500程度になっていたりするらしい.通常はOSの段階で5以下に制限されてしまう場合が多いので5でいい.

accept()

struct sockaddr_in addr;
int client;
socklen_t addrlen = sizeof(addr);

client = accept(listener, (struct sockaddr*)&addr, &addrlen);
if(client < 0){
  perror("accept");
  exit(1);
}

説明はこちらから引用.
http://research.nii.ac.jp/~ichiro/syspro98/server.html

第二引数addrは sockaddr構造体へのポインタで、インターネットでは、 sockaddr_in構造体をsockaddr構造体にキャストしたもの を与えます。この第二引数には接続してきたクライアントのアドレス等が格納 されます。sockaddr_in構造体として、その中身を取り出すと、クラ イアント側のIPアドレスやポート番号などの情報を調べることができます。な お、第二引数にNULLを与えた場合はクライアント側の情報を返すこ とはありません。

データの送受信と終了

送受信はread(),write(),recv(),send()で行う.
最後にclose(client)で通信を終える.

*1:PF_INETのかわりにAF_INETと書いてもよい.ここでは,Protocolを選んでいるのでPF_ が正しいが,実際にはPF_INET と AF_INET は同じで,区別してない人が多い

*2:よくわかんないけど,ビッグエンディアンかリトルエンディアンがどーのこーので変換しておく必要が有る.