Начинаем писать Jabber-клиент на Delphi. Jabber - система для быстрого обмена сообщениями и информацией о присутствии (в контакт-листе) между любыми двумя пользователями Интернета на основе открытого протокола XMPP.
В отличии от той же Аськи Jabber-сеть имеет на мой взгляд более развитые возможности, а наличие расширений протокола открывает горизонты функциональности на недосягаемые для коммерческих IM-сетей, вот некоторые из них:
Открытость: протокол Jabber открыт, общедоступен и достаточно лёгок для понимания; существует множество реализаций серверов и клиентов, а также библиотек с открытым исходным кодом.
Расширяемость: с помощью пространств имён в XML можно расширить протокол Jabber для выполнения требуемых задач и для обеспечения поддержки взаимодействия между различными системами. Общие расширения разрабатываются под контролем Jabber Software Foundation.
Децентрализованность: кто угодно может запустить свой собственный сервер Jabber, что позволяет организациям и частным лицам заниматься любыми экспериментами с IM.
Безопасность: любой сервер Jabber может быть изолирован от общедоступной сети Jabber, многие из вариантов реализации сервера используют SSL при обмене между клиентом и сервером, и немало[источник не указан 39 дней] клиентов поддерживают шифрование с помощью PGP/GPG внутри протокола.
Jabber удовлетворяет многие потребности частных лиц и организаций. Но важно понимать, что он не является универсальным решением всех задач. В частности, Jabber не является:
Универсальным чат-клиентом для различных систем IM - несмотря на множество клиентов Jabber под различные платформы, они не предоставляют таких возможностей по взаимодействию с различными системами IM, которые обеспечиваются программами Miranda IM, Trillian или Pidgin: вместо этого взаимодействие между Jabber и другими системами осуществляют шлюзы, расположенные на стороне сервера.
Универсальным решением проблем взаимодействия с различными IM-системами - некоторые сервера Jabber предоставляют возможность взаимодействия с другими системами IM через шлюзы, которые транслируют протокол Jabber в протокол этих систем; однако только от самих систем зависит осуществление взаимодействия (к чему они подчас не стремятся, и даже наоборот).
Основные сведения о протоколе XMPP
В основе протокола XMPP (eXtensible Messaging and Presence Protocol) лежит язык XML. XMPP является открытым, свободным протоколом для мгновенного обмена сообщениями и информацией о присутствии в режиме околореального времени.
Изначально спроектированный легко расширяемым протокол помимо передачи текстовых сообщений поддерживает передачу голоса и файлов по сети.
Данный протокол принят как стандарт RFC.
Стандартный порт для Jabber-клиентов - 5222.
Протокол регламентируется следующими стандартами:
RFC 3920 - Extensible Messaging and Presence Protocol (XMPP): Core
RFC 3921 - Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence
Следует также отметить, так как протокол является текстовым, а не бинарным соответственно у этого протокола есть слабые стороны, а именно: избыточность передаваемой информации, отсутствие возможности передачи двоичных данных приводит к использованию различных способов перекодировки. В результате этого, для передачи файлов приходится использовать дополнительные протоколы, например HTTP. Если этого не избежать, то XMPP обеспечивает встроенную передачу файлов кодируя информацию используя base64. Другая двоичная информация, такая как закодированный разговор или графические иконки включаются с использованием такого же метода. Однако прежде чем двигаться дальше рассмотрим адресацию пользователей с Jabber-сетях.
Адресация пользователей в Jabber
Каждый пользователь в сети имеет уникальный идентификатор, адрес - Jabber ID (сокращённо JID). Во избежание необходимости существования сервера с полным списком всех адресов, JID подобно адресу электронной почты содержит имя пользователя (JID node) и DNS-адрес сервера (JID domain), на котором зарегистрирован пользователь, разделённые знаком (@). Например, пользователь user, зарегистрированный на сервере example.com, будет иметь следующий адрес (JID):
user@example.com.
Также пользователь может подключаться, находясь в разных местах, сервер позволяет определять дополнительное значение, называемое ресурсом, который идентифицирует клиента пользователя в данный момент. Так можно включить в адрес пользователя (JID) имя его ресурса (JID resource), добавив через слэш в конце адреса.
К примеру, пусть полный адрес пользователя будет
user@example.com/work, тогда сообщения, посланные на адрес
user@example.com, дойдут на указанный адрес вне зависимости от имени ресурса, но сообщения для
user@example.com/work дойдут на указанный адрес только при соответствующем подключённом ресурсе.
Адреса (JID) могут также использоваться без явного указания имени пользователя (с указанием имени ресурса или без такового) для системных сообщений и для контроля специальных возможностей на сервере.
Запомним эту информацию, она нам пригодятся в дальнейшем.
Структура XML-пакетов Jabber протокола (XML Streams)
Структура XML пакетов получаемых с сервера и передаваемых на него по спецификации RFC 3920 имеет следующий вид:
Код:
/--------------------/
/ <stream> /
/--------------------/
/ <presence> /
/ <show/> /
/ </presence> /
/--------------------/
/ <message to='foo'> /
/ <body/> /
/ </message> /
/--------------------/
/ <iq to='bar'> /
/ <query/> /
/ </iq> /
/--------------------/
/ ... /
/--------------------/
/ </stream> /
/--------------------/
Как вы видите, на схеме представлена иерархическая структура XML подразделенная на следующие элементы, так называемый поток XML и элементы строф XML.
Поток XML - является контейнером для хранения элементов строф XML. Поток XML начинается с открытия тэга <STREAM> (с соответствующими атрибутами и пространством имен), конец потока XML заканчивается закрытием тега </STREAM>. Во время обмена с сервером, клиент и сам сервер может посылать неограниченное количество элементов строф в потоке XML.
Строфы XML - это дискретные семантические модули представленные элементами, заключенными в потоке XML. Строфы XML являются дочерними элементами (child node) корня XML <STREAM>. Начало любой строфы XML обозначено началом элемента (например, <PRESENCE>), конец строфы XML обозначен завершающим тегом (</PRESENCE>). В примере строфы XML: <PRESENCE>, <MESSAGE>, <IQ>. Каждая строфа XML представляет собой конкретную информацию, так например строфа <MESSAGE> представляет сообщение, а <IQ> информационный запрос. Более подробно строфы будут рассмотрены далее.
Примечание: несмотря на стандарт, мной было замечено, что с некоторых серверов могут приходить пакеты, просто содержащие строфы XML, но включенные в поток XML.
Атрибуты элементов XML
При приходе XML у тегов могут быть следующие основные атрибуты:
to - кому (JID).
From - откуда (JID).
Id - уникальный идентификатор, так называемый атрибут 'системы обнаружения атак'. Позволяет конкретно идентифицировать полученные данные. Рекомендовано делать его случайным. Но в принципе это не обязательно.
xml:lang - текущий язык, кодировка данных.
Version - версия.
Теги могут включать также и дополнительные атрибуты, зависящие от передоваемых данных.
Пример строфы <PRESENCE> с некоторыми атрибутами Вы можете увидеть ниже:
Код:
<presence from='delphi-test@jabber.ru/основная'
to='delphi-test@jabber.ru/резервная'>
<show/>
</presence>
Пространства имен XML
Так как первоначально XMPP был задуман, как протокол, поддерживающий расширения, перед разработчиками встал вопрос, как можно реализовать данные расширения, не внося коррективы в основной протокол. И решение нашлось. Это решение - пространство имен, довольно известное в XML.
Пространство имён в XML - именованная совокупность имён элементов и атрибутов, служащая для обеспечения их уникальности в XML-документе. Все имена элементов в пределах пространства имён должны быть уникальны. Таким образом, реализуется различение одинаковых элементов XML или атрибутов. Для клиентов Jabber зарезервировано пространство имен "jabber:client"
Пространства имён объявляются с помощью зарезервированного XML атрибута xmlns, значение которого является названием пространства имен.
Например, элемент <QUERY> описанный пространством имен 'jabber:iq:roster' выглядит так:
Код:
<query xmlns='jabber:iq:roster'>
Подготовка
Сразу оговорюсь, что я не ставлю перед собой задачу написать полноценно работающий клиент соответствующий полному стандарту XMPP. Слишком большой труд, скажем так, однако основные методы работы с XMPP будут включены в мой исходный компонент.
В качестве основы для работы клиента мной были взяты наработки по работе с WinSock Alex-а Demchenko, используемые им в TICQClient, немного портированные, кое-где измененные и дополнительно комментированные мной, для нашего демо-клиента.
В качестве парсера XML мной был взят TjanXMLParser2, благо он бесплатный, довольно быстрый. Стандартный парсер MSXML был мной отброшен по причине, того, что некоторые XML-пакеты приходили синтаксически неправильные, что начисто отрубало желание этого парсера работать с ними.
Что касается приведенных далее листингов обмена протоколом, я постарался описать самые интересные части, если у вас кое-где возникнут вопросы, подробнее вы можете узнать в RFC. Все 800 основного RFC страниц я не смогу Вам подробно изложить, но критические места постараюсь.
Также сразу оговорюсь, что наш пример не будет поддерживать шифрование, то есть данные будут передаваться в открытом виде. Сделано это для упрощения понимания примера. Вышло, то, что вышло, а хорошо иль плохо получилось судить Вам, уважаемые коллеги.
Итак, для тестирования нашего примера, мной был зарегистрирован на сайте jabber.ru аккаунт
delphi-test@jabber.ru с паролем delphi-test. Эти данные нам понадобятся для разбора протокола обмена между сервером jabber (далее - Сервер) и нашим клиентом (также - Клиент) далее.
Прохождение аутенфикации
Итак, первым действием при соединении с сервером Jabber, которым должен выполнить наш клиент - является аутенфикация. Аутенфикация будет происходить используя механизм SASL аутенфикации, описанный в в "RFC 2831 - Using Digest Authentication as a SASL Mechanism", алгоритм работы который будет рассмотрен подробнее, чуть далее.
Итак, мы установили физическое соединение с сервером, теперь нам нужно пройти аутенфикацию, для этого клиент посылает серверу следующий пакет:
Код:
<?xml version='1.0' encoding='UTF-8'?>
<stream:stream to='jabber.ru' xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams' xml:l='ru' version='1.0'>
В ответ сервер пришлет подтверждение, о рукопожатии:
Код:
<?xml version='1.0'?>
<stream:stream xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='3966489307'
from='jabber.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>DIGEST-MD5</mechanism>
<mechanism>PLAIN</mechanism></mechanisms>
<register xmlns='http://jabber.org/features/iq-register'/>
</stream:features>
Что мы видим в пакете, видим, что сервер поддерживает zip компрессию при передаче пакетов, поддерживает механизм аутенфикации DIGEST-MD5, и другие возможности. Стоит также отметить, что возможности сервера зависят от самого сервера и в зависимости от программы могут изменяться. Подробнее вы можете узнать в RFC 3920. Однако нас интересует то, что сервер поддерживает механизм аутенфикации DIGEST-MD5. Отлично, скажем мы и отправим ему пакет, говорящий о том, что мы хотим пройти аутенфикацию используя механизм DIGEST-MD5.
Код:
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
После получения данного пакета сервер присылает нам, так называемый challenge-пакет:
Код:
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
bm9uY2U9IjIyNjQ3NzQ4Iixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz
</challenge>
Данный пакет мы должны будем разобрать. Как это сделать? Ранее внимательный читатель обратил внимание, что некоторые пакеты могут передаваться в кодировке Base64. Это наш случай. Текстовый элемент содержит информацию в данной кодировке, которая после раскодирования примет следующий вид:
Код:
nonce="22647748",qop="auth",charset=utf-8,algorithm=md5-sess