Практика использования классов Socket и ServerSocket в Java

от автора

Введение

В этой статье будет показан пример создания небольшого многопользовательского чата с помощью сокетов. Для его реализации вам понадобиться Java и Maven.

Создание структуры проекта

Структуру проекта будем создавать с помощью Maven. Для этого откройте терминал и перейдите в директорию, в которой хотите создать проект, и введите следующую команду:

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5

После ввода команды, настройте проект, введя такие данные как groupId, artifactId и т.п. После ввода этих данных у вас должно получиться что-то вроде этого:

Итоговые настройки создания проекта

Итоговые настройки создания проекта

После этого напишите символ ‘y’ и нажмите Enter. В текущей директории у вас создался проект с названием, которое вы указывали в ‘artifactId’.

Теперь нужно чучуть подшаманить в файле pom.xml. Найдите в нем тег ‘maven.compiler.release’ и измените его значение на вашу версию Java.

Смена версии Java в файле pom.xml

Смена версии Java в файле pom.xml

Затем, чтобы запустить проект, нам следует для удобство указать имя jar архива, который в дальнейшем будем запускать, и указать для maven главный файл проекта.

Указание имени jar архива

Указание имени jar архива
Указание главного класса проекта

Указание главного класса проекта

Заходим в файл App.java и изменим сгенерированный код на что-то подобное:

Приветствуем мир

Приветствуем мир

Теперь мы готовы запустить проект. Для этого соберем его в jar архив, введя следующую команду в терминале, находясь в корне проекта.

mvn package

Проект собрался, теперь мы можем его запустить, введя следующую команду:

java -jar target/example-chat.jar

После выполнения команды мы увидим успешное выполнение программы.

Начало работы

Конечное приложение после своего запуска позволит как запустить серверную часть, указав для нее порт, так и клиентскую часть, в которой будем указывать ip сервера и порт для подключения.

Начнем с того, что напишем код, который будет спрашивать у пользователя что он хочет, запустить сервер или же клиент:

package ru.example.chat;  import java.util.Scanner;  import ru.example.chat.client.MySocketClient; import ru.example.chat.server.MySocketServer;  public class App {     public static void main(String[] args) {         Scanner reader = new Scanner(System.in);                  int decision = 0;          while (decision != 3) {             System.out.print(                 "___Меню___\n\n"                 + "1. Клиент\n"                 + "2. Сервер\n"                 + "3. Выход\n"                 + "| "             );              decision = reader.nextInt();              switch (decision) {                 case 1:                     // Вызываем статичный метод start для запуска клиента                     MySocketClient.start();                     break;                 case 2:                     // Вызываем статичный метод start для запуска сервера                     MySocketServer.start();                     break;                 case 3:                     return;                 default:                 System.out.println("[INFO] Моя твоя не понимать");                     break;             }         }     } } 

Серверная часть

Рассмотрим код серверной части:

package ru.example.chat.server;  import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List;  public class MySocketServer {          private static int port;      // список пользователей, которые вошли в чат     public static List<User> users = new ArrayList<>();      private static ServerSocket serverSocket;      private static BufferedReader reader = new BufferedReader(         new InputStreamReader(System.in)     );          public static void start() {         boolean correctPort = false;          System.out.print(             "___Настройка сервера самого крутого чата___\n\n"         );          // цикл будет работать, пока пользователь не введет корректный порт         while (!correctPort) {             System.out.print("Введите порт: ");              try {                 port = Integer.parseInt(                     reader.readLine()                 );             } catch (IOException e) {                 System.out.println("[ERROR] Че-то пошло не так, давай еще раз");                  continue;             } catch (NumberFormatException e) {                 System.out.println("[ERROR] Будь добр, введи цифру");                  continue;             } catch (Exception e) {                 System.out.println("[ERROR] Ошибка");                  continue;             }              correctPort = true;         }          try {             // запускаем сервер на указанном порту             serverSocket = new ServerSocket(port);              System.out.println("___Стартовал сервер самого крутого чата___\n");              while (true) {                 // ожидаем подключение нового пользователя                 Socket newUser = serverSocket.accept();                  System.out.println(                     "[INFO] Новое подключение\n"                     + "[INFO] IP: " + newUser.getInetAddress()                 );                      // добавляем нового пользователя в наш список пользователей                 // создавая для него отдельный поток (см. конструктор класса User)                 // который будет слушать сообщения от него                 users.add(                     new User(newUser)                 );             }         } catch (IOException e) {             System.out.println("[ERROR] Произошла ошибка при старте сервера");              return;         } catch (Exception e) {             System.out.println("[ERROR] Ошибка");              return;         }     } } 

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

Серверная логика

Серверная логика

Клиентская часть

Рассмотрим код клиентской части:

package ru.example.chat.client;  import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException;  public class MySocketClient {      private static String domain;      private static int port;      private static BufferedReader reader = new BufferedReader(         new InputStreamReader(System.in)     );      public static void start() {         boolean correctPort = false, correctDomain = false;          System.out.print(             "___Настройка клиента самого крутого чата___\n\n"         );          // цикл будет работать, пока пользователь не введет корректный домен         while (!correctDomain) {             System.out.print(                 "Введите домен сервера: "             );              try {                 domain = reader.readLine();             } catch (IOException e) {                 System.out.println("[ERROR] Че-то пошло не так, давай еще раз");                  continue;             } catch (Exception e) {                 System.out.println("[ERROR] Ошибка");                  continue;             }              correctDomain = true;         }          // цикл будет работать, пока пользователь не введет корректный порт         while (!correctPort) {             System.out.print("Введите порт: ");              try {                 port = Integer.parseInt(                     reader.readLine()                 );             } catch (IOException e) {                 System.out.println("[ERROR] Че-то пошло не так, давай еще раз");                  continue;             } catch (NumberFormatException e) {                 System.out.println("[ERROR] Будь добр, введи цифру");                  continue;             } catch (Exception e) {                 System.out.println("[ERROR] Ошибка");                  continue;             }              correctPort = true;         }          try {             // пробуем подключиться к серверу, создавая экземляр класса Socket             // который передаем в конструктор самописного класса ClientSocket             ClientSocket clientSocket = new ClientSocket(                 new Socket(domain, port)             );              String message = "";              // цикл работает, пока сообщение, введенное пользователем             // не равно строке #exit             while (!message.equals("#exit")) {                 System.out.print("| ");                  // получаем сообщение от пользователя из консоли                 message = reader.readLine();                  // отправка сообщения на сервер                 clientSocket.sendMessage(message);             }              System.out.println("[INFO] Закрытие чата...");              clientSocket.closeClient();         } catch (UnknownHostException e) {             System.out.println("[ERROR] Неизвестный хост");         } catch (IOException e) {             System.out.println("[ERROR] Че-то пошло не так");         } catch (Exception e) {             System.out.println("[ERROR] Ошибка");         }     } } 

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

package ru.example.chat.client;  import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket;  public class ClientSocket {     private Socket socket;      private BufferedReader in;      private BufferedWriter out;      // самописный класс, который занимается прослушкой сообщений с сервера     private MessageListener messageListener;      public ClientSocket(Socket socket) throws IOException {         this.socket = socket;          // получаем input stream нашего сокета         // для получения данных с сервера         this.in = new BufferedReader(             new InputStreamReader(                 socket.getInputStream()             )         );          // получаем output stream нашего сокета         // для отправки данных на сервер         this.out = new BufferedWriter(             new OutputStreamWriter(                 socket.getOutputStream()             )         );          // получаем экземпляр нашего самописного класса         // передавая ему ссылку на текущий объект         this.messageListener = new MessageListener(             this         );     }      // метод занимается отправкой данных на сервер     public void sendMessage(String message) {         try {             this.out.write(message + "\n");              this.out.flush();         } catch (IOException e) {             System.out.println("[ERROR] Ошибка при отправке сообщения");         } catch (Exception e) {             System.out.println("[ERROR] Ошибка");         }     }      // метод занимается получением данных с сервера     public String getMessage() {         try {             return this.in.readLine();         } catch (IOException e) {             System.out.println("[ERROR] Ошибка при получении сообщения");         } catch (Exception e) {             System.out.println("[ERROR] Ошибка");         }          return "";     }      public void closeClient() {         try {             this.socket.close();              this.in.close();              this.out.close();              this.messageListener.stopListener();         } catch (IOException e) {             System.out.println("[ERROR] Ошибка");         } catch (Exception e) {             System.out.println("[ERROR] Ошибка");         }     } } 

Главный поток постоянно занят тем, что ждет сообщение пользователя из консоли, после его получения отправляет сообщение на сервер. Процесс создания объектов на клиенте можно представить так:

Процесс создания объектов на клиенте

Процесс создания объектов на клиенте

Пример работы

После запуска приложения нам отрисуется меню, в котором мы выберем 2-й пункт для запуска сервера, затем введем порт, например 8080 после чего увидим сообщение об успешном старте сервера:

Запуск сервера чата

Запуск сервера чата

Теперь откроем новую сессию терминала и после запуска приложения выберем 1-й пункт для старта клиента, после чего нам будет предложено ввести домен сервера, в нашем случае localhost и порт 8080:

Запуск клиента чата

Запуск клиента чата

В тоже время на серверной стороне зарегистрировано новое подключение:

Логи на серверной стороне

Логи на серверной стороне

Для начала общения в чате достаточно просто ввести свое имя и начать общение.

Пример переписки

Пример переписки

Заключение

Надеюсь у меня получилось показать, как реализовать клиент-серверное приложение на Java с помощью сокетов.

Исходный код приложения можете посмотреть на GitHub.

Ссылка на GitHub — https://github.com/ZhadanD/example-chat


ссылка на оригинал статьи https://habr.com/ru/articles/883076/