前回、echoサーバーを書いてみた。その続き。
echoサーバーを書いてみたときのメモ その1 ソケットAPIとTCP - bati11 の 日記
このechoサーバーだと同時に複数のクライアントを捌けない。どうしてか?実際に試して見る。クライアントから2回telnetでつないでみる。
クライアント1
$ telnet 192.168.33.10 30000 Trying 192.168.33.10... Connected to 192.168.33.10. Escape character is '^]'. Hello World!
クライアント2
$ telnet 192.168.33.10 30000 Trying 192.168.33.10... Connected to 192.168.33.10. Escape character is '^]'.
2つ目の接続では、 Hello World!
が表示されていない。
サーバー側で接続を確認してみる。
netstatでみると複数の接続ができてる。
$ netstat -an | grep 30000 tcp 0 0 0.0.0.0:30000 0.0.0.0:* LISTEN tcp 0 0 192.168.33.10:30000 192.168.33.1:64085 ESTABLISHED tcp 0 0 192.168.33.10:30000 192.168.33.1:64084 ESTABLISHED
しかし、ソケットに対するディスクリプタを確認すると、1つしかない(64084ポートを使ってる方)。
$ lsof -i:30000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME echo_serv 2316 ubuntu 3u IPv4 52901 0t0 TCP *:30000 (LISTEN) echo_serv 2316 ubuntu 4u IPv4 52902 0t0 TCP 192.168.33.10:30000->192.168.33.1:64084 (ESTABLISHED) $ ll /proc/2316/fd total 0 dr-x------ 2 ubuntu ubuntu 0 Mar 5 03:12 ./ dr-xr-xr-x 9 ubuntu ubuntu 0 Mar 5 03:12 ../ lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 0 -> /dev/pts/0 lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 1 -> /dev/pts/0 lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:12 2 -> /dev/pts/0 lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 3 -> socket:[52901] lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 4 -> socket:[52902]
$ ps x | grep echo_server 2316 pts/0 S+ 0:00 ./echo_server
psのSTATが S+
なので割り込み可能なスリープ状態でフォアグラウンドのプロセスグループに入っているという状態。
straceで見てみると、readでスリープしている。
$ sudo strace -p 2316 strace: Process 2316 attached read(4,
クライアント1で文字列を入力
$ telnet 192.168.33.10 30000 Trying 192.168.33.10... Connected to 192.168.33.10. Escape character is '^]'. Hello World! hoge hoge Connection closed by foreign host.
接続がサーバー側から切られて、クライアント2のtelnetに Hello World!
が表示される。
$ telnet 192.168.33.10 30000 Trying 192.168.33.10... Connected to 192.168.33.10. Escape character is '^]'. Hello World!
サーバー側で確認してみると新しい接続に変わってることがわかる。
$ lsof -i:30000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME echo_serv 2316 ubuntu 3u IPv4 52901 0t0 TCP *:30000 (LISTEN) echo_serv 2316 ubuntu 4u IPv4 56445 0t0 TCP 192.168.33.10:30000->192.168.33.1:64085 (ESTABLISHED) ll /proc/2316/fd total 0 dr-x------ 2 ubuntu ubuntu 0 Mar 5 03:12 ./ dr-xr-xr-x 9 ubuntu ubuntu 0 Mar 5 03:12 ../ lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 0 -> /dev/pts/0 lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 1 -> /dev/pts/0 lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:12 2 -> /dev/pts/0 lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 3 -> socket:[52901] lrwx------ 1 ubuntu ubuntu 64 Mar 5 03:14 4 -> socket:[56445]
まとめると、 read
から処理が進まず、クライアント2の接続に対するacceptが呼ばれない。そのためサーバーはクライアントを1つずつしか処理できない。
I/Oモデル
前回に引き続きUNIXネットワークプログラミングを参考にする。

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI
- 作者: W.リチャードスティーヴンス,W.Richard Stevens,篠田陽一
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 1999/07
- メディア: 単行本
- 購入: 8人 クリック: 151回
- この商品を含むブログ (35件) を見る
UNIXネットワークプログラミングを読むと5種類のI/Oがあるって書いてある。
- ブロッキングI/O
- 非ブロッキングI/O
- I/Oの多重化
- シグナル駆動I/O
- 非同期I/O
Unixネットワーキングプログラミング P.140
さらに、入力操作は2つのステップから成ると書いてある。
入力操作は次の2段階で構成されている。
- データの用意ができるまで待ち、
- そのデータをカーネルからプロセスにコピーする。
ソケットに関する入力では、最初のステップでは普通のネットワークからのデータの到着を待つ。パケットが到着すると、カーネル内のバッファにコピーされる。2つ目のステップでは、このデータをカーネルのバッファからアプリケーションのバッファにコピーすることになる。
Unixネットワーキングプログラミング P.140
ブロッキングI/O、ノンブロッキングI/O、I/Oの多重化、シグナル駆動I/Oの4つはステップ1をどう扱うかが異なる。ステップ2については同じ。非同期I/Oだけが他と異なる方法でステップ1,2を扱う。
非同期I/Oは、Linuxだとaioとして提供されているが、ググって色々読んでみた感じだとあまり使われてないっぽい?
同期I/O操作と非同期I/O操作
I/Oモデルとは別の観点で同期I/O操作と非同期I/O操作というのがある。
Posix.1 ではこれら2つの語を以下の様に定義している。
- 同期I/O操作では、これを要求したプロセスは要求したI/O操作が完了するまでブロックする
- 非同期I/O操作では、これを要求したプロセスはブロックしない
この定義によれば、最初の4つのI/Oモデル、すなわちブロッキング、非ブロッキング、多重化I/OおよびシグナルI/O操作のすべてにおいてプロセスはブロックする。非同期I/Oモデルのみが非同期I/Oの定義に一致している。
UNIXネットワーキングプログラミング P.144
書いてみたechoサーバーのI/Oモデル
書いてみたechoサーバーで使ってる read
はブロッキングI/Oである。ブロッキングI/Oではステップ1と2が終わるまで元のプロセスに処理が戻らない。 read
で処理が止まっていたのはステップ1で、クライアントからのデータの到達を待っているからである。クライアントからのデータが届くまでechoサーバーのプロセスはブロックされる。
複数クライアントを同時に捌くために、echoサーバーを以下の方法で書き換えてみようと思う。
- ブロッキングI/Oのまま。forkを使って複数プロセスを立ち上げ、1プロセス1クライアントで対応する
- 非ブロッキングI/Oを使って対応する。1プロセスで複数クライアントに対応する
- I/O多重を使って対応する。1プロセスで複数クライアントに対応する