Jabber и PHP
Раздел:
Programming /
PHP
@
03.05.2010 |
Ключевые слова: xmpp протокол jabber
Автор: WeBi
В этой статье расскажу, как можно работать с протоколом 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://www.rusdoc.ru/go.php?http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="ya.ru" xml:lang="en" xmlns:xml="http://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="ya.ru" xml:lang="en" xmlns:xml="http://www.rusdoc.ru/go.php?http://www.w3.org/XML/1998/namespace"> Если связь по защищенному соединению удалась и сервер получил привествие, ответ будет таким
<?xml version=`1.0`?> <stream:stream xmlns=`jabber:client` xmlns:stream=`http://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="ya.ru" xml:lang="en" xmlns:xml="http://www.rusdoc.ru/go.php?http://www.w3.org/XML/1998/namespace"> Сервер отвечает и показывает, какие действия доступны уже после авторизации
<?xml version=`1.0`?> <stream:stream xmlns=`jabber:client` xmlns:stream=`http://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="`.$domain.`" xml:lang="en" xmlns:xml="http://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="`.$domain.`" xml:lang="en" xmlns:xml="http://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="`.$domain.`" xml:lang="en" xmlns:xml="http://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?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://www.rusdoc.ru/go.php?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 я не разобрался, не смог найти нужной информации.
Ссылки по теме:
Готовый PHP класс для работы с jabber Описание протокола XMPP (jabber)
Вернуться в раздел:
Programming /
PHP
|