まず、お読みいただきありがとうございます。ユーザーとしてスタックオーバーフローに参加するのはこれが初めてですが、常にそれを読んで有用な解決策を見つけました: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 では正常に動作します)。