WeBinstruments

Инструментарий для веб-мастеров

 

Техподдержка и
сопровождение
сайтов


на сайте на всех сайтах для веб разработчиков везде
 
 Скрипты
  PHP скрипты   
  Flash скрипты   
  Javascript скрипты   

 Софт
  Инструменты SEO   
  Разработка сайтов   
  Серверное ПО   

 Документация
  MySQL   
  PHP   
  HTML   
  Протоколы и сервисы Internet   

 Статьи
  PHP   
  MySQL   
  Сервер   
  еще...   

 Услуги и сервисы
  Домены   
  Хостинги   
  Блоки для сайта   

 Новости


 Контакты
  icq: 158325531
  email:
  все контакты: здесь




Atom все поступления
Раздел Новости
Раздел Услуги и сервисы
Раздел Статьи
Раздел Файлы
комментарии

Главная / Статьи / PHP / Jabber и PHP


Jabber и PHP

27.04.2010
сайт автора: WeBi
публикация данной статьи разрешена только со ссылкой на сайт автора статьи

Ссылки по теме
Готовый PHP класс для работы с jabber
Описание протокола XMPP (jabber)

Протокол XMPP(jabber).
В этой статье расскажу, как можно работать с протоколом jabber через php с помощью сокетов.
Как отправлять сообщения и как их получать через php скрипт.
Делать полноценный jabber клиент на PHP нет никакого смысла, для этого существуют сотни различных клиентов.
Для чего может понадобиться работать с jabber через php, это уже решать вам.


Jabber и XMPP это один и тот же протокол.
XMPP - современное название протокола.
Jabber - старое название.

Протокол XMPP открыт и обмен информацией идет с помощью XML.
Именно из-за XML этот протокол имеет большой минус - избыточность в трафике. На фоне коротких сообщений эта избыточность выглядит просто огромной.
На примерах это будет видно.

Для начала расскажу теорию работы протокола jabber на примерах.
Вот пример соединения с яндексом (Я.Онлайн).

Сначала соединяемся с хостом xmpp.yandex.ru по порту 5222.
После установления соединения посылаем следующий XML

<?xml version="1.0"?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="ya.ru" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">

Это что-то вроде приветствия. Обратите внимание, to="ya.ru" здесь указывается доменная часть идентификатора jabber (например от test@ya.ru)
Далее, после вашего приветствия сервер должен ответить примерно так

<?xml version='1.0'?>
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='3357826913' from='ya.ru' version='1.0' xml:lang='en'>
<stream:features>
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
<compression xmlns='http://jabber.org/features/compress'>
<method>zlib</method>
</compression>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>

В этом ответе сервер указывает, что может работать в защищенном режиме, поддерживает сжатие zlib, механизм авторизации и т.д.
Раз сервер умеет работать в защищенном режиме (tls), значит переводим общение с сервером в защищенный режим следующей командой

<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
После этой команды сервер должен ответить согласием примерно так

<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
После этого ответа сервер готов работать по защищенному протоколу и нужно снова отправлять приветствие, так как было в самом начале.

<?xml version="1.0"?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="ya.ru" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">

Если связь по защищенному соединению удалась и сервер получил приветствие, ответ будет таким

<?xml version='1.0'?>
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='3833292332' from='ya.ru' version='1.0' xml:lang='en'>
<stream:features>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>

Как видно, сейчас сервер уже не показывает, что поддерживает сжатие zlib, так как в защищенном режиме tls поток уже сжат.
Теперь можно начинать авторизацию. Сервер яндекса показывает, что поддерживает лишь один вариант авторизации (sasl PLAIN).
Для этого логин (без домена) и пароль кодируются с помощью base64 ( base64_encode("\x00".$user."\x00".$pass); ) и отправляем на сервер в таком виде

<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">ADEyMwAxMjM=</auth>
Если авторизация прошла, ответ будет таким

<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
После этого опять отправляем приветствие

<?xml version="1.0"?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="ya.ru" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">

Сервер отвечает и показывает, какие действия доступны уже после авторизации

<?xml version='1.0'?>
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='1839452106' from='ya.ru' version='1.0' xml:lang='en'>
<stream:features>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</stream:features>


Сейчас нужно создать полный JID, то есть связать свой идентификатор с ресурсом. Jabber протокол позволяет соединяться под одним логином из нескольких мест, при этом все соединения будут оставаться в сети. Чтобы определять кто есть кто нужно добавить некую метку (ресурс, любое текстовое имя), при соединении из другого места эту метку нужно ставить другой.
В данном примере связываю с ресурсом webi

<iq type="set" id="1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>webi</resource></bind></iq>
Вот такой ответ сервера

<iq id='1' type='result'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>test@ya.ru/webi</jid></bind></iq>
Сервер показывает, какой получился полный JID (test@ya.ru/webi)
Полный JID не всегда будет таким как вы ожидаете. Например, talk google добавляет к имени ресурса еще случайную строку и по алгоритму гугла полный JID в этом случае мог бы получиться test@ya.ru/webi75AE39EC. Поэтому после установки ресурса нужно обязательно получить ответ от сервера и узнать какой JID присвоил сервер и его уже использовать дальше.

Далее запускаем сессию

<iq type="set" id="sess_2" to="ya.ru"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>
Получаем ответ

<iq type='result' from='ya.ru' id='sess_2'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>
<iq from='ya.ru' to='test@ya.ru/webi' id='ask_version' type='get'><query xmlns='jabber:iq:version'/></iq>
<iq from='ya.ru' to='test@ya.ru/webi' id='ping_0' type='get'><ping xmlns='urn:xmpp:ping'/></iq>

Ну а теперь можно запросить список контактов например так

<iq type="get" id="3"><query xmlns="jabber:iq:roster"/></iq>
Вот в таком виде выдаются контакты.

<iq from='test@ya.ru' to='test@ya.ru/webi' id='3' type='result'>
<query xmlns='jabber:iq:roster'>
<item subscription='both' name='Иванова' jid='name@ya.ru'><group>Имя группы</group></item>
<item subscription='both' name='Петров' jid='name@gmail.com'/>
<item subscription='both' jid='name2@ya.ru'/>
<item subscription='both' name='Почта (test@)' jid='lastmail.ya.ru'><group>Яндекс.Информеры</group></item>
</query>
</iq>


На данном этапе статус в сети отключен, вас не видно. Для выхода в онлайн посылаем команду.

<presence><show>chat</show><status>текстовая запись о статусе</status><priority>10</priority></presence>
В данном случае установлен статус chat и приоритет 10. Если ничего не указать в теге <show> статус будет online.

Ответом на эту команду будет список статусов контактов, получение сообщений и т.д.
Теперь если не разрывать соединение будет идти получение различной информации.

Обратите внимание на атрибут тегов id во многих запросах и ответах. Например запрос контактов

<iq type="get" id="3"><query xmlns="jabber:iq:roster"/></iq>
Атрибут id должен быть уникальным при каждом запросе, это нужно для более точной идентификации запроса, на который отвечает сервер или клиент.
В данном примере сервер выдаст список контактов и атрибут id будет указан тоже 3. То есть сервер дает ответ именно для конкретного запроса.
По правилам, вы должны контролировать этот id и посылая команду на сервер нужно проверить, соответствует ли id в ответе сервера.
Это касается не только исходящих запросов, но и входящих к вам.
Например входящее к вам сообщение будет иметь id и может иметь запрос на подтверждение получения сообщения. И при получении такого сообщения, клиент должен сразу отправить ответ, что сообщение доставлено, при этом указать id входящего сообщения. Про работу с сообщениями напишу ниже.

Я показал принцип общения с jabber сервером.
Сейчас рассмотрим как это все сделать с помощью php.
Обратите внимание, jabber работает в юникоде, поэтому скрипты должны быть написаны тоже в юникоде.
Если вы будете отправлять русские тексты на сервер не в юникоде, то сервер может разрывать соединение без предупреждения, либо тексты будут разрушены.

Для начала напишем небольшую функцию, которая будет получать ответы сервера.

<?php
function getxml($stream)
{
    
sleep(1); // перед получением информации дадим паузу, чтобы сервер успел отдать информацию
    
$xml='';

    
// запрашивать данные 1600 раз, но не более 15 пустых строк
    
$emptyLine = 0;
    for(
$i=0; $i<1600; $i++)
    {
        
$line = fread($stream,2048);
        if(
strlen($line) == 0) {
            
$emptyLine++;
            if(
$emptyLine > 15) break;
        }
        else {
            
$xml .= $line;
        }
    }
    if(!
$xml) return false;
    return
$xml;
}
?>

Так как у данного открытого потока не будет наблюдаться конца, то получать данные из потока можно бесконечно, поэтому ограничимся получением данных 1600 раз, либо встретив 15 пустых строк.
И вот эту функцию и будем использоваться для получения ответов от сервера.

Пример демонстрирую опять же на яндексе, почтовый ящик test@ya.ru

<?php
$user
="test"; // логин до '@'
$domain="ya.ru"; // домен после '@'
$pass="123"; // пароль
$host="xmpp.yandex.ru"; // jabber сервер
$port=5222; // порт

// устанавливаем соединение с сервером
$stream = fsockopen($host,$port,$errorno,$errorstr,10);

// эти настройки необходимы, чтобы при получении данных из потока не было зависания.
// иначе при обнаружении пустой строки php зависнет в длительном ожидании
stream_set_blocking($stream,0);
stream_set_timeout($stream,3600*24);

// после соединения с сервером посылаем приветствие(все как писал ранее)
$xml = '<?xml version="1.0"?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="'
.$domain.'" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">';
fwrite($stream,$xml."\n"); // отправка данных на сервер в конце ставится перенос строки \n
$xmlin=getxml($stream); // получение ответа от сервера
// обрабатываем ответ сервера, узнаем может ли сервер работать в защищенном режиме,если может переходим в защищенный режим

// посылаем команду на переход в защищенный режим
$xml = '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream); // получаем ответ

// если сервер подтвердил переводим поток в защищенный режим
stream_set_blocking($stream, 1); // сначала блокировку ставим в 1
stream_socket_enable_crypto($stream, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT); // переходим в защищенный режим
stream_set_blocking($stream, 0); // блокировку обратно ставим в 0

// после перехода в защищенный режим снова посылаем приветствие
$xml = '<?xml version="1.0"?>';
$xml .= '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="'.$domain.'" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">';
fwrite($stream, $xml."\n");
$xmlin=getxml($stream); // получение ответа

// теперь проходим авторизацию
$xml = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">';
$xml .= base64_encode("\x00".$user."\x00".$pass); // вот так кодируется логин пароль для этого типа авторизации
$xml .= '</auth>';
fwrite($stream, $xml."\n");
$xmlin=getxml($stream);

// после авторизации опять посылаем приветствие
$xml = '<?xml version="1.0"?>';
$xml .= '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="'.$domain.'" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream);

// сейчас устанавливаем имя ресурса (расположение вашего клиента)
$xml = '<iq type="set" id="2"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>webi</resource></bind></iq>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream);

// пошла сессия
$xml = '<iq type="set" id="sess_2" to="'.$domain.'"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream);

// а теперь можно получить список контактов
$xml = '<iq type="get" id="3"><query xmlns="jabber:iq:roster"/></iq>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream); // здесь сейчас список ваших контактов

// ну и теперь выходим в онлайн и становимся видимыми для ваших контактов
$xml = '<presence><show></show><status>мой статус онлайн</status><priority>10</priority></presence>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream); // после выхода в онлайн здесь будут получены офлайн сообщения и дополнительная информация по статусам ваших контактов.


// теперь можно отправить сообщение например для контакта asd@asd.ru
// в поле from указываете полный JID вместе с ресурсом(он должен быть получен в ответе сервера при установке ресурса), в поле to - кому адресовано сообщение, если ресурс не известен, можно без указания ресурса.
$xml = '<message type="chat" from="test@ya.ru/webi" to="asd@asd.ru" id="et5r">';
$xml .= '<body>тестовое письмо</body>';
$xml .= '</message>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream);


// Если есть необходимость, можно зациклить скрипт и оставаться подключенным и получать входящие данные
while(1)
{
    
sleep(3); // ставим паузу в 3 секунды, чтобы не создавать большую нагрузку на php
    
$xmlin=getxml($stream); // и раз в 3 секунды идет сбор данных из потока. тут будут приходить сообщения, информация о смене статусов ваших контактов и т.д.
}

?>

Данный пример показывает принцип работы с jabber сервером, но для полноценной работы нужно разбирать ответы сервера, получать сообщения и т.д.
Далее показываю как получать информацию от сервера и ее обрабатывать.
Для разбора xml подойдет встроенная в php поддержка SimpleXML.

Каждый ответ от сервера посылаем на разбор во встроенную функцию simplexml_load_string().
Но предварительно удалим строку <?xml version='1.0'?> и окружим оставшийся XML тегами <webi_xml>

<?php
$xmlin
= preg_replace ("'<\?xml.*\?>'si", "", $xmlin); // сначала удалим начальный тег < xml > если он есть
$xmlin = "<webi_xml>".$xmlin."</webi_xml>"; // окружение xml специфическим тегом, чтобы получилась обработка некоторых невалидных xml
$xml_ob=simplexml_load_string($xmlin); // получился удобный объект, который легко анализировать

?>

Если каждый ответ от сервера пропускать через этот код, то на выходе будет получаться достаточно удобный и читаемый объект.
Можете просмотреть его
print_r($xml_ob);

Для чего же нужно удалять <?xml version='1.0'?> и окружать оставшийся XML каким то тегом?
Дело в том, что сервер может выдавать за раз сразу несколько ответов.
Например, при подключении можно получить сразу несколько оффлайн сообщений, примерно так

<message [...]>
<body [...]>первое сообщение</body>
</message>

<message [...]>
<body [...]>второе сообщение</body>
</message>

Этот пример демонстрирует как сервер сначала выдал первое сообщение, а затем выдал второе сообщение.
Отдельно каждое сообщение имеет валидный XML.
Но при получении данных из потока, эти два сообщения будут получены как один целый XML, а рассматривая эти два сообщения как одно целое, получается уже не валидный xml и при разборе будет ошибка.
Но если эти сообщения окружить любым тегом, то xml станет валидным.
Поэтому сначала удаляем строку <?xml version='1.0'?>, если она есть, а затем окружаем оставшийся xml любым тегом, в моем примере <webi_xml> и потом уже отдаем на разбор в функцию simplexml_load_string().

Вот пример, как получать входящие сообщения.
Начну пример с выхода в онлайн, после успешной авторизации.

<?php
$xml
= '<presence><show></show><status>мой статус онлайн</status><priority>10</priority></presence>'; // ставим статус онлайн
fwrite($stream,$xml."\n");
$xmlin=getxml($stream); // после появления в сети будут получены офлайн сообщения

// далее начинаем обработку полученного xml
$xmlin = preg_replace ("'<\?xml.*\?>'si", "", $xmlin); // сначала удалим начальный тег < xml > если он есть
$xmlin = "<webi_xml>".$xmlin."</webi_xml>"; // окружение xml специфическим тегом, чтобы получилась обработка некоторых невалидных xml
$xml_ob=simplexml_load_string($xmlin); // и теперь здесь находится разложенный по полочкам объект, с полученными сообщениями и т.д.

// например вот информация по первому сообщению
print $xml_ob->message[0]->body; // текст сообщения
print $xml_ob->message[0]->attributes()->from; // от кого
?>

соответствено можно циклом перебрать все пришедшие сообщения подобным способом

<?php
if(isset($xml_ob->message)) // если есть сообщения
{
    foreach (
$xml_ob->message as $message)
    {
        print
$message->body; // очередное сообщение
        
print $message->attributes()->from; // от кого сообщение
    
}
}
?>


Вот таким образом происходит работа с jabber сервером на практике.
Теперь у вас не должно возникнуть никаких вопросов как отправлять, получать и разбирать ответы сервера.

Дополнительная информация по теме
Готовый PHP класс для работы с jabber
Описание протокола XMPP (jabber) на русском



Авторизация механизмом sasl DIGEST-MD5
В своих примерах я показывал как работает авторизация sasl PLAIN, сейчас расскажу про sasl DIGEST-MD5
Этот механизм авторизации считается более надежным и предподчительным. Но по моему мнению, этот метод более защищенный лишь за-за своей запутанности и некой усложненности.
sasl PLAIN поддерживают почти все jabber сервера, а вот DIGEST-MD5 поддерживают не все сервера, например яндекс на момент написания статьи поддерживал лишь sasl PLAIN атворизацию, а вот гугловский Talk поддерживает оба этих механизма, можно выбрать любой по вашему усмотрению.

Чтобы понять какой механизм авторизации поддерживается, смотрите ответ сервера в самом начале общения. Например ответ сервера

<?xml version='1.0'?>
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='3833292332' from='ya.ru' version='1.0' xml:lang='en'>
<stream:features>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>DIGEST-MD5</mechanism>
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>

Здесь видно, что сервер поддерживает два механизма авторизации, один из них DIGEST-MD5.
Для начала авторизации посылаем серверу команду

<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="DIGEST-MD5"/>
Ответ от сервера должен выглядеть примерно так

<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>bm9uY2U9IjM3MzMyMTM1MjIiLHFvcD0iYXV0aCIsY2hhcnNldD11dGYtOCxhbGdvcml0aG09bWQ1LXNlc3M=</challenge>
Это challenge-пакет, его содержимое закодировано в base64, раскодируем содержимое этого пакета с помощью php функции base64_decode(), получится что то похожее на это
nonce="3733213522",qop="auth",charset=utf-8,algorithm=md5-sess
Для последующей отправки данных понадобится значение nonce
Теперь создаем такую строку (опишу ее нижу)
username="asdasd2641",
response="780e42409d6a40ce7bb59f6d52ec9112",
charset="utf-8",
nc="00000001",
qop="auth",
nonce="3733213522",
digest-uri="xmpp/jabber.ru",
cnonce="gk8K99UVutfDTdj/wPWYt/Klc1qIZV5wLl1+Jw+tdbc="

Далее кодируем ее в base64 с помощью base64_encode() и отправляем таким образом

<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">dXNlcm5hbWU9ImFzZGFzZDI2NDEiLHJlc3BvbnNlP[=урезано=]</response>
В случае успешной авторизации сервер ответит

<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD05Yjg5YjA0MTU1MGQ1ZDMzYTQ5ZjRmYTZjZjk4YjBlMg==</challenge>
В этом пакете нет ничего нужного.
После этого отправим на сервер такой пакет

<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
Сервер ответит

<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
Авторизация пройдена. Теперь можно отправлять стандартное приветствие и далее работаете с сервером по стандартной схеме.

А сейчас разберем самый запутанный момент всего этого механизма авторизации, а именно строку

username="asdasd2641",
response="780e42409d6a40ce7bb59f6d52ec9112",
charset="utf-8",
nc="00000001",
qop="auth",
nonce="3733213522",
digest-uri="xmpp/jabber.ru",
cnonce="gk8K99UVutfDTdj/wPWYt/Klc1qIZV5wLl1+Jw+tdbc="

username логин
charset кодировка
nonce уникальный номер сессии, присланный сервером в предыдущем пакете
nc счетчик, сколько раз был использован этот nonce. обычно всегда используется 00000001
digest-uri протокол, для XMPP сервера он выглядит "xmpp/домен"
cnonce любой уникальный код, сгенерированный клиентом
response содержит пароль и другую информацию в формате MD5, построенную по определенному алгоритму.
Сейчас приведу пример, как с помощью PHP создать response.

<?php
// функция для разбора подобной строки nonce="3733213522",qop="auth",charset=utf-8,algorithm=md5-sess на массив
function explodeData($data) {
    
$data = explode(',', $data);
    
$pairs = array();
    
$key = false;

    foreach (
$data as $pair) {
        
$dd = strpos($pair, '=');

        if (
$dd) {
            
$key = trim(substr($pair, 0, $dd));
            
$pairs[$key] = trim(trim(substr($pair, $dd + 1)), '"');
        }
        else if (
strpos(strrev(trim($pair)), '"') === 0 && $key) {
            
$pairs[$key] .= ',' . trim(trim($pair), '"');
            continue;
        }
    }
    return
$pairs;
}

// обратная функция, создает из массива строку значений
function implodeData($data) {
    
$return = array();
    foreach (
$data as $key => $value) {
        
$return[] = $key . '="' . $value . '"';
    }
    return
implode(',', $return);
}


// функция создания поля response
function response_code($data, $user, $pass) {
    
$data['nc']='00000001';
    if (isset(
$data['qop']) && $data['qop'] != 'auth' && strpos($data['qop'],'auth') !== false) {
        
$data['qop'] = 'auth';
    }
    foreach (array(
'realm', 'cnonce', 'digest-uri') as $key){
        if (!isset(
$data[$key])) {
            
$data[$key] = '';
        }
    }
    
$pack = md5($user.':'.$data['realm'].':'.$pass);
    if (isset(
$data['authzid'])) {
        
$a1 = pack('H32',$pack).sprintf(':%s:%s:%s',$data['nonce'],$data['cnonce'],$data['authzid']);
    }
    else {
        
$a1 = pack('H32',$pack).sprintf(':%s:%s',$data['nonce'],$data['cnonce']);
    }
    
$a2 = 'AUTHENTICATE:'.$data['digest-uri'];

    return
md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2)));
}


// начинаю демонстрацию с отправки серверу команды на авторизацию методом DIGEST-MD5
$xml = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="DIGEST-MD5"/>';
fwrite($stream,$xml."\n");
$xmlin=getxml($stream); // получаем ответ от сервера, тут должен быть challenge пакет, закодированный в base64

// Ответ сервера раскладываем на объект
$xmlin = preg_replace ("'<\?xml.*\?>'si", "", $xmlin);
$xmlin = "<webi_xml>".$xmlin."</webi_xml>";
$xml_ob=simplexml_load_string($xmlin);

$challenge=base64_decode( $xml_ob->challenge[0]); // теперь раскодируем challenge в нормальный вид
$challenge = explodeData($challenge); // раскладываем строку (nonce="3733213522",qop="auth",...) на массив

// добавление к challenge digest-uri если он не пришел в ответе от сервера
if (!isset($challenge['digest-uri'])) {
    
$challenge['digest-uri'] = 'xmpp/'.$domain;
}

// Генерация cnonce - уникальный номер сессии
$str = '';
mt_srand((double)microtime()*10000000);
for (
$i=0; $i<32; $i++) {
    
$str .= chr(mt_rand(0, 255));
}
$challenge['cnonce'] = base64_encode($str);


$response=response_code($challenge, $user, $pass); // создание кодированной строки response на основании некоторых данных из challenge и логина-пароля

// теперь создаем массив значений для отправки
$response_arr = array('username'=>$user,
'response'=>$response,
'charset'    => 'utf-8',
'nc'=>'00000001',
'qop'=>'auth',
);

// Добавление некоторых значений из challenge ответа сервера
foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key) {
    if (isset(
$challenge[$key])) {
        
$response_arr[$key] = $challenge[$key];
    }
}

// и теперь формирование xml на отправку. Получившийся массив переводим в строку значений и кодируем в base64
$xml = '<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">';
$xml .= base64_encode(implodeData($response_arr));
$xml .= '</response>';
fwrite($stream,$xml."\n"); // отправка
$xmlin=getxml($stream); // получение ответа

// ну и сразу посылаем встречный ответ и авторизация пройдена
fwrite($stream,'<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'."\n");
$xmlin=getxml($stream);
?>


Ну а дальше все стандартно...


Отправка и прием сообщений

<message from="от кого" to="кому" xml:lang="ru" type="chat" id="уникальный id">
<body>текст сообщения</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
</message>

Стандартный шаблон сообщения (входящего или исходящего)
from указывается отправитель сообщения полный JID вместе с ресурсом (test@ya.ru/resurs)
to кому адресовано. если ресурс получателя не известен, можно указать без ресурса
id уникальный номер

Сообщение с подтверждением о получении выглядит так

<message from="от кого" to="кому" xml:lang="ru" type="chat" id="уникальный id">
<body>текст сообщения</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
<request xmlns="urn:xmpp:receipts"/>
</message>

После получения сообщения с таким запросом нужно ответить

<message from="от кого " to="кому" xml:lang="ru" id="ID сообщения ">
<received xmlns="urn:xmpp:receipts"/>
</message>

В поле id должен стоять ID того сообщения, о котором подтверждается доставка.
Такой ответ может быть ответом на ваше сообщение с запросом, либо вы должны отправить такой ответ на сообщение с запросом.

Сразу после подключения к серверу вы можете получить оффлайн сообщения.
Формат этих сообщений будет таким

<message from='от кого' to='кому' xml:lang='ru' type='chat' id='уникальный id'>
<body>текст сообщения</body>
<active xmlns='http://jabber.org/protocol/chatstates'/>
<x xmlns='jabber:x:delay' stamp='20100422T05:41:59'/>
</message>

Именно строка<x xmlns='jabber:x:delay' stamp='20100422T05:41:59'/> говорит о том, что сообщение офлайн. Содержит в себе время отправки сообщения

JID - Jabber идентификатор
Jabber ID состоит из имени юзера, домена и ресурса. Например ( test@ya.ru/webi )
Для начала работы с jabber сервером нужно установить ресурс, то есть сформировать полный JID
Как это сделать я уже писал, но послать команду для установки ресурса мало, нужно проверить ответ сервера и вытащить из ответа именно тот JID, который вернул сервер.
Так как некоторые сервера, например talk google добавляют к вашему ресурсу свои метки. Поэтому после установки ресурса узнаем JID из ответа таким образом

<?php
$xmlin
= preg_replace ("'<\?xml.*\?>'si", "", $xmlin);
$xmlin = "<webi_xml>".$xmlin."</webi_xml>";
$xml_ob=simplexml_load_string($xmlin);
$jid=$xml_ob->iq[0]->bind[0]->jid[0]; // здесь теперь тот JID, который установился на сервере, именно его теперь нужно использовать во всех исходящих операциях.
?>



Статус и приоритет
Для чего нужен статус и так все знают(в сети, занят, отсутствую и т.д.)
Но есть еще приоритет, который устанавливается в одной команде со статусом.
Приоритет нужен для того, чтобы понять какому ресурсу отдать предпочтение, если в сети несколько подключений одной учетной записи и если вам отправят сообщение без указания ресурса, то оно доставится на тот ресурс, у которого приоритет выше.
Такая схема смены статуса.

<presence>
<show>chat</show>
<status>Текстовое сообщение</status>
<priority>10</priority>
</presence>

В данном примере установлен статус chat и приоритет 10.
Статус задается в тегах show, возможны следующие варианты
away - Отошел,
chat - Готов чатиться (В сети),
dnd - Занят,
xa - Недоступен
Пустой элемент <show/> определяет статус контакта "В сети".


Хитрая авторизация Google Talk
Совершенно не удивительно, что гугл придумал свой механизм авторизации X-GOOGLE-TOKEN.
Пока вы не перейдете в защищенный режим, вам будет доступен только этот механизм авторизации, по мнению гугла в незащищенном потоке только их механизм является самым защищенным.
Если перейти в защищенное соединение, то дополнительно к этому механизму авторизации добавится еще и PLAIN.
Поэтому если нужно соединиться с Google Talk, вам обязательно нужно установить защищенное соединение и авторизоваться через sasl PLAIN.
Как происходит авторизация X-GOOGLE-TOKEN я не разобрался, не смог найти нужной информации.

 





Комментарии

RSS комментарии


30.11.2010 Tolik
спасибо за инфрмацию, очень подробно и доступно



01.02.2011 Marconi
спасибо огромное, просто супер!



21.06.2011 Nik
Спасибо! Очень помогло )



26.07.2011 Александр
Спасибо! понятно и по существу !



22.01.2012 Maix
Спасибо, то что искал !



12.03.2013 Wertoy
Респект за статью



05.08.2014 KIBor
Спасибо огромное! То что надо !!!



12.12.2016 BEEn
избыточно



Добавить свой комментарий


Ваше имя(* обязательно)


Текст сообщения(* обязательно)









 
 
  запомнить

 
Copyright © 2003-2017 WeBi Constructor
Rambler's Top100