GrabDuck

Java: IP-телефония с нуля

:

В предыдущей статье «Транслируем звук по сети с помощью Java» я описывал способ приема и трансляции звука по сети встроенными средствами Java.

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

Система IP-телефонии состоит из серверной части, которая хранит учетные данные пользователей и их текущие IP, и консольного клиента, который способен совершать и принимать звонки напрямую от второго абонента.

Полностью исходники можно посмотреть на github.

Всех заинтересовавшихся прошу под кат.

NetworkingAudioServer — серверная часть системы


Серверная часть работает на базе сервлетов, Apache Tomcat и MySQL.
Структура базы данных

База данных состоит из двух таблиц: users — хранит учетные данные пользователей и userinfo — сопоставляет каждому пользователю IP и время его последнего обновления.
CREATE TABLE IF NOT EXISTS `users` (
    `email` varchar(100) NOT NULL PRIMARY KEY, 
    `password` varchar(100) NOT NULL, 
    `confirm` varchar(100) NOT NULL,
    `user_id` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Названия email и password говорят сами за себя, confirm — токен подтверждения почтового адреса либо 'done', если e-mail подтвержден, user_id — md5-хэш от адреса электронной почты (так как он имеет фиксированную длину, его удобно использовать для идентификации входящего звонка).

CREATE TABLE IF NOT EXISTS `userinfo` (
    `email` varchar(100) NOT NULL PRIMARY KEY, 
    `user_id` varchar(100) NOT NULL, 
    `ip` varchar(100) NOT NULL, 
    `last_update` TIMESTAMP NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Поля email и user_id соответствуют полям из предыдущей таблицы, что такое ip понятно из названия, last_update — время последнего обновления IP.

Сервлеты

RegisterServlet регистрирует пользователя в базе данных и высылает на электронную почту запрос на подтверждение с помощью следующего скрипта:
#!/bin/sh
email=$1
confirm=$2
SERVER_URL="http://tabatsky.ru/networkingaudio";
TMP_FILE="/common_scripts/tmp/$confirm";

echo "To: $email" > $TMP_FILE;
echo "From: service@tabatsky.ru" >> $TMP_FILE;
echo "Subject: Networking Audio e-mail confirmation" >> $TMP_FILE;
echo >> $TMP_FILE;
echo "$SERVER_URL/confirm?confirm=$confirm" >> $TMP_FILE;
echo >> $TMP_FILE;

sendmail $email < $TMP_FILE

ConfirmServlet — выполняет подтверждение e-mail.

UpdateIPServlet — обновляет IP клиента при запросе, IP определяется автоматически.

GetUserInfoServlet — в качестве параметра принимает email или user_id, при запросе возвращает email или user_id и текущий IP, либо значение 'offline', если IP не обновлялся более трех минут.

Настройка серверной части

Для установки серверной части нужно:
  • Создать базу данных MySQL
  • Указать правильные значения логина, пароля и базы данных MySQL в классе MyDBConnect
  • Собрать и задеплоить на Tomcat
  • Указать правильное значение SERVER_URL в скрипте отправки почты, сам скрипт разместить по адресу /common_scripts/sendConfirm и установить права на выполнение
  • Создать папку /common_scripts/tmp и установить права на запись

jNetworkingAudioClient — консольный клиент


Структура клиента

Консольный клиент состоит из четырех классов с программной логикой — Main, Master, Slave и IPUpdater, и двух вспомогательных классов — Util и DeclinedException.

Класс Util хранит настройки клиента — такие как параметры звука и объем буфера.

Класс Main отвечает за программную логику интерфейса.

Класс IPUpdater каждые 90 секунд отправляет сервлету запрос на обновление IP.

Класс Master слушает сетевой порт и, в свою очередь, содержит в себе два вложенных класса-потока: MicrophoneReader — читает данные с микрофона и Sender — отправляет данные второму абоненту.

Класс Slave: инициирует соединение, отправляя второму абоненту md5-хэш адреса электронной почты, затем, если звонок принят, начинает читать данные из сокета и отправлять их на аудио-выход.

Более подробно описывать устройство клиента, пожалуй, не стану — все желающие могут сами ознакомиться с исходным кодом.

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

Здесь можно взять готовый исполняемый jar.

Запуск:

java -jar jNetworkingAudioClient.jar http://serverUrl 2>log.txt

Желающие могут попробовать на моем сервере:

java -jar jNetworkingAudioClient.jar http://tabatsky.ru/networkingaudio/ 2>log.txt

Но: в целях хабраэффектоустойчивости сервера я установил ограничение в 100 зарегистрированных пользователей.

И последнее: если Вы сидите за роутером, нужно сделать редирект порта 7373 на свою машину.

UPD:


Возникла небольшая проблема — когда я пытаюсь обновлять свой собственный IP через внешний хост, сервлет вместо моего внешнего IP получает IP моего роутера во внутренней сети — 192.168.1.1.

Придумал небольшой костыль — триггер для MySQL:

CREATE TRIGGER `my_host_ip` BEFORE INSERT ON `userinfo`
FOR EACH ROW SET NEW.ip=IF(NEW.ip='192.168.1.1','tabatsky.ru',NEW.ip);