Библиотека сайта rus-linux.net
Разрабатывем XMPP-клиент для Google Talk
Оригинал: Use XMPP to Create Your Own Google Talk ClientАвтор: Sarath Lakshman
Дата публикации: 29 Июня 2012 г.
Перевод: А.Панин
Дата публикации перевода: 13 октября 2012 г.
Системы мгновенных сообщений являются одной из основных составляющих организации социальных сетей и Интернет. Популярный клиент Google Talk, использующий протокол XMPP (Extensible Messaging and Presence Protocol), вывел этот протокол обмена мгновенными сообщениями в лидеры среди открытых стандартов. Изучение протокола XMPP (известного ранее как Jabber) доставляет удовольствие - он прозрачен и использует простейшую архитектуру. После того, как вы поймете это, вам не составит труда разрабатывать собственные клиенты для XMPP/Google Talk без использования сторонних программных компонентов на понятном и мощном языке программирования Python.
XMPP - открытый протокол, основанный на XML. Он использует XML для реализации всего цикла обмена мгновенными сообщениями. В основе XMPP лежит децентрализованная клиент-серверная архитектура, в которой сервер выступает посредником при передаче сообщений, а также выполняет такие функции, как создание учетных записей пользователей, аутентификация пользователей, хранение списка контактов, и.т.д. Поскольку в данной статье рассматриваются клиенты, не будем углубляться в подробности реализации серверной части - просто будем считать сервер "черным ящиком", службой работающей на узле с определенным IP-адресом/именем узла на определенном порту и отвечающую определенным образом на наши XMPP-запросы.
Для соединения с сервером в нашей программе используется TCP-сокет. Описание сетевых технологий, TCP/IP, IP-адресов, портов и сокетов выходит за рамки данной статьи из-за большого объема информации, поэтому в том случае, если вам не знакомы данные понятия, следует обратиться к статье в Wikipedia.
Так как мы разрабатываем клиент для Google Talk, будем использовать имя узла gmail.com и порт 5222, являющийся стандартным портом для XMPP.
- Клиент соединяется с сервером и отправляет идентификационные данные: имя пользователя и пароль.
- Сервер сравнивает предоставленные данные с информацией из базы данных и отправляет ответ клиенту.
- После успешной аутентификации, клиент получает от сервера ответ, содержащий информацию о присутствующих в сети собеседниках. Этот ответ представляет собой данные о присутствии в сети людей из списка контактов. Более подробное пояснение будет приведено ниже.
Как говорилось ранее, передача XML-потока между клиентом и сервером начинается сразу после установления сединения.
Перед тем, как мы приступим к программированию, давайте определимся с рядом терминов и принципов, тесно связанных с XMPP. Для разработки XMPP-клиента совсем не обязательно понимать структуру XML-потоков, служащих для обмена данными между клиентом и сервером в том случае, когда используются библиотеки, скрывающие сложности протокола и предоставляющие нам API. В данной статье используется язык программирования Python, для которого существует библиотека python-xmpp, предоставляющая отточенный API. Тем не менее, для понимания основ процесса обмена сообщениями и взаимодействия клиента и сервера, мы рассмотрим некоторые примеры.
Примечание: примеры XML-потоков, приведенные ниже, не являются точными копиями тех, что можно встретить в реальной жизни, поскольку я убрал атрибуты некоторых элементов и части потоков, которые не требуются для получения общего представления о XMPP. Поскольку библиотека python-xmpp способна обработать операции создания, отправки и приема XML-потоков, нет необходимости запоминать приведенные примеры, используйте их как иллюстрации процесса работы клиента, а не как код, который вам придется писать. |
Ресурс (Resource)
XMPP-клиент может работать на большом спектре оборудования, начиная с мобильных телефонов и встраиваемых устройств и заканчивая лэптопами и настольными компьютерами. Тип устройства, на котором работает клиент, может быть отображен в атрибуте "from" XML-потока, отправленного клиентом, эта информация и называется ресурсом. Атрибут "from" записывается в следующем формате: from="имя_пользователя@домен/ресурс". Например, клиент, работающий на мобильном телефоне под управлением Android, будет отправлять что-то наподобие "userid@gmail.com/Android". Этот идентификатор может быть весьма полезен: инструменты для администрирования могут разделять подключенные клиенты на группы, руководствуясь этим критерием. Сервер может корректировать свои ответы в зависимости от используемого ресурса - например, в случае соединения с мобильного телефона, длина ответов сервера, размер изображений и другие параметры могут быть сведены к минимуму для уменьшения времени на передачу данных через медленное GPRS-соединение.
Данная информация, предоставляемая клиентам из списка контактов, может также использоваться для отправки сообщений в зависимости от ресурсов. Например, я могу отправить сообщение "Hello Android guys!" всем людям в списке контактов, подключившимся с устройств под управлением Android, в то же время отправив "Hello, netbook guys!" всем подключившимся с нетбуков.
Станс (Stanza)
Станс является неделимой командой XMPP и одной из фундаментальных структур уровня протокола. Вы можете отправлять неограниченное число станс посредством установленного соединения между сервером и клиентом.
Как только аутентификация успешно пройдена и налажен обмен данными в формате XML между сервером и клиентом, клиент и сервер могут обмениваться (предоставляя возможность передачи мгновенных сообщений) тремя основными типами XML-станс - <message/>, <pesence/> и <iq/>. Рассмотрим их.
Уведомление о присутствии (Presence)
Несложно догадаться, что этот станс служит для доставки уведомления от пользовательского клиента к XMPP-серверу и клиентам из списка контактов о своем состоянии - находится ли пользователь в сети или нет. После процесса аутентификации, клиент отправляет серверу уведомление о присутствии, содержащее дополнительные данные, описывающие состояние клиента - сообщение о текущем состоянии, занят ли пользователь, доступен или отошел, ресурс, прозвище пользователя, название клиента и.т.д.
Когда сервер получает эту информацию, он отправляет копии станса всем клиентам из списка контактов пользователя. Если вы используете клиент Pidgin для работы с Gtalk, наведите указатель мыши на пользователя из списка контактов для того, чтобы увидеть часть этой информации.
<presence from="slynux@slynux.com/Android"> <show>xa</show> <status>Writing for LFY</status> </presence>
Сообщение (Message)
<message from="slynux@gmail.com/Home" to="slynuxguy@gmail.com" type="chat"> <body>Hey, Whats up ?</body> <subject>Query</subject> </message>
Запрос (IQ)
Станс IQ (Info/Ouery) является аналогом запросов GET и POST в протоколе HTTP. Мы используем IQ-запросы для получения информации от сервера и храним ответы для последующего использования. Если запрос некорректен или не может быть исполнен, сервер возвращает станс с сообщением об ошибке. Протокол XMPP устанавливает правило, по которому каждый iq-станс должен иметь атрибут "id", значение которого генерируется клиентом (или используемой библиотекой для работы с XMPP).
Этот атрибут устанавливается с таким же значением и в ответе сервера, что позволяет сопоставить полученные ответы с соответствующими iq-запросами (это удобно, в том случае, когда клиент отправляет несколько запросов друг за другом). Поскольку генерация уникальных значений атрибута "id" осуществляется на уровне библиотеки (в нашем случае python-xmpp), мы не будем заниматься этим.
<iq from="slynuxguy@gmail.com" id="7" to="slynuxguy@gmail.com/Pidgin" type="get"> <query xmlns="jabber:iq:roster"/> </iq>
<iq to="slynuxguy@gmail.com/Pidgin" id="7" type="result"> <query xmlns="jabber:iq:roster"> <item jid="user3@gmail.com/Home"/> <item jid="user4@gmail.com/Adium"/> <item jid="user4@gmail.com/Android"/> </query> </iq>
Вы можете заметить несколько записей для пользователя user4 - "user4@gmail.com/Adium" и "user4@gmail.com/Android". Adium и Android - названия ресурсов, как обсуждалось ранее. Это говорит о том, что user4 подключен к серверу с двух клиентов одновременно.
Ростер (Roster)
Под ростером в протоколе XMPP понимается список контактов, содержащий атрибут присутствия для каждого контакта. Ваш ростер содержит список идентификаторов пользователей Jabber (называемых JID) и состояние каждого из пользователей, разрешивших вам получать их статус. Когда вы подключаетесь, ваш клиент оповещает сервер о вашем присутствии в сети, а сервер в свою очередь делает все остальное - оповещает людей из вашего списка контактов о вашем присутствии и получает их статусы для обновления данных в списке контактов вашего клиента. Ваш ростер обновляется тогда, когда вы отправляете уведомление о присутствии.
Ниже приведен пример XML-потока, комбинирующего три типа станс в сессии обмена данными с сервером. Обратите внимание, что это не вывод отладочной информации - в случае вывода отладочной информации сообщения бы были намного подробнее. Это просто структурный пример внутреннего представления XML-потока, предназначенный для демонстрации обмена данными между клиентом и сервером, взятый из документации XMPP. В этом примере клиент (обозначенный "C:" в примере) прошел аутентификацию на сервере gmail.com как user5@gmail.com.
C:<stream:stream> C: <presence/> C: <iq type="get" id="1"> <query xmlns="jabber:iq:roster"/> </iq> S: <iq type="result" id="1" > <query xmlns="jabber:iq:roster"> <item jid="user1@gmail.com"/> <item jid="user2@gmail.com"/> <item jid="user3@gmail.com"/> </query> </iq> C: <message from="user5@gmail.com" to="user2@gmail.com"> <body>Hello world!</body> </message> S: <message from="user3@gmail.com" to="user5@gmail.com"> <body>Kudos to you</body> </message> C: <presence type="unavailable"/> C:</stream:stream>
Можно привести много примеров запросов, таких как: добавление контакта, удаление контакта, открытие группового чата, и.т.д. Все они осуществляются при помощи XML-потока. Протокол XMMP включает в себя метод для защиты потока данных от вмешательства и прослушивания. Этот метод шифрования данных называется TLS (Transport Layer Security) с расширением "STARTTLS", которое разработано на основе расширений для протоколов IMAP, POP3 и ACAP.
Займемся кодом клиента
Теперь подумаем, как мы можем разработать свой собственный XMPP-клиент для Google Talk с нуля, используя простой и мощный язык программирования Python с удивительными возможностями стандартной библиотеки, сформированной по принципу "все включено" ("batteries included"). В дополнение к этому существует множество отдельных устанавливаемых расширений и библиотек для Python - практически для всего того, что можно сделать с использованием других языков.
$ sudo apt-get install python-xmpp
$ wget http://downloads.sourceforge.net/project/xmpppy/xmpppy/0.5.0-rc1/xmpppy-0.5.0rc1.tar.gz?use_mirror=nchc $ tar -xzvvf xmpppy-0.5.0rc1.tar.gz $ cd xmpppy-0.5.0rc1 $ sudo python setup.py install
#!/usr/bin/env python import xmpp user="username@gmail.com" password="password" server="gmail.com" jid = xmpp.JID(user) connection = xmpp.Client(server,debug=[]) connection.connect() result = connection.auth(jid.getNode(), password,"LFY-client") connection.sendInitPresence() while connection.Process(1): pass
$ chmod a+x base.py $./base.py
Теперь запустим Pidgin и этот клиент одновременно. Используйте две учетные записи Gtalk для одновременной работы Pidgin и данного клиента. После запуска base.py, наведите курсор мыши на контакт, используемый нашим клиентом (учетная запись пользователя, под которой произошла аутентификация). Проверьте статус - "LFY-client" - статус, установленный нашим клиентом. Попробуйте отправить сообщение нашему клиенту - конечно же, он не ответит, так как он пока еще не достаточно проработан и мы не задали действие на случай получения сообщения.
Теперь давайте попробуем установить, как выглядят XML-потоки. Включите отладку: замените строку connection = xmpp.Client(server,debug=[]) на connection = xmpp.Client(server), удалив параметр debug=[]. Теперь при запуске клиента можно видеть XML-поток, как показано на рисунке 1.
Рисунок 1: Отладочная информация (XML-поток) в окне терминала
Разработка бота для GTalk
Вы наверняка использовали или сталкивались с ботами GTalk, отвечающими автоматически на приходящие сообщения - примером могут служить боты для транслитерации Google. Если мы добавим бот транслитерации в список контактов и отправим ему фонетическое слово, он отправит в ответ транслитерированное слово в кодировке Unicode на языке Хинди. Несложно разработать подобный бот в том случае, если есть отдельная программа для транслитерации.
#!/usr/bin/env python import xmpp user="username@gmail.com" password="password" server="gmail.com" def message_handler(connect_object, message_node): message = "Welcome to my first Gtalk Bot :)" connect_object.send( xmpp.Message( message_node.getFrom() ,message)) jid = xmpp.JID(user) connection = xmpp.Client(server) connection.connect() result = connection.auth(jid.getNode(), password, "LFY-client") connection.RegisterHandler('message', message_handler) connection.sendInitPresence() while connection.Process(1): pass
Здесь функция connection.RegisterHandler('message',message_handler) используется для указания, что функция message_handler() должна быть вызвана при приеме станса сообщения. Два аргумента, передаваемых функции - это объект соединения и станс сообщения. Используя функцию getFrom(), мы получаем JID пользователя, отправившего сообщение и отправляем сообщение этому пользователю. Для получения текста присланного сообщения, используется функция message_node.getBody().
Бот для удаленного выполнения команд
def message_handler(connect_object,message_node): command = str(message_node.getBody()) process = subprocess.Popen(command,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE) message = process.stdout.read() if message=="": message=process.stderr.read() connect_object.send( xmpp.Message( message_node.getFrom() ,message))
Примечание: Необходимо добавить строку import subprocess в начало файла программы, поскольку этот модуль сейчас используется в функции message_handler(). |
В этом обработчике сообщений мы получаем текст сообщения, принятого от отправителя, и выполняем как команду, используя модуль Python под названием "subprocess". Мы проверяем дескрипторы файлов process.stdout (стандартный вывод дочернего процесса) на наличие данных, затем process.stderror (стандартный вывод сообщений об ошибках дочернего процесса). После этого данные, возвращенные дочерним процессом отсылаются отправителю сообщения.
Как и раньше, попробуйте с помощью Pidgin (работающей с учетной записью другого пользователя) отправить боту команды, такие как ls, cat /proc/cpuinfo, и.т.д. Вы должны увидеть ожидаемые результаты исполнения этих команд в виде ответных сообщений в окне Pidgin.
def message_handler(connect_object,message_node): admin = "admin_user@gmail.com" from_user = message_node.getFrom().getStripped() if admin == from_user: # allow to execute command only if admin requested command = str(message_node.getBody()) process = subprocess.Popen(command,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE) message = process.stdout.read() if message=="": message=process.stderr.read() else: message="Access denied!\nContact system admin" connect_object.send( xmpp.Message( message_node.getFrom() ,message))
Поиск невидимых пользователей
Большинство протоколов мгновенных сообщений поддерживают невидимость пользователей, поэтому каждый желающий может находиться в сети без уведомления лиц из списка контактов о статусе. В этом отношении GTalk не является исключением. Когда я начал разбираться в протоколе XMPP, я отметил интересную вещь в его реализации в рамках GTalk: невидимость реализована только на стороне клиента, а не на стороне сервера. Мы можем легко обнаружить таких невидимых, но подключенных пользователей в нашем списке контактов, просто исследуя сообщения о присутствии. (Как было объяснено ранее, когда клиент подключается к серверу/сети, отправляется уведомление о присутствии.)
#!/usr/bin/python -W ignore::DeprecationWarning import xmpp user="user@gmail.com" password="password" server="gmail.com" def presenceHandler(conn, presence): if presence: if presence.getType() == "unavailable": print presence.getFrom().getStripped() print "Invisible users:" jid = xmpp.JID(user) connection = xmpp.Client(server,debug=[]) connection.connect() result = connection.auth(jid.getNode(), password,"Client Name") connection.RegisterHandler('presence',presenceHandler) connection.sendInitPresence() while connection.Process(1): pass
Разработка графического интерфейса для клиента
До этого момента наш простейший клиент GTalk, разработанный на языке Python с применением модуля поддержки XMPP был программой с интерфейсом командной строки, которую необходимо запускать в терминале. Вы можете разработать графический интерфейс с целью придания вашему клиенту привлекательности для пользователя и улучшения пользовательских качеств. Две наиболее популярных библиотеки для создания графических пользовательских интерфейсов на Python - это Qt и GTK. Во время разработки графического интерфейса нужно помнить о том, что необходимо работать с XMPP и графическим интерфейсом в отдельных потоках. Функция connection.Process(1) должна вызываться в бесконечном цикле.
В приведенных выше программах мы использовали для этой цели цикл while. В случае работы с Qt или GTK работа с окнами осуществляется в цикле приема событий (event loop) - поэтому использование еще одного бесконечного цикла внутри цикла приема событий приведет к потере отзывчивости интерфейса или к тому, что окна перестанут реагировать на события. Напротив, использование модуля для поддержки потоков в Python позволит выполнять работу с протоколом XMPP в отдельном потоке.
Надеюсь, вам понравилось работать с Google Talk и XMPP. Удачи в разработках и до встречи.