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

в 5:16, , рубрики: java, ServerSocket, socket
Практика использования классов Socket и ServerSocket в Java - 1

Введение

В этой статье будет показан пример создания небольшого многопользовательского чата с помощью сокетов. Для его реализации вам понадобиться 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(
                "___Меню___nn"
                + "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(
            "___Настройка сервера самого крутого чата___nn"
        );

        // цикл будет работать, пока пользователь не введет корректный порт
        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(
            "___Настройка клиента самого крутого чата___nn"
        );

        // цикл будет работать, пока пользователь не введет корректный домен
        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

Автор: DVZH7

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js