お手伝いの関係でWinsock2を触り始めているが、MSDN掲載の初歩のサンプルコードを打ち込んでみたら妙な挙動になって2時間ほど嵌ってしまった。
このサンプルコードでは send() 後に shutdown(SOCKET, SD_SEND) して「もうこれ以上クライアント側からの送信データはありませんよ」とサーバ側に明示した後、recv()で受信し、closesocket()を呼んでいる。
shutdown()は送信 or 受信 or 両方についてソケットを無効化する。もちろんPOSIXでも定義されているが、あまり見かけることはない。
一旦shutdown()を明示的に呼んでソケットを無効化したのち、recv() or send() で残りのデータ送受信処理を行い、closeするというのはMSDN上では「Graceful Shutdown」と呼ばれているらしい。
あまり見かけることがないのは、closesokcet()内で暗黙的にgraceful shutdownが呼ばれている為、明示的にshutdown()を呼ばなくても問題ないケースが多い為だろう。
ここからが本題だが、client-sideでの「send() -> shutdown(SD_SEND) -> recv()」で、recv()がいきなり0 = connection closeを返す場合がある。
実験してみたところ、サーバー側が先にFINパケットを送信すると、recv()が0を返す挙動を見つけた。
例えばJavaで作った単純な"echo"サーバーに接続したところ、問題なくrecv()は"echo"サーバーが送信してきたデータを受信出来た。
ところが、送信データを"GET / HTTP.1.0"にしてローカルのApache等に送信してみたところ、かならずrecv()が0を返してしまう。こちらのケースは、Apacheプロセス側が応答パケットの送信に続いてFINパケットまで送ってきている。つまりサーバー側から先に切断フェーズに進む形になる。どうもこのケースにおいては、クライアント側でshutdown(SD_SEND) -> recv() すると0が返されconnection closeとして動作するようだ。
更に、試しにクライアント側でshutdown()の前に適当なwaitを入れてみたところ(getchar())、その場合はrecv()が正常値を返すことを確認出来た。
前述の「graceful shutdown」MSDNでは「クライアント側からの切断」処理については書かれているが、「サーバー側が先に切断フェーズに入った場合」の挙動については書かれていない。
あまりまとまっていないが、shutdown()を明示的に呼び出すgraceful shutdownで期待したとおりの挙動にならない場合は、どちらが先にFINパケットを送り切断フェーズに入ったのかについて注意が必要。
コメント