
Введение
В этой статье будет показан пример создания небольшого многопользовательского чата с помощью сокетов. Для его реализации вам понадобиться 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.
Затем, чтобы запустить проект, нам следует для удобство указать имя jar архива, который в дальнейшем будем запускать, и указать для maven главный файл проекта.
Заходим в файл 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/
Добавить комментарий