Предыстория
Как-то в одном проекте, представляющим собой централизованную систему управления программными модулями, установленными на разных рабочих станциях в сети, понадобилось реализовать рассылку уведомлений по протоколу XMPP (также известному как Jabber).
Из доступных готовых решений на платформе .NET Framework обнаружилась библиотека agsXMPP, которая позволяет решить поставленную задачу. Однако из-за некоторых ограничений лицензии, а также доступности свободного времени и наличия желания, было принято в качестве пищи для ума написать свою библиотеку, позволяющую выполнять необходимые для проекта действия, а именно уметь рассылать конкретным адресатам автоматически сгенерированные сообщения.
Библиотека была написана, однако в дальнейшем, по мере изучения технологий и желания применить их для закрепления новых навыков, было принято решение переписать код с использованием Windows Workflow Foundation. Автору показалось, что такие действия, как аутентификация с помощью механизма Digest SASL (RFC 2831) неплохо будет смотреться, если представить ее шаги в виде определенных Activity.
Немного теории
Как говорит википедия, XMPP-протокол (Extensible Messaging and Presence Protocol расширяемый протокол обмена сообщениями и информацией о присутствии) — это основанный на XML открытый, свободный для использования протокол для мгновенного обмена сообщениями и информацией о присутствии (см. список контактов) в режиме, близкому к режиму реального времени. Изначально спроектированный легко расширяемым, протокол, помимо передачи текстовых сообщений, поддерживает передачу голоса, видео и файлов по сети.
В отличие от коммерческих систем мгновенных сообщений, таких, как AIM, ICQ, MSN и Yahoo, XMPP является децентрализованной, расширяемой и открытой системой. Любой желающий может открыть свой сервер мгновенных сообщений, регистрировать на нём пользователей и взаимодействовать с другими серверами XMPP.
В тоже время, Windows Workflow Foundation – представляет собой технологию компании Microsoft для определения, выполнения и управления рабочими процессами.
WF – это специальная среда исполнения, которая управляет выполнением программ, состоящих из возобновляемых программных «блоков», называемых Activity.
Реализация
Проект состоит из двух сборок и одного xolm-файла: JabberClient.dll (пространство имен Jabber.Client), WorkflowActivities.dll (пространство is Workflow) и Sequence.xml
Пространство Jabber.Client
Пространстов имен Jabber.Client включает в себя классы, необходимые для реализации XMPP-клиента с минимальной функциональностью:
- Jabber.Client.Core.TcpBinder обертка стандартного класса System.Net.Sockets.Socket
- Jabber.Client.Core.ClientState перечисляет возможные состояния соединения клиента
- Jabber.Client.Xml.PacketBuilder влючает методы для формирования запросов к XMPP-серверу (например, аутентификация, получение списка контактов, сообщение и т.д.)
- Jabber.Client.Xml.PacketParser включает методы для обработки ожидаемых ответов от XMPP-сервера
- Jabber.Client.Cryptography.DigestMD5Auth реализует аутентификацию с использованием механизма Digest SASL согласно RFC 2831
- Jabber.Client.JabberClient инкапсулирует определенную логику XMPP клиента.
Namespace Worflow
Пространство имен Workflow содержит множество объектов Activity и вспомогательных классов, с помощью которых конструируется алгоритм исполнения программы. Основные из них перечислены ниже:
- Workflow.Activities.Receiver получает сообщение из очереди, обрабатывает его с помощью метода ReceiveMethod класса ReceiveType сборки AssemblyFile и вызывает метод OnReceived интерфейса IWorkflowDataService
- Workflow.Activities.Sender Sender получает сообщение из очереди, обрабатывает его с помощью метода SendMethod класса SendType сборки AssemblyFile и вызывает метод Send интерфейса IWorkflowDataService
- Workflow.Activities.StateSendAndReceive посылает сообщение, ожидает ответа и обрабатывает ответ. Для своей работы использует следующие свойства: ReceiveMethod, ReceiveType, AssemblyFile, SendMethod, SendType и Context. Свойство Context предназначено для использования разными Activity.
- Workflow.Activities.Sequence – последовательно выполняет дочерние Activity
- Workflow.Activities.Switch — эмулирует инструкцию Switch.
XOML-файл XMPP-клиента представлен ниже:
<Sequence x:Name="root"
xmlns="http://Workflow/Activities"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<Switch x:Name="sw1">
<StateSequence x:Name="Auth" Queue="AuthInputQueue">
<StateSendAndReceive Name="step1_0" AssemblyFile="JabberClient.dll"
SendType="Jabber.Client.Xml.PacketBuilder" SendMethod="AuthStreamHeader"
ReceiveType="Jabber.Client.Xml.PacketParser" ReceiveMethod="AuthStreamHeader1"
Context="{wf:ActivityBind Auth, Path=Context}" />
<StateSendAndReceive Name="step1_1" AssemblyFile="JabberClient.dll"
ReceiveType="Jabber.Client.Xml.PacketParser" ReceiveMethod="AuthStreamHeader2"
Context="{wf:ActivityBind step1_0, Path=Context}" />
........
</StateSequence>
<Receiver x:Name="Receive" Queue="ReceiveQueue" AssemblyFile="JabberClient.dll"
ReceiveType="Jabber.Client.Xml.PacketParser" ReceiveMethod="ResponseMessage" />
<Sender x:Name="Send" Queue="SendQueue" AssemblyFile="JabberClient.dll"
SendType="Jabber.Client.Xml.PacketBuilder" SendMethod="ChatMessage"/>
<Sender x:Name="Status" Queue="StatusQueue" AssemblyFile="JabberClient.dll"
SendType="Jabber.Client.Xml.PacketBuilder" SendMethod="PresenceShow"/>
<Sender x:Name="Roster" Queue="RosterQueue" AssemblyFile="JabberClient.dll"
SendType="Jabber.Client.Xml.PacketBuilder" SendMethod="Roster"/>
<Sender x:Name="Subscribe" Queue="RosterQueue" AssemblyFile="JabberClient.dll"
SendType="Jabber.Client.Xml.PacketBuilder" SendMethod="Subscribe"/>
<Sender x:Name="Unsubscribe" Queue="RosterQueue" AssemblyFile="JabberClient.dll"
SendType="Jabber.Client.Xml.PacketBuilder" SendMethod="Unsubscribe"/>
</Switch>
</Sequence>
Здесь следует обратить на то, как выполняется блок аутентификации: он состоит из нескольких Activity, которые выполняются строго в определенном порядке и используют динамическую привязку свойства Context для передачи необходимых данных между ними (таких, как nonce, AuthMechanism и других).
Пример использования XMPP-клиента:
....
using Jabber.Client;
using Jabber.Client.Core;
....
using (JabberClient jabber =
new JabberClient("jabber.org", "test", "test"))
{
//subscribe to events
jabber.Authentificated += new EventHandler(jabber_Authentificated);
jabber.RosterReceived += new RosterHandler(jabber_RosterReceived);
jabber.MessageReceived += new MessageHandler(jabber_MessageReceived);
Console.WriteLine("Authentication...");
jabber.Auth();
string s = Console.ReadLine();
Console.WriteLine("Client state={0}", jabber.State);
while (!string.IsNullOrEmpty(s))
{
if (jabber.State == ClientState.Authenticated)
{
Contact contact = new Contact("", "veleslaff@jabber.ru");
jabber.AddToContacts(contact); //Add to contacts
jabber.SetStatusForContact(contact, UserStatus.Online);//Set status
jabber.Send(contact, s); //Send message
jabber.Contacts(); //Get roster list
}
s = Console.ReadLine();
}
}
....
Послесловие
Основной код взаимодействия с XMPP-сервером написан довольно-таки давно, с тех пор практического опыта прибавилось. Также последняя реализация написана в качестве пищи для ума в свободное время, поэтому там очень много недостатков, которые исправляются потом и кровью при реальном использовании. Следует отметить следующие недостатки:
- плохая обработка ошибок
- слабая реализация событийной модели
- «некрасивая» реализация Jabber.Client.Xml.
Исходники и статья на английском доступны codeproject.com