2011年7月28日木曜日

(やはり)奥が深いTCP/IP・・・

簡易httpサーバを実装中に勉強になったことを記録しておく。

今回、知ったのは"2MSL待ち"という状態のことだ。

実装中のhttpサーバをテスト中、何度かhttpコマンドのやりとりをしたあと、なぜか急にhttpサーバがウェブブラウザからのアクセスに反応しなくなる。Wiresharkでプロトコルを見てみると、ブラウザからのSYN要求に対してサーバはRSTを返して接続を拒否している。サーバそのものが死んでいる訳ではないようだ。さっきまで動いていたのに、急になぜ?

調べてみると、TCPのステートマシンがTIME_WAIT状態に入っているようだ。この状態では、2MSL時間だけ続くようで、実装によってはそれは4分くらいの時間になることもあるらしい。そもそもこのTIME_WAITステートは何のためにあるのか?

それは、通信相手が最後に送ってくるFINに対して、このサーバがACKを返す期間を設けるためのようだ。TCPはロスの無い通信路を提供するモノだけれど、そのTCP制御パケットを送る下位レイヤー(通常IP)はパケットの到達の正否を保証しないので、相手が送ってくるFINに対するACK応答が正しく相手に伝わったかどうかは分からない。もしACKが伝わっていなければ相手は再度FINを要求してくるかもしれない、そのためにこのTIME_WAITステートにとどまる必要がある、と理解した。そしてTIME_WAITステートにとどまっている時間は、そのポート番号が使えなくなる(使えなくする)というのがTCPの仕様にあるとのこと。そうだったのか、知らなかった・・・。

で、なぜ今回この状態に陥ったのか?httpサーバ側の実装の問題だった。サーバー側からTCPコネクションをアクティブcloseしてはいけない。アクティブcloseすると、上記のWAIT_TIMEステートに入ってしまう。あくまでもブラウザ側からのcloseを待つべし。パッシブcloseであれば、WAIT_TIMEステートを経由せずにソケットはCLOSEステートに戻ることができる。

と思ったけれど、これも事情によるようだ。今回の場合はリソースが限られている組み込み系TCP/IPの話。先日から苦しんでいる(?)TINETの話だ。
GByteサイズのメモリを搭載するPCなら気にする必要もないけれど、組み込み系では扱えるソケットの数を5個とか10個とか極端に制限せざるを得ないことがある。今回の件も「アクティブcloseがいけない」と書いたけれど、実際にはhttpサーバ側からアクティブクローズした場合、それ以降はブラウザは別のポート番号を使って接続してこようとするから手続き上は問題ないと思われる。問題は、やはり制限されたソケットしか取り合え使えない組み込み機器側のTCPスタックの実装によると思われる。

今回の場合、何回もhttpリクエストを実行してその都度ポート番号を使い捨てにしていると、なけなしのソケットすべてがWAIT_TIMEステートの状態になってしまう。この状態で新しい別ポート番号で接続しようとしてももう使えるソケットが残っていないのだ。少なくとも2MSL期間が過ぎるまでは・・・。
その点で限られたリソースの中で動作し、かつ、短時間に次々とコネクションの確立と切断を繰り返す環境の中では、少なくともアクティブcloseは貴重なソケットリソースを長時間占有してしまう。これを回避する方法は・・・・、これから考えます。

0 件のコメント:

コメントを投稿