またまたTCPネタ。
を読んでいたら出てきた、shutdown(2)が色々と気になったので実験してみた。manを引くとこんな感じ。ざっくり言うとsocketに対して送信を行わない通知をしたら受信をしない通知をしたりするという機能となっています。SHUT_RD
とSHUT_WR
とSHUT_RDWR
でそれぞれどう言うことが起きてるのかをtcpdumpを使って観測してみます。
書式 #include <sys/socket.h> int shutdown(int sockfd, int how); 説明 shutdown() は、 sockfd に関連づけられているソケットによる全二重接続 (full-duplex connection) の一部または全てを閉じる。 how が SHUT_RD ならば、それ以降の受信を禁止する。 how が SHUT_WR ならば、それ以降の送信を禁止する。 how が SHUT_RDWR ならば、それ以降の送受信を禁止する。
加えて参考までに詳解UNIXプログラミング 第3版引用します。
ソケットをcloseできるのに、なぜshutdownが必要なのでしょう?理由はいくつかあります。まず、closeは使用中の最後の参照がクローズされたときにのみネットワークの端点を解放します。ソケットを(例えばdupで)複製している場合、それを参照する最後のファイル記述子をクローズするまではソケットは解放されません。関数shutdownは、ソケットを参照する使用中のファイル記述子の個数に関係なくソケットを非活性にできます。第2に、ソケットのある方向だけを止めると便利なことがあります。例えば、通信相手のプロセスにデータ送出を完了したことを伝えるためにソケットへの書き込みを閉じても、当該ソケットを使って相手プロセスからデータを受信し続けることができます。
SHUT_RD
こちらを指定してshutdown(2)を呼び出すと以降で受信を禁止するという動作をとります。ncとかを使ってshutdown後のソケットに対してデータを送信してみて状況を確認してみます。
tcpdumpの結果を確認します。どうやらデータに対してackが返されていて正常に通信を行っているように見えます。shutdown(SHUT_RD)してるのに??ってなりますがこれは仕様です。shutdown(SHUT_RD)にはTCPプロトコルに対応するものがないため、相手側がSHUT_RDしたsocketに誰かが書き込んだ場合、または宣言した後に読み取ろうとした場合の動作はOSでの実装次第になります。shutdown(SHUT_RD)のread(2)などの呼び出しは即座にEOFを示す0が返ります。
05:43:39.278286 IP 192.168.1.210.11111 > 192.168.1.210.54550: Flags [.], ack 3, win 8186, options [nop,nop,TS val 1155795369 ecr 1155795369], length 0 05:43:40.050903 IP 192.168.1.210.11111 > 192.168.1.210.54550: Flags [.], ack 5, win 8186, options [nop,nop,TS val 1155796142 ecr 1155796142], length 0 05:43:40.441818 IP 192.168.1.210.11111 > 192.168.1.210.54550: Flags [.], ack 7, win 8186, options [nop,nop,TS val 1155796533 ecr 1155796533], length 0
netstatで見るとRecv-Qにデータが到着していてアプリケーションが読み取っていないことがわかります。nc側的にはackが帰ってきているのでデータは読み取られたと認識するためにコマンド自体はエラーになりません。これの何が嬉しいのかと言うと接続は引き続きアクティブで使用可能な状態で新しいデータが来ないことが保証されるのでgraceful shutdownの途中に挟むことで新規データの受付をせずに既存コネクションへはデータを返すと言う処理を行うことができます。
tcp 6 0 192.168.1.210:11111 192.168.1.210:54550 ESTABLISHED 22525/./a.out off (0.00/0/0)
SHUT_WR
こちらを指定してshutdown(2)を呼び出すと以降で受信を禁止するという動作をとります。tcpdump内ではサーバ->クライアントでFINが飛んでいることを確認できてハーフクローズの状態となりクライアント->サーバへのデータ送信は以降も可能となります。この時にサーバがソケットに対してwrite(2)などを発行するとSIGPIPEがサーバプロセスへ送られます。
06:24:07.893466 IP 192.168.1.210.11111 > 192.168.1.210.54566: Flags [F.], seq 1, ack 1, win 8186, options [nop,nop,TS val 1158223984 ecr 1158223984], length 0
netstatで見るとクライアントのncはCLOSE_WAITでサーバ側はFIN_WAIT2となって以降もrecv-qなどのキューが上がっていないことが確認できます。
tcp 0 0 192.168.1.210:54554 192.168.1.210:11111 CLOSE_WAIT 24004/nc off (0.00/0/0) tcp 0 0 192.168.1.210:11111 192.168.1.210:54554 FIN_WAIT2 24002/./a.out off (0.00/0/0)