3連休何をしようかと考えていたら,なんとなくデーモンプログラムを書いてみたくなったので,Cによるネットワークプログラミングを勉強することにした.
2年くらい前に文献[1]を購入し,過去に2回ほど勉強しようと思ったけれど,とにかくインクルードする必要があるヘッダーファイルが大量にある時点で嫌気が差して挫折してしまった.
ソケットのイメージはわかっているつもりだけれど,実はよくわかっていなかった.なぜかわからないけれど,今日本を読みながらてしてしコードを書いていたら色々つながって基本的なことを理解することができた.
デーモンを作るところまで行きたかったけれど,途中まででいったん中断.サーバプログラム,クライアントプログラムを書く手順をメモしておく.
- サーバプログラム
- socketをつくる
- bindする
- listenする
- acceptする
- いろいろ処理する
- クライアントプログラム
- socketをつくる
- connectする
- いろいろ処理する
サーバプログラムが子プロセスを作る場合には,acceptの段階でforkすればよい.ここらへんはまた今度勉強しよう.
socketを作る際に,sockaddr_in構造体をいじるのだけれど,Cプログラミングに慣れていないとかなりカオスに見えるので,前回はここで気持ち悪くなってしまったことを思い出した.きちんと眺めると,当たり前のことをしていることに気がついた.
とにかくたくさんヘッダファイルをインクルードしなければいけない.文献[2]を見ながら,なるほどこれはこのヘッダファイルなのか,と思っていたけれど,30分経ったら全て忘れていた.対応を覚えるのはあきらめて,関数を覚えておいて,その都度manすることにしよう.
昔 (*1),職場のUbuntuにオンラインマニュアルを入れておらず,
「manも入れてないのか,このゆとりめ!」
と先輩に呆れられたのを思い出した.manを引きながらコードを書くようになったので,少しは成長したのかもしれない.
(*1) はい,ほんの一年ちょっと前です.すんません.
細かいメモ
bindできなくなる
どうやらクライアントから停止しないとTIME_WAIT状態にあるためbindに失敗するというのが理由らしい.
sockaddr_in.sin_addrの設定方法
色々な方法がある
- inet_addr, inet_atonを利用する方法
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> struct sockaddr_in sin; // in_addr_t inet_addr(const char *cp) // 255.255.255.255の場合-1を返すので,エラー処理の観点からinet_aton()使用を推奨 sin.in_addr = inet_addr("127.0.0.1"); // int inet_aton(const char *cp, struct in_addr *pin) inet_aton("127.0.0.1", &(sin.sin_addr));
- gethostbyname()を利用する方法
hostent構造体が返されるので,二度手間かかる
#include <netdb.h> struct hostent *gethostbyname(const char *name); // memcpyでh->h_addrをsin_addrにコピーする // c.f. #define h_addr h_addr_list[0] // h_length /* length of address; IPv4の場合4バイト */ hostent *h = gethostbyname("localhost"); memcpy(sin.sin_addr, h->h_addr, h->h_length);
残った疑問
accept()の使い方
文献によって,accept()を行う際に第2,3引数を渡す,渡さない方法があるらしい.クライアント情報を使わない場合でも丁寧にsockaddr_in構造体を用意して,情報を受け取っていた.
// SYNOPSIS #include <sys/types.h> #include <sys/socket.h> int accept(int s, struct sockaddr * restrict addr, socklen_t * restrict addrlen); // 参考文献[1] clinet_soc = accept(server_soc, NULL, NULL); // 参考文献[2] struct sockaddr_in client_sin; memset(client_sin, 0, sizeof(client_sin); int addrlen = sizeof(client_sin); client_soc = accept(server_soc, (struct sockaddr *) &client_sin, &addrlen);
まだわかっていないこと
- send, recvの使い方 (今回はwrite, readを使っていた)
参考文献
今回参考にした文献は以下の2つ.他にもたくさん良書はあるに違いない.
- [1] 雪田修一. UNIXネットワークプログラミング. 技術評論社 (2003).
- [2] 内田智史監修, 株式会社システム計画研究所編. C言語によるプログラミング -応用編- 第2版. オーム社 (2002).
文献[1]は,ステップアップできるように書かれている.後半では,前半で作成した自作ライブラリを利用するようにしているので,Cプログラミングの勉強にもなる.また,ソースを分割し,Makefileを作ってコンパイルしているのでmakeの使い方の勉強にもなる.僕は拾い読みをしようとしたので,なかなか理解ができなかったのだと思う.ある程度理解したとたんに,なんて丁寧に書かれているんだと感じたので...
文献[2]は解説がとても丁寧.例えば構造体がどのヘッダで宣言されているか,そして構造体の各要素がどのような意味を持っているかということも記述されている.ネットワークプログラミングに限らず,入門書には書いていない,意外と本に書かれていない大切なことが書かれている印象 (全部読んでいないので).とにかく「丁寧」というのが正しい形容詞だと思う.