3

単純な nio ベースの Java サーバーがあるとします。例(簡略化されたコード):

while (!self.isInterrupted()) {
  if (selector.select() <= 0) {
    continue;
  }

  Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    iterator.remove();
    SelectableChannel channel = key.channel();

    if (key.isValid() && key.isAcceptable()) {
      SocketChannel client = ((ServerSocketChannel) channel).accept();
      if (client != null) {
        client.configureBlocking(false);
        client.register(selector, SelectionKey.OP_READ);
      }
    } else if (key.isValid() && key.isReadable()) {
      channel.read(buffer);
      channel.close();
    }
  }
}

したがって、これは単純なシングル スレッドのノンブロッキング サーバーです。

問題は次のコードにあります。

channel.read(buffer);
channel.close();

同じスレッド(接続と読み取りデータを受け入れるスレッド)でチャネルを閉じると、すべて正常に動作します。しかし、別のスレッドで接続が閉じられたときに問題が発生しました。例えば

((SocketChannel) channel).read(buffer);
executor.execute(new Runnable() {
   public void run() {
     channel.close();
   }
});

このシナリオでは、ソケットがサーバーで TIME_WAIT 状態、クライアントで ESTABLISHED 状態になりました。そのため、接続が正常に閉じられません。何が問題なのですか?私が逃したものは何ですか?

4

5 に答える 5

1

TIME_WAITは、OSがソケットを閉じる要求を受信したが、クライアント側からの通信が遅れる可能性があるのを待つことを意味します。クライアントは、それがまだ確立されていると考えているため、明らかにRSTを取得しませんでした。それはJavaのものではなく、OSです。RSTは、何らかの理由でOSによって明らかに遅延されています。

別のスレッドで閉じたときにのみ発生するのはなぜですか?誰が知っていますか?OSは、別のスレッドでのクローズは元のスレッドの終了などを待つ必要があると考えている可能性があります。私が言ったように、それはOSの内部メカニズムです。

于 2009-06-05T16:53:39.857 に答える
0

クローズが例外をスローしない限り、なぜそれが違いを生むのかわかりません。もしそうなら、あなたは例外を見ないでしょう。キャッチ(Throwable t)にクローズを入れて、例外を印刷することをお勧めします(1つあると仮定して)

于 2009-05-09T14:11:30.607 に答える
0

あなたの例には大きな問題があります。

Java NIO では、accept() を実行するスレッドは、 accept ( ) のみを実行する必要があります。おもちゃの例は別として、予想される接続数が多いため、おそらくJava NIOを使用しています。選択と同じスレッドで読み取りを行うことを考えても、保留中の受け入れられていない選択は、接続が確立されるのを待ってタイムアウトします。この 1 つのオーバーロードされたスレッドが接続を受け入れるようになるまでに、いずれかの側の OS があきらめて、accept() が失敗します。

選択スレッドでは絶対最小限のことだけを行います。これ以上、最小限のことだけを行うまで、コードを書き直すだけです。

【コメントへのお返事】

おもちゃの例でのみ、メインスレッドで読み取りを処理する必要があります。

処理してみてください:

  • 300 回以上の同時接続試行。
  • 確立された各接続は、1 つのサーバーに 24K バイトを送信します。つまり、小さな Web ページ、小さな .jpg です。
  • 各接続を少し遅くします (接続がダイヤルアップ経由で確立されているか、ネットワークのエラー/再試行率が高い)。そのため、TCP/IP ACK は理想よりも長くかかります (制御 OS レベルの問題から)。
  • いくつかのテスト接続を行い、1 ミリ秒ごとに 1 バイトを送信します。(これは、独自の高負荷状態にあるクライアントをシミュレートするため、非常に遅い速度でデータを生成します。) スレッドは、24K バイトを処理する場合とほぼ同じ量の労力を 1 バイトの処理に費やさなければなりません。
  • いくつかの接続を警告なしで切断します (接続が失われる問題)。

実際問題として、試行中のマシンが接続を切断する前に、500ms ~ 1500ms 以内に接続を確立する必要があります。

これらすべての問題の結果として、単一のスレッドは、相手側のマシンが接続の試行をあきらめる前に、十分な速さですべての接続をセットアップすることができなくなります。読み取りは別のスレッドにある必要があります。限目。

【ポイント】 はっきり言い忘れていました。ただし、読み取りを行うスレッドには独自のセレクターがあります。接続を確立するために使用されるセレクターは、新しいデータをリッスンするために使用しないでください。

追加 (ストリームを読み取るための Java 呼び出し中に I/O が実際には発生しないという Gnarly の主張に応えて。

各レイヤーには定義済みのバッファー サイズがあります。そのバッファがいっぱいになると、IO は停止します。たとえば、TCP/IP バッファには、接続ごとに 8K ~ 64K のバッファがあります。TCP/IP バッファがいっぱいになると、受信側コンピュータは送信側コンピュータに停止するように指示します。受信側のコンピューターがバッファリングされたバイトを十分な速度で処理しない場合、送信側のコンピューターは接続を切断します。

受信側のコンピュータがバッファリングされたバイトを処理している場合、送信側は引き続きバイトをストリーミングしますが、java io read 呼び出しが行われています。

さらに、到着する最初のバイトがセレクターで「読み取り可能なバイト」をトリガーすることを認識してください。何人が到着したかについての保証はありません。

Java コードで定義されているバッファ サイズは、OS のバッファ サイズとは関係ありません。

于 2009-06-12T08:44:44.170 に答える
0

ここで述べた問題と関係があるかもしれません。それが本当にBSD / OS X の poll() メソッドの動作である場合、運が悪いと思います。

私が理解しているように、BSD / OS Xのバグのため、このコードを移植不可としてマークすると思います。

于 2009-05-22T12:01:07.320 に答える