Socket作成で「Address already in use: connect」が発生する件の続き
昨日の続き。「closeしたソケットがTIME_WAIT状態になってしばらく使えない」件ですが、TCPの仕様的に使えないようです。
- TCPコネクションの破棄処理では、切断を開始した側のホストは、自身のCloseおよび相手のCloseが完了した後も、一定期間接続を維持しないといけない。。
- これは、Finパケットが再送されてきたりする可能性があるので、それを適切に処理できるようにするため、らしい。
- このあたりのフローについては、Ray:雑学事典 - コネクション とはの図がわかりやすい。
- RFCも参照。→RFC: 793 (日本語訳)
ということで、Windowsのソケット実装のバグとかではないようです。
昨日のコードがLinux環境でエラーにならなかったのは、作成と破棄を繰り返した数が少なかったからっぽい...orz。以下のように直すとLinux環境でもエラーを出すようになります。ただし、こっちででるのは「(TIME_WAIT中のソケットが浪費しているため、)割り当てるポートがありせん」とかいう感じのエラーですが。
long i = 0L; while ( true ) { System.out.println( i++ ); Socket s = null; try { s = new Socket(); s.connect(new InetSocketAddress("foo.com", 80)); } finally { if (s != null) { s.close(); } } }
実行結果です。
... 28230 28231 28232 Exception in thread "main" java.net.NoRouteToHostException: Cannot assign requested address at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333) at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) at java.net.Socket.connect(Socket.java:519) at java.net.Socket.connect(Socket.java:469) ...
SO_REUSEADDRとSO_LINGER
りょうさんが教えてくれた(ありがとうございます!)SO_REUSEADDRとSO_LINGERについてもちょっと調べてみました。
SO_LINGERを設定してやることで問題を回避/軽減することは可能ですが、やはり、「ソケットを短時間に大量に生成/破棄するようなコードを書かない」というのがまっとう且つ無難な対応のように思います。
SO_LINGER
TIME_WAITの時間を制御するオブション。
- TIME_WAITの待ち時間を0にすることで、上記問題を回避できる。
- ただし、この場合、finが再送されてきた場合の処理とかはできなくなるはず。
- 再送の処理ができなくなることのリスクとかどうなんだろー。それが評価できないと何ともいえないなー。
- JavaだとSocket#setSoLinger(boolean, int)で設定できる。
以下のコードだと、「Address already in use: connect」は発生しません。
long i = 0L; while ( true ) { System.out.println( i++ ); Socket s = null; try { s = new Socket(); s.setSoLinger(true, 0); s.connect(new InetSocketAddress("foo.com", 80)); } finally { if (s != null) { s.close(); } } }
SO_REUSEADDR
これをtrueにすると、「同一のポートを使うソケットがタイムアウト状態で存在しても、ソケットをバインドすることができる」ようになるらしい。
- JavaだとSocket#setReuseAddress(boolean)で設定できる。
- 利用するポートが固定な待ち受け側のソケットで使う機能っぽい。