2011-06-30 11 views
5

Java NIOを使用してTCPソケットを使用してC++サーバーに接続するJavaクライアントがあります。これはLinux、AIX、HP/UXで動作しますが、SolarisではOP_CONNECTイベントは発生しません。Java Solaris NIO OP_CONNECTの問題

さらなる詳細:

  • Selector.select()は0を返して、そして '選択キーセット' が空です。
  • ローカルマシンに(ループバックまたはイーサネットインターフェイス経由で)接続するときにのみ問題が発生しますが、リモートマシンに接続するときには問題が発生します。
  • 2つの異なるSolaris 10マシンでこの問題を確認しました。 JDKバージョン1.6.0_21と_26の両方を使用する物理SPARCおよび仮想x64(VMWare)

    import java.io.IOException; 
    import java.net.InetSocketAddress; 
    import java.nio.ByteBuffer; 
    import java.nio.channels.SelectionKey; 
    import java.nio.channels.Selector; 
    import java.nio.channels.SocketChannel; 
    import java.util.HashSet; 
    import java.util.Iterator; 
    import java.util.Set; 
    
    public class NioTest3 
    { 
        public static void main(String[] args) 
        { 
         int i, tcount = 1, open = 0; 
         String[] addr = args[0].split(":"); 
         int port = Integer.parseInt(addr[1]); 
         if (args.length == 2) 
          tcount = Integer.parseInt(args[1]); 
         InetSocketAddress inetaddr = new InetSocketAddress(addr[0], port); 
         try 
         { 
          Selector selector = Selector.open(); 
          SocketChannel channel; 
          for (i = 0; i < tcount; i++) 
          { 
           channel = SocketChannel.open(); 
           channel.configureBlocking(false); 
           channel.register(selector, SelectionKey.OP_CONNECT); 
           channel.connect(inetaddr); 
          } 
          open = tcount; 
          while (open > 0) 
          { 
           int selected = selector.select(); 
           System.out.println("Selected=" + selected); 
           Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 
           while (it.hasNext()) 
           { 
            SelectionKey key = it.next(); 
            it.remove(); 
            channel = (SocketChannel)key.channel(); 
            if (key.isConnectable()) 
            { 
             System.out.println("isConnectable"); 
             if (channel.finishConnect()) 
             { 
              System.out.println(formatAddr(channel) + " connected"); 
              key.interestOps(SelectionKey.OP_WRITE); 
             } 
            } 
            else if (key.isWritable()) 
            { 
             System.out.println(formatAddr(channel) + " isWritable"); 
             String message = formatAddr(channel) + " the quick brown fox jumps over the lazy dog"; 
             ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); 
             channel.write(buffer); 
             key.interestOps(SelectionKey.OP_READ); 
            } 
            else if (key.isReadable()) 
            { 
             System.out.println(formatAddr(channel) + " isReadable"); 
             ByteBuffer buffer = ByteBuffer.allocate(1024); 
             channel.read(buffer); 
             buffer.flip(); 
             byte[] bytes = new byte[buffer.remaining()]; 
             buffer.get(bytes); 
             String message = new String(bytes); 
             System.out.println(formatAddr(channel) + " read: '" + message + "'"); 
             channel.close(); 
             open--; 
            } 
           } 
          } 
    
         } 
         catch (IOException e) 
         { 
          e.printStackTrace(); 
         } 
        } 
    
        static String formatAddr(SocketChannel channel) 
        { 
         return Integer.toString(channel.socket().getLocalPort()); 
        } 
    } 
    

    あなたは、このコマンドラインを使用して実行できます:ここで

は、問題を示し、いくつかのテストコードであるあなたはに対して実行している場合、ポートは7でなければならない

java -cp . NioTest3 <ipaddr>:<port> <num-connections> 

本物のエコーサービス。すなわち:

java -cp . NioTest3 127.0.0.1:7 5 

あなたが実行している本当のエコーサービスを受けることができない場合は、いずれかのソースがhereです。 Solarisの下echoサーバをコンパイルします。

$ cc -o echoserver echoserver.c -lsocket -lnsl 

とこのようにそれを実行します。

$ ./echoserver 8007 > out 2>&1 & 

これはbugとして日に報告されています。

答えて

1

Selector.select()場合は0を返す(タイムアウトバージョンが使用された場合、タイムアウトしませんでした)、その後:

  1. 反復を登録されたキーの上にセレクタでselector.keys().iterator()ではなくiterator.remove()と覚えておいてください)。
  2. OP_CONNECTがキーで設定されている場合は、channel.finishConnect()と呼び出して、isConnectable()が返された場合は何でもしてください。trueが返されます。たとえば、

:これはbugとして日に報告されている

if (selected == 0 && elapsed < timeout) 
{ 
    keyIter = selector.keys().iterator(); 
    while (keyIter.hasNext()) 
    { 
     key = keyIter.next(); 
     if (key.isValid()) 
     { 
      channel = (SocketChannel)key.channel(); 
      if (channel != null) 
      { 
       if ((key.interestOps() & SelectionKey.OP_CONNECT) != 0) 
       { 
        if (channel.finishConnect()) 
        { 
         key.interestOps(0); 
        } 
       } 
      } 
     } 
    } 
}   

9

あなたのバグレポートは「バグではない」と解説されています。 connect()の結果を無視しています。チャンネルがすでに接続されているため、trueの場合はOP_CONNECTは実行されません。偽を返す場合はOP_CONNECT/finishConnect()メギラ全体が必要です。だから、あなたも偽OP_CONNECTconnect()ない限り、リターンを登録し、ましてやあなたもconnect().

さらに発言と呼ばれてきた前にそれを登録しないでください。

ボンネットの下に、OP_CONNECTとOP_WRITEは一部を説明し、同じこと、ありますそれの。

これに対して単一のスレッドを使用しているため、回避方法は接続インブロックモードを実行してから、I/Oのノンブロッキングに切り替えることです。

のセレクタでチャンネルを登録した後で、select()を実行していますか?

次のように接続し、非ブロッキング処理する正しい方法は次のとおりです。あなたの拡張コードを検討した後

channel.configureBlocking(false); 
if (!channel.connect(...)) 
{ 
    channel.register(sel, SelectionKey.OP_CONNECT, ...); // ... is the attachment, or absent 
} 
// else channel is connected, maybe register for OP_READ ... 
// select() loop runs ... 
// Process the ready keys ... 
if (key.isConnectable()) 
{ 
    if (channel.finishConnect()) 
    { 
    key.interestOps(0); // or SelectionKey.OP_READ or OP_WRITE, whatever is appropriate 
    } 
} 

少数の非網羅的なコメント:チャンネルを閉じる

  1. はキーをキャンセルします。両方を行う必要はありません。

  2. 非静的なremoveInterest()メソッドが正しく実装されていません。

  3. TYPE_DEREGISTER_OBJECTもチャネルを閉じます。それがあなたが本当に意図したものかどうかはわかりません。私はそれがちょうどキーをキャンセルすべきだと思っていただろうし、チャンネルを閉じるための別の操作が必要です。

  4. あなたは、小さな方法と例外処理に乗り遅れてしまいました。 addInterest()とremoveInterest()は良い例です。彼らは例外をキャッチし、それらをログに記録し、例外が発生しなかったかのように処理を進めます。実際にはすべて1つのコード行が設定またはクリアされます。さらに、静的バージョンと非静的バージョンの両方があります。同じことが、key.cancel()、channel.close()などを呼び出すすべての小さなメソッドに当てはまります。これには何の指摘もなく、コード行をクロッキングするだけです。それはあいまいさを追加し、コードを理解しにくくします。インラインで操作する必要があり、selectループの一番下に単一のキャッチャーがあります。

  5. finishConnect()がfalseを返した場合、接続に失敗していないだけで、まだ完了していません。 例外をスローすると、接続に失敗したが返されます。

  6. OP_CONNECTとOP_READを同時に登録しています。これは意味をなさないし、問題を引き起こす可能性があります。 OP_CONNECTが起動するまでは、何も読み込めません。最初にOP_CONNECTに登録してください。

  7. 読み取りごとにByteBufferを割り当てています。これは非常に無駄です。接続の寿命は同じものを使用してください。

  8. あなたはread()の結果を無視しています。ゼロにすることができます。チャンネルを閉じる必要があるEOSを示す-1にすることができます。また、1回の読み込みでアプリケーションメッセージ全体を取得すると仮定しています。あなたはそれを仮定することはできません。これは、接続の存続期間に1つのByteBufferを使用するもう一つの理由です。

  9. あなたはwrite()の結果を無視しています。 buffer.remaining()よりも小さいかもしれません。ゼロにすることができます。

  10. NetSelectableをキーアタッチメントにすることで、これを簡単にすることができます。次に、キーのチャンネルが常にキーのアタッチメントのチャンネルと等しくなければならないため、チャンネルマップやアサーションなど、いくつかのことをなくすことができます。

  11. また、私はfinishConnect()コードをNetSelectorに移動し、connectEvent()を成功/失敗通知にするだけです。あなたはこの種のものを広げたくありません。 readEvent()で同じことを行います。つまり、NetSelectableでバッファを使用してNetSelectorで読み取り自体を行い、読み取り結果のNetSelectableにcountまたは-1または の例外を通知します。書き込み時にはDitto:チャネルが書き込み可能な場合は、NetSelectableから書き込むものを取得し、NetSelectorに書き込み、結果を通知します。通知コールバックに、次に何をするかを示す何かが返されるようにすることができます。チャネルを閉じます。

本当にこれは必要なだけ複雑な5倍であり、このバグがあることを証明しています。頭を簡潔にする。

私が働い-の周りにいるこのバグ、以下のものを使用して
+0

はい、チャンネルは 'OPEN 'に関心があります。' OP_CONNECT'が 'connect'を呼び出した後で設定されています(そうでなければ' ConnectionPendingException'が発生します)。接続をブロックすると問題が発生します。接続は特別な時間に起こり、他のスレッドのI/Oに干渉すると悪いことになります。 – trojanfoe

+0

@trojanfoe connect()の前にチャンネルを登録してOP_CONNECTに設定する必要があります。そうしないと、イベントを見逃すことがあります。 connect()を呼び出す前にConnectPendingExceptionをスローすることはできません*私は理解しません。その例外の理由はJavadocではかなり明白であり、これはそうではありません。 – EJP

+0

私はその変更を行ってLinuxでテストしましたが(動作しますが)、Solarisでこの問題は修正されません。私は間違った例外を述べた - それはNoConnectionPendingExceptionでしたが、それはもはや起こっていないので、無視してください。 – trojanfoe

関連する問題