- PVSM.RU - https://www.pvsm.ru -
Приветствую сообщество!
Моя первая статья Пример реализации обращения к Asterisk CLI на PHP. Структуризация ответа звездочки [1] наконец то перешла из модерации в песочницу и модератор сразу дал мне полные права для публикации на хабре.
Я хочу начать цикл статей о том как можно реализовать собственное API для обращения с Астериском через AMI. Если у Вас имеются время, желание и самое главное опыт, поддержать меня и попытаться составить команду которая на данном портале создаст полноценное php API для взаимодействия с AMI Астериска велком к диалогу.
Задача очень интересная так как многие сервисы посвященные Звездочке сразу прячут всю информацию о попытках разработки собственных интерфейсов для Asterisk. Хотя мой опыт который я здесь изложу может стать для многих «коддеров» попыткой потешить свое «эго», но вариантов создать собственное веб приложение, которое не будет глючить каждую секунду не даст (правда если вы виртуоз языка javascript то очень даже получится какой нибудь грамотный заменитель буржуйской веб-морды для астера).
В этой части я покажу что написать собственный класс для обращения к AMI звездочки проще простого, главное идти от простого к сложному.
Для особо ленивых в конце статьи готовый (рабочий) модуль с классом Asterisk AMI. С примерами запросов. Класс полностью рабочий и готов к использованию.
Я решил реализовать механизм работы сокета на fsockopen.
Прежде чем начать коддинг маленький ликбез.
Пример:
function __destruct()
{
echo "Завершение работы";
// другие действия определенные пользователем.
}
Пример:
var $a = array ("a","b","c");
echo count($a);
// count вернет 3 (количество элементов)
Пример:
die ("Завершение скрипта");
// скрипт запершит работу после вывода строки: "Завершение скрипта"
Пример:
$a = explode(" ","Маша любит быть наездницей");
// Результат: $a[0]="Маша", $a[1]="любит", $a[2]="быть", $a[3]="наездницей"
Пример:
fclose($a);
// Закрывает соединение с файлом/сокетом связанным с $a дескриптором
Пример:
$b = fread($a,100);
// Прочитает 100 байтов из файла/сокета связанного с $a дескриптором
Пример:
$m= fsockopen(host, port,$a,$b,2);
// $m будет содержать дескриптор(ссылку) на открытый сокет или false если соединение не получилось установить.
Пример:
function example()
{
echo "function";
}
// Объявит функцию example
Пример:
fwrite($a, "Строка для записи");
// Запишет в файл/сокет с дескриптором $a строку "Строка для записи"
Пример:
class TempClass
{
public $a = 'Здесь строка к которой можно обратиться вне класса';
}
$b = new TempClass();
echo $b->a;
// Выведет строку 'Здесь строка к которой можно обратиться вне класса
Пример:
echo rand(100,999);
// Выведет случайное число между 100 и 999
Пример:
function ad ()
{
return 5;
}
echo ad();
// Выведет 5
Пример:
set_time_limit (10);
echo ad();
// Максимальное время работы будет 5 секунд.
Пример:
sleep (10);
echo "Не бойся, у тебя все получится";
// Задержит время выполнения вывода строки "Не бойся, у тебя все получится" на 10 секунд.
Пример:
socket_get_status ($a);
// Получим метаданные(свойства) сокета связанного с дескриптором $a.
Пример:
stream_set_timeout ($a,0,1000);
// Установит таймауты у сокета связанного с дескриптором $a.
Пример:
echo trim (" Маша любит на Саше ");
// Выведет строку "Маша любит на Саше";
Пример:
var_dump(5);
// Выведет int(5);
Пример:
class TempClass
{
public $a = 'Маше нравится темперамент Саши';
}
$b = new TempClass();
echo $b->a;
// Выведет строку 'Маше нравится темперамент Саши'
* Пояснения составлены лично мной, не являются энциклопедическими данными или стандартами, и служат лишь для пояснения кода на данной странице. Если Вас не устраивает мое описание просьба отписаться в комментах. В случае если Ваше описание будет более ясно описывать приведенные выше понятия для объяснения кода примеров,то я внесу в статью изменения. Корректировки будут указаны с Вашим авторством.
2. Запускаем программу и заходим на сервер заполняя поля указанные красным маркером.
Нажимаем «да» на все вопросы и если потребуется заново вводим пароль.
3. Далее ищем файл manager.conf. Стандартное расположение у файла /etc/asterisk/.
Открываем файл двойным щелчком мышки.
4. Внутри файла находим секцию [admin]
Записываем логин и пароль от Вашего Астериск AMI. В моем случае это admin и amp11
2. Запускаем putty. Выделенное красным на рисунке отмечаем. В строке выделенной красным с надписью 1, пишем IP адрес сервера Asterisk. SSH протокол по которому будет работать putty. Never запретит putty закрываться в случае разрыва соединения.
3. На приглашение системы вводим пользователя(обычно это рут) и нажимаем Enter.
4. Вводим пароль и нажимаем Enter.
5. Если авторизация прошла успешно, получаем примерно такой экран.
6. Вводим: telnet 127.0.0.1 5038 и получаем приглашение Астериска: Asterisk Call Manager
7. Вводим следующие строки:
Action: Login // Нажимаем Enter
Username: Имя_которое_мы_нашли_через_WinSCP // Нажимаем Enter
Secret: Пароль_который_мы_нашли_через_WinSCP // Нажимаем 2 раза Enter
Получаем сообщение Астериска: Message: Authentication accepted
8. Вводим следующие строки:
Action: Logoff // Нажимаем 2 раза Enter
Получаем сообщение системы
Response: Goodbye
Message: Thanks for all the fish.
* Вот мы научились подключаться через putty по SSH к AMI Астериска
Составим таблицу, чтобы понять какой функционал необходимо добавлять в класс.
Задача. | Необходимость. | Функционал класса. |
---|---|---|
Первоначальная инициализация переменных класса. | Обязательное условие. | Конструктор __construct(); Глобальный массив $this->ini в котором мы будем хранить все переменные класса. |
Закрытие класса. | Обязательное условие. | Деструктор __destruct(); |
Соединение с сокетом. | Обязательное условие. | Функция connect(); |
Разъединение с сокетом | Обязательное условие. | Функция disconnect(); |
Авторизация в AsterIsk AMI. | Обязательное условие. | Функция init(); |
Запись в сокет. | Обязательное условие. | Функция write(); |
Считывание из сокета синхронно. | Не обязательное условие. | Функция read_syn(); |
Считывание из сокета асинхронно. | Обязательное условие. | Функция read(); |
Это грубое ТЗ которое позволит написать класс для взаимодействия через PHP с Asterisk AMI.
При написании данного класса я использовал следующий регламент:
Основная задача конструктора объявить все переменные которые будут использоваться в классе. Все переменные у нас будут храниться в массиве $this->ini.
<?php
class Asterisk_ami
{
/* Переменные класса */
public $ini = array();
/* Первоначальная инициализация переменных класса. */
function __construct ()
{
/* Настройки класса по умолчанию */
$this->ini["con"] = false; /* ссылка на соединение */
}
/* Деструктор класса */
function __destruct()
{
unset ($this->ini); /* Так как мы во всем классе использовали всего одну переменную $this->ini то и уничтожать будем только ее одну. Некоторые критики могут сказать что php сам может почистить память за классом, и я с этим соглашусь почти во всех случаях кроме как использования памяти в демонах */
}
}
?>
Остальные переменные в данный массив мы будем добавлять по мере необходимости, и будем детально рассматривать каждый элемент по функциональности.
Для соединения с сокетом я решил использовать функцию fsockopen.
fsockopen(host, port,$a,$b,$c);
где
host - IP адрес хотса к которому мы будем соединяться,
port - порт сервера на который будет идти соединение,
$a - номер ошибки в случае неудачного соединения,
$b - текстовое описание ошибки в случае неудачного соединения,
$c - таймаут сокета(очень интересный параметр, но вообще не работающий параметр).
Из описания функции мы можем понять, что нам нужно ввести как минимум 2 переменные в глобальный массив. Это IP адрес сервера и порт для коннекта. Так как $a,$b возвращаемые значения (а мы не справочное бюро чтобы хранить справки) то их мы проигнорируем. А $c параметр вообще не работает, поэтому держать его в памяти не будем.
Почему я пишу что не работает? Потому что это реальный опыт работы данной функции в демонах, которым глубоко по
барабану на собственные же спецификации PHP.
Для двух новых переменных введем 2 элемента массива:
$this->ini["host"] = "127.0.0.1"; /* IP сервера */
$this->ini["port"] = "5038"; /* Порт для соединения */
Напишем функцию:
<?php
class Asterisk_ami
{
/* Переменные класса */
public $ini = array();
/* Первоначальная инициализация переменных класса. */
function __construct ()
{
/* Настройки класса по умолчанию */
$this->ini["con"] = false; /* ссылка на соединение */
$this->ini["host"] = "127.0.0.1"; /* IP сервера */
$this->ini["port"] = "5038"; /* Порт для соединения */
}
/* Деструктор класса */
function __destruct()
{
unset ($this->ini); /* Так как мы во всем классе использовали всего одну переменную $this->ini то и уничтожать будем только ее одну. Некоторые критики могут сказать что php сам может почистить память за классом, и я с этим соглашусь почти во всех случаях кроме как использования памяти в демонах */
}
/* Функция для поднятия сокета */
function connect()
{
$this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10);
if ($this->ini["con"])
{
stream_set_timeout($this->ini["con"], 0, 400000); /* Вот честно я понятия не имею почему данная функция работает с этим сокетом, но без нее асинхронность не получиться и у нас будет сыпаться обычный мусор, или сокет будет вообще спать, а вместе с ним и класс */
}
}
/* Функция для закрытия сокета */
function connect()
{
if ($this->ini["con"]) /* Если есть соединение */
{
fclose($this->ini["con"]); /* То мы закрываем сокет */
}
}
}
?>
Для работы с Астериск АМИ мы должны записывать в сокет пакеты строк, в ответ на которые мы будем также получать строки о статусе наших запросов. Для записи мы будем использовать php функцию fwrite.При этом в с каждым пакетом мы можем отправить индикатор ActionID нашего запроса, чтобы получив пакеты информации от Астериска мы могли классифицировать это пакеты по отправленным заранее событиям.
/* Функция для записи в сокет */
function write($a) /* $a это строка которую мы должны записать в сокет */
{
$m = rand (10000000000000000,99999999900000000); /* Рандомое число, для генерации рандомного ActionID */
fwrite($this->con, "ActionID: $this->action_id$mrn$arnrn"); /* Записываем в сокет */
$this->sleepi(); /* Суть этой фунции я расскажу в следующем параграфе. */
return $m; /* Возвращаем ActionID */
}
Каждый раз, когда наш скрипт отправляет пакет строк в сокет, возвращается для исполнения следующего кода. Проследим что же происходит с нашим пакетом. Наш пакет попадает в очередь для чтения со стороны сервера контролирующего сокет. Сервер в свою очередь в данный момент может быть занят другой операцией, и прочитает наши строки немного позже. В связи с этим, по моему мнению, лучше дать небольшое время для того, чтобы сервер успел прочитать наши строки и начать обработку нашего пакета. Для этого я ввел в класс функцию, которая будет заставлять скрипт засыпать на некоторое время.
class Asterisk_ami
{
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function __construct ()
{
/* Настройки класса по умолчанию */
$this->ini["con"] = false; /* ссылка на соединение */
$this->ini["host"] = "127.0.0.1"; /* IP сервера */
$this->ini["port"] = "5038"; /* Порт для соединения */
$this->ini["sleep_time"]=1500; /* В демонах эта цифра намного меньше, в моем тестовом сервере, всего "5" */
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* Задает стандартную паузу между функциями записи и чтения */
function sleepi ()
{
sleep($this->ini["sleep_time"]); /* Скрипт будет засыпать на заданное время */
}
}
Здесь все просто. Смотрим код.
/* Считывает данные из сокета */
function read()
{
$mm = array();
$b = array();
$k = 0;
$s = "";
$this->sleepi();
do
{
$s.= fread($this->con,1024);
sleep($this->read_sleep_time);
$mmm=socket_get_status($this->con);
} while ($mmm['unread_bytes']);
$mm = explode ("rn",$s);
for ($i=0;$i<count($mm);$i++)
{
if ($mm[$i]=="")
{
$k++;
}
$m = explode(":",$mm[$i]);
if (isset($m[1]))
{
$this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]);
}
}
return $this->ini["lastRead"];
}
В итоге мы получили класс для работы с Астериск AMI:
<?php
class Asterisk_ami
{
public $ini = array();
function __construct ()
{
$this->ini["con"] = false;
$this->ini["host"] = "127.0.0.1";
$this->ini["port"] = "5038";
$this->ini["lastActionID"] = 0;
$this->ini["lastRead"] = array ();
$this->ini["sleep_time"]=1.5;
$this->ini["login"] = "admin";
$this->ini["password"] = "amp111";
}
function __destruct()
{
unset ($this->ini);
}
public function connect()
{
$this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10);
if ($this->ini["con"])
{
stream_set_timeout($this->ini["con"], 0, 400000);
}
}
public function disconnect()
{
if ($this->ini["con"])
{
fclose($this->ini["con"]);
}
}
public function write($a)
{
$this->ini["lastActionID"] = rand (10000000000000000,99999999900000000);
fwrite($this->ini["con"], "ActionID: ".$this->ini["lastActionID"]."rn$arnrn");
$this->sleepi();
return $this->ini["lastActionID"];
}
public function sleepi ()
{
sleep($this->ini["sleep_time"]);
}
public function read()
{
$mm = array();
$b = array();
$k = 0;
$s = "";
$this->sleepi();
do
{
$s.= fread($this->ini["con"],1024);
sleep(0.005);
$mmm=socket_get_status($this->ini["con"]);
} while ($mmm['unread_bytes']);
$mm = explode ("rn",$s);
$this->ini["lastRead"] = array();
for ($i=0;$i<count($mm);$i++)
{
if ($mm[$i]=="")
{
$k++;
}
$m = explode(":",$mm[$i]);
if (isset($m[1]))
{
$this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]);
}
}
unset ($b);
unset ($k);
unset ($mm);
unset ($mm);
unset ($mmm);
unset ($i);
unset ($s);
return $this->ini["lastRead"];
}
public function init()
{
return $this->write("Action: LoginrnUsername: ".$this->ini["login"]."rnSecret: ".$this->ini["login"]."rnrn");
}
}
$a = new Asterisk_ami();
$a->connect();
if ($a->ini["con"])
{
$a->init();
$a->write("Action: ListCommands");
var_dump($a->read());
$a->disconnect();
}
unset($a);
?>
Создать собственный класс для работы с сокетами в асинхронном режиме не сложно. И не важно, будет ли это работа с Астериск АМИ или другим серверным приложением.
HiNeX [4] за ошибки найденные в промежуточном коде.
Спасибо всем кто дочитал. И до встречи на порталах ТМ.
(Если вы считаете что стоит продолжить, то какой из функционалов Астериск АМИ вы хотите, чтобы я показал в следующей части? Пишите в комментариях.)
Убедительная просьба для тех кто минусует статью, если Вам не трудно, пожалуйста, напишите причину.
Автор: RinatUsmanov
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/svoimi-rukami/86506
Ссылки в тексте:
[1] первая статья Пример реализации обращения к Asterisk CLI на PHP. Структуризация ответа звездочки : http://habrahabr.ru/post/253351/
[2] официального сайта программу WinSCP: http://winscp.net/eng/download.php
[3] Загружаем putty с официального сайта: http://www.putty.org/
[4] HiNeX: http://habrahabr.ru/users/HiNeX/
[5] AotD: http://habrahabr.ru/users/AotD/
[6] RUgaleFF: http://habrahabr.ru/users/RUgaleFF/
[7] Источник: http://habrahabr.ru/post/253387/
Нажмите здесь для печати.