🎯 Цель работы

Освоить принципы прямого peer-to-peer (P2P) взаимодействия, работу с UDP для передачи аудиоданных, а также реализовать автоматическое обнаружение узлов в локальной сети с помощью multicast.

📋 Задание

  1. Реализовать P2P-приложение для голосовых звонков, в котором узлы соединяются напрямую.

через прямое указание IP-адреса и порта собеседника

  1. Реализовать простой текстовый протокол сигнализации по TCP для управления вызовом (автоматическое обнаружение других узлов в локальной сети)

🏗️ Теория

P2P (Peer-to-Peer, «равный-равному») — это сетевая архитектура, в которой каждый участник (узел, peer) обладает равными правами и может выступать одновременно и как клиент (получатель данных), и как сервер (отправитель данных).

В отличие от клиент-серверной модели, в P2P-сети

Узел А (инициатор)                  Узел Б (получатель)
       │                                    │
       │ ◄─── 1. LanDiscovery (HELLO) ────► │
       │ (оба знают IP и порты друг друга)   │
       │                                    │
       │ ──── 2. TCP-соединение ───────────►│
       │ (порт Б известен из HELLO)          │
       │                                    │
       │ ◄─── 3. Обмен командами (TCP) ────►│
       │    - CALL_START                    │
       │    - MY_UDP_PORT                   │
       │    - CALL_ACCEPTED                 │
       │                                    │
       │ ──── 4. UDP-аудиопоток ───────────►│
       │ (на согласованные порты)            │
       │ ◄─── 5. UDP-аудиопоток ────────────│
       │                                    │

LanDiscovery (Local Area Network Discovery) — это механизм автоматического обнаружения других узлов в одной локальной сети без использования центрального сервера.

Чтобы решить проблему - Как узлу А узнать IP-адрес и порт узла Б, если Нет центрального сервера, который знает всех и Пользователь не хочет вводить адрес вручную каждый раз.

LanDiscovery позволяет узлам самостоятельно объявлять о своём присутствии и слушать объявления других.

  1. Все узлы заранее договариваются об адресе multicast-группы и порте.

  2. При запуске каждый узел создаёт MulticastSocket и присоединяется к группе.

  3. Каждые 3–5 секунд узел отправляет в группу короткое сообщение HELLO (анонсирует себя)

  4. Узел в отдельном потоке постоянно принимает пакеты из группы (Прослушивание )

  5. Раз в несколько секунд узел проверяет, когда в последний раз приходило сообщение от каждого известного узла (Heartbeat (проверка активности))

Если прошло меньше 15 секунд — узел считается онлайн, иначе считается офлайн и удаляется из списка.

Узел А (192.168.1.10)                Узел Б (192.168.1.20)                Узел В (192.168.1.30)
       │                                      │                                      │
       │ ──────────── HELLO (А) ─────────────►│                                      │
       │ (multicast: 230.0.0.1:8888)          │                                      │
       │                                      │ ──────────── HELLO (Б) ─────────────►│
       │                                      │ (multicast: 230.0.0.1:8888)          │
       │ ◄──────────── HELLO (Б) ─────────────│                                      │
       │                                      │                                      │
       │ ◄──────────────────── HELLO (В) ──────────────────────────────────────────│
       │                                                                             │
       │ ──────────────────────────────────── HELLO (А) ────────────────────────────►│
       │                                                                             │
       │            Через несколько секунд все знают всех                            │

📝 Указания по выполнению

Задача 1. Реализовать приложение, в котором пользователь вручную вводит IP-адрес и порт собеседника для установления звонка

Шаг 1.1. Создание P2P-узла

Каждый экземпляр приложения должен выполнять две роли: ждать входящего TCP-соединения (как сервер) и одновременно иметь возможность инициировать исходящее соединение (как клиент).

Важно: Для этого используйте отдельные потоки. Например, один поток постоянно слушает ServerSocket на выбранном порту. Главный поток (или GUI) отвечает за инициирование исходящего соединения.

Шаг 1.2. Реализация TCP-канала сигнализации

Создайте простой текстовый протокол для управления звонком. Все команды передаются по установленному TCP-соединению.

CALL_START – сигнал о начале звонка (после этого стороны начинают аудиопоток).

CALL_END – сигнал о завершении звонка.

MESSAGE Текст – для отправки текстового сообщения (опционально).

Шаг 1.3. Реализация UDP-аудиопотока

Захват звука: Используйте TargetDataLine из javax.sound.sampled.

Отправка: Захваченные байты (PCM) упаковывайте в DatagramPacket и отправляйте на IP-адрес и UDP-порт собеседника.

Приём: В отдельном потоке слушайте DatagramSocket на выбранном порту. Полученные байты передавайте в SourceDataLine для воспроизведения.

Синхронизация: UDP-порт для аудио должен быть согласован между собеседниками. Можно использовать тот же порт, что и для TCP, или договориться о другом (например, TCP-порт + 1).

Шаг 1.4. Создание простого GUI (консольного или JavaFX)

Поля для ввода: Свой ник, Свой TCP/UDP порт, IP собеседника, Порт собеседника.

Кнопки: Запустить ожидание, Позвонить, Завершить звонок.

Текстовое поле для статуса (например, “Ожидание вызова…”, “Разговор идёт”).

Сценарий работы (Этап 1):

Студент А запускает приложение, указывает свой ник и порт (например, 5000), нажимает Запустить ожидание.

Студент Б делает то же самое (ник: “Б”, порт: 5001).

Студент А вводит IP и порт студента Б (например, 192.168.1.50:5001) и нажимает Позвонить.

Приложение А инициирует TCP-соединение с Б и отправляет команду CALL_START.

Приложения обмениваются информацией о своих UDP-портах (можно отправить в том же сообщении) и начинают отправлять аудиоданные друг другу.

Нажатие Завершить звонок отправляет команду CALL_END, и аудиопотоки останавливаются.

Задача 2. Расширить приложение, добавив механизм multicast-обнаружения и GUI-список онлайн-пользователей.

Шаг 2.1. Реализация механизма Announce/Discovery

Объявление о себе: Каждые 3-5 секунд ваше приложение должно отправлять multicast-сообщение в группу (например, 230.0.0.1, порт 8888). Сообщение может содержать: ник пользователя, его IP-адрес, TCP-порт для сигнализации.

Пример сообщения: HELLO Alice 192.168.1.10 5000.

Прослушивание: Ваше приложение должно постоянно слушать эту же multicast-группу и порт. При получении HELLO-сообщения от другого узла, оно добавляет информацию о нём в локальный список “Обнаруженные пользователи”.

Heartbeat (пульс): Если от какого-то узла давно (например, 15 секунд) не приходило HELLO-сообщений, он считается отключившимся и удаляется из списка.

Шаг 2.2. Обновление GUI

Добавьте в интерфейс список (например, ListView) “Доступные для звонка”. Он должен динамически обновляться на основе данных, полученных через multicast.

Тестирование приложения

Локальное тестированиe

Запустите два экземпляра приложения на одном компьютере (разные порты). Убедитесь, что они находят друг друга и звонок работает.

Сетевое тестирование

Запустите приложения на двух разных компьютерах в одной сети.

Тестирование инициализации

Каждый студент указывает свой ник.

  1. Приложения начинают multicast-рассылку своих HELLO-сообщений.

Через несколько секунд в GUI каждого студента появляется имя другого студента в списке “Доступные”.

Студент А кликает по нику “Б” в списке и нажимает кнопку Позвонить.

  1. При повторном запуске Приложение использует сохранённые IP и порт “Б” для установки TCP-соединения и инициации звонка.

  2. При повторном запуске при отсутсвии запущенного приложения Б приложение А указывает ник как отсутсвующий

При новом запуске Б, состояние изменяется.

+1 балл за индивидуальный вариант + 1 балл за дедлайн**

Индивидуальные варианты

Вариант пояснение
1 Push-to-Talk (Кнопка говорить) Звук передаётся только пока зажата специальная кнопка. Реализовать в GUI и показать его работу.
2 Групповой звонок (конференция) Поддержка звонка с 3 и более участниками. Необходимо решить проблему микширования или маршрутизации аудиопотоков (например, через один из узлов).
3 Запись разговора Добавить возможность записи разговора (обеих сторон) в файл формата .wav.
4 Индикатор активности (VU-метр) Визуализировать уровень громкости входящего и исходящего аудиопотока (например, с помощью ProgressBar).
5 Переключение кодека Реализовать простое сжатие PCM-данных (например, упаковка 16-битных сэмплов в 8-битные) для экономии трафика.
6 Эхо-тест (самопроверка) Сделать режим, при котором аудиопоток зацикливается локально, чтобы пользователь мог проверить работу микрофона и динамиков.
7 Автоматический выбор порта Приложение само находит свободный порт вместо фиксированного. GUI показывает выбранный порт.
8 Статус “онлайн/оффлайн” Добавить возможность менять статус (доступен / не беспокоить / отошёл). Статус передаётся в HELLO-сообщениях и отображается в списке контактов.
9 История вызовов Локальное сохранение истории входящих и исходящих вызовов (кто, когда, длительность). Выводить в отдельном окне.
10 Защита от случайного вызова При входящем вызове показывается уведомление с кнопками “Принять” / “Отклонить”. До ответа аудиопоток не активируется.
11 Повторный вызов Если собеседник не ответил (отклонил или нет ответа), добавить кнопку “Перезвонить” с автоматической повторной попыткой через 10 секунд (до 3 раз).
12 Громкая связь (speaker mode) Кнопка для переключения вывода звука на динамики (вместо наушников). Использовать SourceDataLine с настройкой громкости.
13 Микрофон-заглушка (mute) Кнопка для временного отключения микрофона во время разговора. При этом аудиопоток не отправляется, но соединение остаётся.
14 Настройка громкости Ползунки для регулировки громкости входящего и исходящего аудиопотока.
15 Отображение длительности разговора Во время звонка показывать таймер (hh:mm:ss). Останавливается при завершении вызова.
16 Эхо-тест с автонастройкой При запуске приложения автоматически запускать короткий эхо-тест (запись 2 секунд, воспроизведение) для проверки аудиоустройств.
17 Пинг (проверка соединения) Кнопка “Проверить связь”, отправляющая TCP-команду PING и измеряющая время ответа (RTT). Отображать в мс.
18 Фильтр шума (простейший) При тишине (уровень громкости ниже порога) не отправлять UDP-пакеты для экономии трафика.
19 Уведомление о начале разговора При получении CALL_START проигрывать короткий звуковой сигнал (даже если приложение свёрнуто).
20 Выбор аудиоустройства В GUI выпадающий список для выбора микрофона и динамиков из доступных.