しばらく車輪の再開発しながらもうちょっと低レイヤのことを学びたいと思った。
車輪の再開発初日。
簡単なTCPサーバーとクライアントを作ってみた。
ウェブ屋さんなんだけど、HTTP通信ってどうやってやってるんじゃっていうところはよくわかっていないので、もう少し知っておこうと思った。
今日はとりあえず動くところまで持ってきた。
どうやればいいかというのもまずわからなかったわけだが、ネットワークはなぜつながるかを読んだ時に、接続に必要な情報を持っているのはsocketだったような気がするなというのを思い出した。
なるほどそれならsocketを作ればいいかと考えてみた。
socketを作るにはどうしたらいいかなと思ったが、好きな言語で書こうとするとSocketの便利なものが用意されていてなんかちょろっとできちゃって良くないので、ほとんど初めてCを使うことにしている。
調べてみるとソケット関連の一連のシステムコールがある。
http://www.geocities.co.jp/Athlete-Samos/7760/study/unix_socket1.html
見た感じsocketを作ってbindしてlistenしてacceptすれば良さそうだなと。
それぞれ何をしているのか少し見てみる。
socket
ソケットディスクリプタを返す。
ソケットディスクリプタなんぞや
たまたまとても良い説明があった。
http://itpro.nikkeibp.co.jp/article/COLUMN/20071031/285990/
- 入出力は全てバイト列
- バイト列のことをストリームと呼ぶ
- ディスクリプタはただの整数で、ストリームを区別する識別子
- 重要なのは、ディスクリプタが対応しているストリームの接続先がファイルであれネットワークであれ同じように入出力できるということ
- ディスクリプタがあれば、read, writeシステムコールが読み書きに使える
ようするにディスクリプタというのはストリームの識別子なんだと。
ソケットディスクリプタもファイルディスクリプタの一種で、そのディスクリプタが指すストリームを介して他のソケットとやりとりができるってことかな?
ここも参考にした。
bind
bindは、manページによるとソケットに名前をつけるもののようだ。
名前をつけるってなんだよ、と思っていたら、manページによると、アドレスを割り当てることを伝統的に「ソケットに名前を付ける」と呼ぶらしい。
bindシステムコールに渡しているものを考えてみる。
第1引数はソケットディスクリプタ。これになにかを割り当てるということなので、第2引数が重要そう。
第2引数は、sockaddr構造体だ。
sockaddr構造体は、汎用なアドレス型らしい。(http://www.geocities.co.jp/Athlete-Samos/7760/study/unix_socket1.html)
どういう構造体なのか。
sockaddr構造体は、
struct sockaddr { u_char sa_len; /* total length */ sa_family_t sa_family; /* address family */ char sa_data[14]; /* actually longer; address value */ };
このような構造体だということ。
長さとアドレスファミリーの種類と実際のアドレスの値を持っている。
これが汎用アドレス型で、実際にはUNIX_LOCALに特化したsockaddr_unかInternetに特化したsockaddr_inを使うとのこと。(http://www-cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/NETWORK/struct.html)
確かに、コード上では宣言している方はsockaddr_in型で、使う時に汎用型にキャストしている。
アドレスファミリ
ここでアドレスファミリってなんぞという疑問がわく。
なんぞ。
http://www.geekpage.jp/programming/winsock/addressfamily.php
AF_INET, AF_INET6くらいしか使うことはなさそう。これはIPv4とIPv6を指してる。
そういうネットワークアドレスの種類を表すのがアドレスファミリで、AFなんとかっていう定数が用意されてる。
今回はAF_INETを使っている。
まとめ
今日はここまで。
- ファイルディスクリプタがストリームの読み書きを抽象化してる
- ソケットもファイルディスクリプタ
- ソケットを作ったらまずソケットにアドレスを割り当てる
- アドレスファミリとは、ネットワークアドレスの種類。例えば、IPv4などがあるよ。
コード
割とすぐ書けたけど最初は動かなかった。
なぜかと思ったら、bindしたアドレス構造体のポートの指定方法がおかしかった。
- addr.sin_port = 18081; + addr.sin_port = htons(18081);
htonsする必要があった。これなにしてるのかよくわからない・・。
次回調べる。