Java: создание простого масштабирования сервера! Часть 2

В предыдущей статье мы подробно рассмотрели и создали сервер, который позволял пользователю запускать его и создавать к нему соединение. Затем наш сервер будет выводить все, что ввел пользователь. В этой статье мы будем использовать многопоточность, чтобы гарантировать, что наш сервер может обрабатывать несколько подключений к нему. На самом деле так работают настоящие серверы. Каждый раз, когда добавляется новое соединение, он порождает новый поток в этом порту для обработки входящих запросов. Эта статья будет короткой, так как она просто изменяет наш старый код.

1. Добавление многопоточности

1.1 Установка нашего лимита подключений

В нашем новом connectToServerMultiple() методе (да, я плохо разбираюсь в именовании методов). Начнем с того, что укажем максимальное количество подключений, которое должен создать наш сервер. В EC-1 ниже мы просто установили для него значение 3, однако реальный сервер должен иметь возможность обрабатывать более тысячи соединений. Помните, что каждое новое соединение будет влиять на память + ЦП на вашем сервере. Мы также устанавливаем для нашего порта произвольный 8187, как показано в Пример кода 1 (EC-1) ниже.

Как и в нашей предыдущей статье, мы создаем сокет и помещаем его в try-catch, чтобы избежать ошибок.

EC-1: Stating our max connections
public static void connectToServerMultiple() {
     int max_con = 3; //indicate maximum number of connections.
     int port = 8187;

     //Create multiple ServerSockets to connect to a server
    ServerSocket serverSocket = null;
    try {
        serverSocket = new ServerSocket(port);
    } catch (IOException e) {
        e.printStackTrace();
    }

1.2 Создание наших потоков

Мы указали необходимое количество подключений, все, что нам нужно сделать сейчас, это создать цикл for, который создаст новый поток, который выполняет следующие

  1. Создайте новый сокет для каждого подключения
  2. Слушает соединение.
EC-2 - Creating the ForLoop & Threads
//Create a for loop that creates max_con number of threads
for (int i = 0; i < max_con; i++) {
    ServerSocket finalServerSocket = serverSocket;
    //Create the thread
    Runnable runnable = () -> {
        try {
            Socket listenerSocket = finalServerSocket.accept();
            InputStream inputToServer = listenerSocket.getInputStream();
            OutputStream outputFromServer = listenerSocket.getOutputStream();

2. Добавление функциональности на наш сервер

В нашей предыдущей статье наш сервер просто принимал вводимые пользователем данные и выводил именно то, что ему было сказано. В этом примере мы сделаем наш сервер немного более полезным, позволив ему принимать любые числа и умножив их на 10.

Большая часть кода аналогична предыдущей статье, мы создаем Scanner , который принимает входные данные нашего клиента и отправляет их на сервер, а также мы создаем PrintWriter, который принимает выходные данные с сервера и отправляет их клиенту.

EC-3 | Adding functionality to the server
                  //...continues from EC-2
Scanner input = new Scanner(inputToServer, "UTF-8");
    PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputFromServer, "UTF-8"), true);

    printWriter.println("Welcome Minion! I'll multiply the number you give by 10.\n Type -19 to quit");
    printWriter.println("I'm Running on Thread: " + Thread.currentThread().getName());

    boolean done = false;
    while(!done && input.hasNextLine()) {
        String line = input.nextLine();
        double inputDouble = 0;

        try{
            inputDouble = Double.parseDouble(line);
            if(line == null || inputDouble == -19) {
                done = true;
                printWriter.println("Sad to see you leave! ... Closing Connection!");
                listenerSocket.close(); //close socket
            }
            printWriter.println("Your answer is: " + inputDouble * 10);
        }catch (Exception ex) {
            printWriter.println(":{( - Only insert numbers!!!");
        }
    }

} catch (IOException e) {
    e.printStackTrace();
}
};
//Execute the runnables!
Executors.newCachedThreadPool().execute(runnable);

Если пользователь вводит магическое число -19, это сигнализирует серверу, что пользователь хочет выйти, и закрывает сокет, разрывая соединение.

3. Собираем все вместе

Итак, мы добавили на наш сервер параллелизм с помощью цикла for, а также сделали наш сервер немного более полезным. Пришло время просмотреть весь код и запустить его! Скопируйте приведенную ниже суть в свою любимую Java IDE и запустите ее.

В вашем терминале используйте telnet, программу, которую мы загрузили в прошлый раз, и направьте ее на ваш работающий сервер. У вас должен получиться следующий результат.

На рисунке 1 выше я создал 4 подключения к работающему серверу через порт 8187. Обратите внимание, что на самом деле должны подключаться только 3, поскольку мы установили наш предел 3. Соединение в правом нижнем углу (BR) останавливается, а в верхнем левом (TL). , верхний правый (TR), нижний левый (BL) все соединяются. Также интересно то, что код распечатывает, в каком потоке запущен сокет. Это говорит о том, что наша многопоточность работает в полную силу. Что касается функциональности нашего сервера, TL, TR, BL, похоже, работают, как ожидалось, умножая ввод на 10. На TR мы закрываем соединение, вводя наш пресловутый -19, и сокет закрывается. Также обратите внимание, как только TR завершает свое соединение, BR не сразу занимает пространство этого соединения.

Поиграйте с кодом и различными сценариями и получайте удовольствие!

В следующей статье мы развернем наш сервер на реальном сервере в Google Cloud Compute Engine и подключимся к нему оттуда.

Спасибо за прочтение!

Twitter - @martinomburajr