2

まず、お読みいただきありがとうございます。ユーザーとしてスタックオーバーフローに参加するのはこれが初めてですが、常にそれを読んで有用な解決策を見つけました:D。ところで、説明が不十分で申し訳ありませんが、私の英語はあまり上手ではありません。

私のソケットベースのプログラムは奇妙な動作をしており、いくつかのパフォーマンスの問題があります。クライアントとサーバーは、シリアル化されたオブジェクトをオブジェクトの入力ストリームと出力ストリームにマルチスレッドで読み書きすることによって、相互に通信します。コードの基本をお見せしましょう。読みやすいように簡略化しました。たとえば、完全な例外処理は意図的に省略されています。サーバーは次のように機能します。

サーバ:

// (...)

public void serve() {
    if (serverSocket == null) {
        try {
            serverSocket = (SSLServerSocket) SSLServerSocketFactory
                                    .getDefault().createServerSocket(port);
            serving = true;
            System.out.println("Waiting for clients...");
            while (serving) {
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                System.out.println("Client accepted.");
                //LjServerThread class is below
                new LjServerThread(clientSocket).start();
            }
        } catch (Exception e) {
            // Exception handling code (...)
        }
    }
}

public void stop() {
    serving = false;
    serverSocket = null;
}

public boolean isServing() {
    return serving;
}

LjServerThread クラス、クライアントごとに 1 つのインスタンスが作成されます。

private SSLSocket clientSocket;
private String IP;
private long startTime;

public LjServerThread(SSLSocket clientSocket) {
        this.clientSocket = clientSocket;
        startTime = System.currentTimeMillis();
        this.IP = clientSocket.getInetAddress().getHostAddress();
}

public synchronized String getClientAddress() {
    return IP;
}

@Override
public void run() {
    ObjectInputStream in = null;
    ObjectOutputStream out = null;
    //This is my protocol handling object, and as you will see below,
    //it works processing the object received and returning another as response.
    LjProtocol protocol = new LjProtocol();
    try {
        try {
            in = new ObjectInputStream(new BufferedInputStream(
                                     clientSocket.getInputStream()));
            out = new ObjectOutputStream(new BufferedOutputStream(
                                    clientSocket.getOutputStream()));
            out.flush();
        } catch (Exception ex) {
            // Exception handling code (...)
        }
        LjPacket output;
        while (true) {
            output = protocol.processMessage((LjPacket) in.readObject());
            // When the object received is the finish mark, 
            // protocol.processMessage()object returns null.
            if (output == null) {
                break;
            }
            out.writeObject(output);
            out.flush();
            out.reset();
        }
        System.out.println("Client " + IP + " finished successfully.");
    } catch (Exception ex) {
        // Exception handling code (...)
    } finally {
        try {
            out.close();
            in.close();
            clientSocket.close();
        } catch (Exception ex) {
            // Exception handling code (...)
        } finally {
            long stopTime = System.currentTimeMillis();
            long runTime = stopTime - startTime;
            System.out.println("Run time: " + runTime);
        }
    }
}

そして、クライアントは次のようになります。

    private SSLSocket socket;

    @Override
    public void run() {
        LjProtocol protocol = new LjProtocol();
        try {
            socket = (SSLSocket) SSLSocketFactory.getDefault()
                     .createSocket(InetAddress.getByName("here-goes-hostIP"),
                                                                       4444);
        } catch (Exception ex) {

        }
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try {
        out = new ObjectOutputStream(new BufferedOutputStream(
                                         socket.getOutputStream()));
        out.flush();
        in = new ObjectInputStream(new BufferedInputStream(
                                          socket.getInputStream()));
        LjPacket output;
        // As the client is which starts the connection, it sends the first 
        //object.
        out.writeObject(/* First object */);
        out.flush();
        while (true) {
            output = protocol.processMessage((LjPacket) in.readObject());
            out.writeObject(output);
            out.flush();
            out.reset();
        }
        } catch (EOFException ex) {
            // If all goes OK, when server disconnects EOF should happen.
            System.out.println("suceed!");
        } catch (Exception ex) {
            // (...)
        } finally {
            try {
                // FIRST STRANGE BEHAVIOUR:
                // I have to comment the "out.close()" line, else, Exception is
                // thrown ALWAYS.
                out.close();
                in.close();
                socket.close();
            } catch (Exception ex) {
                System.out.println("This shouldn't happen!");
            }
        }
    }
}

ご覧のとおり、サーバー側で受け入れられたクライアントを処理する LjServerThread クラスは、所要時間を測定します... 通常、75 ~ 120 ミリ秒かかります (x は IP です)。

  • クライアント x は正常に終了しました。
  • 実行時間: 82
  • クライアント x は正常に終了しました。
  • 実行時間: 80
  • クライアント x は正常に終了しました。
  • 実行時間: 112
  • クライアント x は正常に終了しました。
  • 実行時間: 88
  • クライアント x は正常に終了しました。
  • 実行時間: 90
  • クライアント x は正常に終了しました。
  • 実行時間: 84

しかし、突然、予測可能なパターンはありません (少なくとも私にとっては):

  • クライアント x は正常に終了しました。
  • 実行時間: 15426

時には25秒に達する!時折、スレッドの小さなグループが少し遅くなることがありますが、それはあまり心配していません:

  • クライアント x は正常に終了しました。
  • 実行時間: 239
  • クライアント x は正常に終了しました。
  • 実行時間: 243

なぜこうなった?これはおそらく、サーバーとクライアントが同じ IP を持つ同じマシンにあるためでしょうか? (このテストを行うために、サーバーとクライアントを同じマシンで実行しますが、それらはインターネット経由で接続し、パブリック IP を使用します)。

これは私がこれをテストする方法です。main() で次のようにサーバーに要求します。

    for (int i = 0; i < 400; i++) {
        try {
            new LjClientThread().start();
            Thread.sleep(100);
        } catch (Exception ex) {
            // (...)
        }
    }

「Thread.sleep(100)」を使用せずにループで実行すると、接続リセットの例外が発生します (400 のうち 7 または 8 の接続が多かれ少なかれリセットされます)。 () が接続を受け入れると、serverSocket.accept() に再び到達するまでに非常に短い時間がかかります。その間、サーバーは接続を受け入れることができません。そのせいでしょうか。そうでない場合、なぜですか?サーバーにまったく同時に 400 の接続が到着することはめったにありませんが、発生する可能性があります。「Thread.sleep(100)」がないと、タイミングの問題も悪化します。

前もって感謝します!


更新しました:

なんてばかだ、私はローカルホストでそれをテストしました...そしてそれは何の問題も与えません! 「Thread.sleep(100)」の有無にかかわらず、問題なく動作します。どうして!したがって、ご覧のとおり、接続のリセットがスローされる理由についての私の理論は正しくありません。これは物事をさらに奇妙にします!誰かが私を助けてくれることを願っています...ありがとう!:)


更新 (2):

さまざまなオペレーティング システムで、まったく異なる動作が見られることを発見しました。私は通常、Linux で開発を行っており、説明した動作は、私の Ubuntu 10.10. Windows 7 では、接続間で 100 ミリ秒一時停止すると、すべて問題なく、すべてのスレッドが高速で点灯し、150 ミリ秒以上かかることはありません (低速接続の問題はありません!)。これは Linux で起こっていることではありません。ただし、「Thread.sleep(100)」を削除すると、一部の接続のみが接続リセット例外を取得するのではなく、すべてが失敗して例外がスローされます (Linux ではそれらの一部のみ、400 のうち 6 程度)。失敗していました)。

ふぅ!OS だけでなく、JVM 環境にも少し影響があることがわかりました。大したことではありませんが、注目に値します。私は Linux で OpenJDK を使用していましたが、現在、Oracle JDK では、接続間のスリープ時間を短縮すると、以前に失敗し始めることがわかります (50 ミリ秒で OpenJDK は正常に動作し、例外はスローされませんが、Oracle のものではかなり多くは 50ms のスリープ時間で動作しますが、100ms では正常に動作します)。

4

2 に答える 2

0

サーバーソケットには、着信接続試行を保持するキューがあります。そのキューがいっぱいになると、クライアントで接続リセット エラーが発生します。このステートメントがないとThread.sleep(100)、すべてのクライアントが比較的同時に接続しようとするため、一部のクライアントで接続リセット エラーが発生します。

于 2011-04-12T19:25:57.867 に答える
0

さらに調査を検討してもよいと思われる 2 つの点があります。曖昧で申し訳ありませんが、こんな感じです。

1) 内部では、tcp レベルでは、ソケットを介してデータを送受信するのにかかる時間を制御するプラットフォームに依存するものはほとんどありません。一貫性のない遅延は、tcp_syn_retries などの設定が原因である可能性があります。ここを見て興味があるかもしれませんhttp://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/tcpvariables.html#AEN370

2)計算された実行時間は、実行を完了するのにかかった時間だけでなく、オブジェクトがファイナライズの準備ができたときにすぐに発生することが保証されていないファイナライズが完了するまでの時間を含みます。

于 2011-04-26T04:44:12.727 に答える