読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Socket作成で「Address already in use: connect」が発生する件の続き

Java

昨日の続き。「closeしたソケットがTIME_WAIT状態になってしばらく使えない」件ですが、TCPの仕様的に使えないようです。

  • TCPコネクションの破棄処理では、切断を開始した側のホストは、自身のCloseおよび相手のCloseが完了した後も、一定期間接続を維持しないといけない。

ということで、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にすると、「同一のポートを使うソケットがタイムアウト状態で存在しても、ソケットをバインドすることができる」ようになるらしい。