Информационный портал

Информационно-новостной портал

Новости рекламы и маркетинга. Новости Интернета.
Море полезной информации на сайте RetailDepartment.ru
Поделиться ссылкой с друзьями

Jabber и PHP

Twitter
Нравится
RedLine


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 (например от This email address is being protected from spambots. You need JavaScript enabled to view it.)
Далее, после вашего приветствия сервер должен ответить примерно так

<?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>This email address is being protected from spambots. You need JavaScript enabled to view it./webi</jid></bind></iq>
Сервер показывает, какой получился полный JID (This email address is being protected from spambots. You need JavaScript enabled to view it./webi)
Полный JID не всегда будет таким как вы ожидаете. Например, talk google добавляет к имени ресурса еще случайную строку и по алгоритму гугла полный JID в этом случае мог бы получиться This email address is being protected from spambots. You need JavaScript enabled to view it./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='This email address is being protected from spambots. You need JavaScript enabled to view it./webi' id='ask_version' type='get'><query xmlns='jabber:iq:version'/></iq>
<iq from='ya.ru' to='This email address is being protected from spambots. You need JavaScript enabled to view it./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='This email address is being protected from spambots. You need JavaScript enabled to view it.' to='This email address is being protected from spambots. You need JavaScript enabled to view it./webi' id='3' type='result'>
<query xmlns='jabber:iq:roster'>
<item subscription='both' name='Иванова' jid='This email address is being protected from spambots. You need JavaScript enabled to view it.'><group>Имя группы</group></item>
<item subscription='both' name='Петров' jid='This email address is being protected from spambots. You need JavaScript enabled to view it.'/>
<item subscription='both' jid='This email address is being protected from spambots. You need JavaScript enabled to view it.'/>
<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 пустых строк.
И вот эту функцию и будем использоваться для получения ответов от сервера.

Пример демонстрирую опять же на яндексе, почтовый ящик This email address is being protected from spambots. You need JavaScript enabled to view it.

<?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); // после выхода в онлайн здесь будут получены офлайн сообщения и дополнительная информация по статусам ваших контактов.

// теперь можно отправить сообщение например для контакта This email address is being protected from spambots. You need JavaScript enabled to view it.
// в поле from указываете полный JID вместе с ресурсом(он должен быть получен в ответе сервера при установке ресурса), в поле to - кому адресовано сообщение, если ресурс не известен, можно без указания ресурса.
$xml = '<message type="chat" from="This email address is being protected from spambots. You need JavaScript enabled to view it./webi" to="This email address is being protected from spambots. You need JavaScript enabled to view it." 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 вместе с ресурсом (This email address is being protected from spambots. You need JavaScript enabled to view it./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 состоит из имени юзера, домена и ресурса. Например ( This email address is being protected from spambots. You need JavaScript enabled to view it./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 я не разобрался, не смог найти нужной информации.

 


Комментарии Jabber и PHP RSS комментарии

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



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

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

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

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

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

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

Источник: http://webi.ru/webi_articles/xmpp_php.html

Add comment

Правила добавления комментариев


Security code
Refresh

Download SocComments v1.3