У меня проблемы с сокетами Java в приложении типа клиент / сервер, когда приходится принимать много подключений - PullRequest
2 голосов
/ 12 апреля 2011

Прежде всего, спасибо за чтение.Это мой первый опыт работы в стеке, как у пользователя, хотя я всегда читал его и находил полезные решения: 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 мс работает нормально).

Ответы [ 2 ]

0 голосов
/ 26 апреля 2011

Два момента, я думаю, вы можете продолжить исследование. Извините за немного смутное здесь, но это то, что я думаю.

1) Под капотом на уровне tcp существует мало вещей, зависящих от платформы, которые контролируют время, необходимое для отправки / получения данных через сокет. Несогласованная задержка может быть связана с такими настройками, как tcp_syn_retries. Вам может быть интересно посмотреть здесь http://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/tcpvariables.html#AEN370

2) Расчетное время выполнения - это не только количество времени, которое потребовалось для завершения выполнения, но и время до завершения завершения, которое не обязательно произойдет немедленно, когда объект готов к завершению.

0 голосов
/ 12 апреля 2011

В сокете сервера есть очередь, в которой хранятся входящие попытки подключения. Клиент столкнется с ошибкой сброса соединения, если эта очередь заполнена. Без оператора Thread.sleep(100) все ваши клиенты пытаются подключиться относительно одновременно, в результате чего некоторые из них сталкиваются с ошибкой сброса соединения.

...