Socketについて - その1

どんな種類のサーバであっても深く理解しようと思ったら、Socketを避けて通れないのでここらで一念発起して勉強してみる。

sock(2)

socket - 通信のための端点(endpoint)を作成する

socket() は通信のための端点(endpoint)を作成し、ディスクリプター(descriptor)を返す。
domain 引数は通信を行なうドメインを指定する; これはどの プロトコル・ファミリ(protocol family)を通信に使用するかを指定する。 これらのファミリは に定義されている。 現在、理解できるフォーマットは以下の通り。

AF_UNIX, AF_LOCAL ローカル通信 unix(7)
AF_INET IPv4 インターネット・プロトコル ip(7)
....

もうちょっと調べてみる。

まつもと直伝 プログラミングのオキテ 第16回 ネットワーク・プログラミング(ソケット編)

http://itpro.nikkeibp.co.jp/article/COLUMN/20071031/285990/

 ソケットはネットワーク通信に用いるファイル・ディスクリプタ(file descriptor)です。そこで,ソケットの説明に入る前に,より一般的なファイル・ディスクリプタについて押さえておきましょう。

 LinuxなどUNIX系OSでは,入出力はすべてバイト列(文字列の一種)として扱われます。入出力では,バイト列のことをストリーム(streamは「流れ」の意味)と呼びます。

 ディスクリプタは単なる整数で,ストリームを区別する識別子として使います。OSはあるプログラム(プロセス)がアクセスしているストリーム一つひとつに固有の番号を付け,その番号をディスクリプタと呼びます。

 ディスクリプタはプロセスごとの固有の値で,0番が標準入力,1番が標準出力,2番が標準エラー出力であると決められています。そのほかのストリームはオープン(利用開始)されるたびに小さい番号から順に使われ,クローズ(利用終了)されると同じ番号が再利用されます。

 ディスクリプタの最も重要な特徴は,ファイル・ディスクリプタが対応しているストリームの接続先が,ローカルのファイルやネットワーク経由の接続というように異なっていても,全く同じように入出力できることです。

なるほど、分かりやすい!ソケットもfile descriptorとして取り扱えるからファイルと同じIFで扱える、と。だからprintで書き込むのねー。

超シンプルなサーバを書いてみる

port:9999でlistenして、1行readしたら1行writeしてcloseするというシンプルなサーバをperlで書いてみる。

use IO::Socket::INET;

my $sock = IO::Socket::INET->new(
    LocalAddr => 'localhost',
    LocalPort => 9999,
    Proto     => 'tcp',
    Listen    => 10,
    ReuseAddr => 1
) or die $!;

while( my $conn = $sock->accept ){
    my $msg = <$conn>;
    print {$conn} "accept: $msg";
    $conn->close;
}
$sock->close;

さて、この時file descriptorはどうなってるか?

kotaro@dev1:~> ls -la /proc/4712/fd/
lrwx------ 1 kotaro kotaro 64  123 23:56 0 -> /dev/pts/1
lrwx------ 1 kotaro kotaro 64  123 23:56 1 -> /dev/pts/1
lrwx------ 1 kotaro kotaro 64  123 23:56 2 -> /dev/pts/1
lrwx------ 1 kotaro kotaro 64  123 23:56 3 -> socket:[14137]

たしかに3番がsocketになってます。

接続フロー

さて、実際にどういうシステムコールが実行されるのか。accept(2)のmanを見ると以下のように書いてある。

1. socket(2) でソケットを作成する。
2. bind(2) を使ってソケットにローカルアドレスを割り当てて、 他のソケットがこのソケットに connect(2) できるようにする。
3. listen(2) を使って、接続要求を受け付ける意志と接続要求を入れるキュー長を指定する。
4. accept(2) を使って接続を受け付ける。

listen(2)

listen() は sockfd が参照するソケットを接続待ちソケット (passive socket) として印をつける。 接続待ちソケットとは、 accept(2) を使って到着した接続要求を受け付けるのに使用されるソケットである。

accept(2)

accept() システムコールは、接続指向のソケット型 (SOCK_STREAM, SOCK_SEQPACKET) で用いられる。 この関数は、接続待ちソケット socket 宛ての保留状態の接続要求が入っているキューから 先頭の接続要求を取り出し、接続済みソケットを新規に生成し、 そのソケットを参照する新しいファイル・ディスクリプタを返す。 新規に生成されたソケットは、接続待ち (listen) 状態ではない。 もともとのソケット sockfd はこの呼び出しによって影響を受けない。

まとめ(というか所感)

socketとは何たるか、ようやく原理が掴めた感じ。perlだとラップされてるので、Cで書いてみたいな。多分写経になっちゃうけど。