Прежде всего, спасибо за чтение.Это мой первый опыт работы в стеке, как у пользователя, хотя я всегда читал его и находил полезные решения: 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, для каждого клиента создается один экземпляр:
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 мс (где х - это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)", Я получаю некоторые исключения сброса соединения (7 или 8 соединений сбрасываются из 400, более или менее), но я думаю, что понимаю, почему это происходит: когда serverSocket.accept () принимает соединение, очень небольшое количество времени должно бытьпотратил, чтобы достичь serverSocket.accept () снова.В течение этого времени сервер не может принимать соединения.Может ли это быть из-за этого?Если нет, то почему?Было бы редко 400 подключений, приходящих на мой сервер точно в одно и то же время, но это могло произойти.Без "Thread.sleep (100)" проблемы с синхронизацией еще хуже.
Заранее спасибо!
ОБНОВЛЕНО:
Как глупо, я проверял этов localhost ... и это не дает никаких проблем!С и без "Thread.sleep (100)", не имеет значения, он работает отлично!Зачем!Итак, как я вижу, моя теория о том, почему сбрасывается сброс соединения, неверна.Это делает вещи еще более странными!Я надеюсь, что кто-нибудь может мне помочь ... Еще раз спасибо!:)
ОБНОВЛЕНО (2):
Я обнаружил явно различное поведение в разных операционных системах.Я обычно разрабатываю в Linux, и поведение, которое я объяснил, касалось того, что происходило в моей Ubuntu 10.10.В Windows 7, когда я делаю паузу в 100 мс между соединениями, все нормально, и все потоки светятся быстро, никому не требуется больше 150 мс или около того (никаких проблем с медленным соединением!).Это не то, что происходит в Linux.Однако, когда я удаляю «Thread.sleep (100)», вместо того, чтобы только некоторые соединения получали исключение сброса соединения, все они терпят неудачу и выдают исключение (в Linux только некоторые из них, 6 или около того из 400терпели неудачу).
Уф!Я только что узнал, что не только ОС, среда JVM также имеет небольшое влияние!Ничего страшного, но заслуживает внимания.Я использовал OpenJDK в Linux, и теперь, с Oracle JDK, я вижу, что, уменьшая время ожидания между соединениями, он начинает отказывать раньше (с 50 мс OpenJDK работает нормально, исключений не выдается, но с Oracle довольномного с временем ожидания 50 мс, а с 100 мс работает нормально).