I. Qu'est-ce que le multicasting ?▲
Vous avez surement entendu ce terme lors d'une discussion entre deux administrateurs réseaux, mais entre développeurs, c'est déjà beaucoup moins probable. Dans la langue de Molière je devrais plutôt utiliser le mot multidiffusion, mais si l'on commence à tout traduire on ne va plus rien y comprendre ! Pour faire simple, le multicasting, c'est envoyer une information simultanément à plusieurs destinataires. Que ce soit en réseau ou en développement la signification reste la même, nous allons diffuser un message (un paquet pour nos amis administrateurs réseaux) simultanément vers plusieurs destinataires.
II. WCF 4 et le multicasting▲
WCF, depuis sa version 4 intègre le " Routing Service " qui permet, entre autre, de faire du multicasting de message, c'est-à-dire qu'il est capable de recevoir un message venant d'un client et de l'envoyer à plusieurs services en même temps, sur des ports et avec des bindings qui peuvent être différents. Voici un schéma qui montre une utilisation possible du multicasting avec WCF 4.
Ce multicasting peut être pratique dans un scénario d'archivage de messages, mais pas seulement. Un autre scénario possible pourrait être le suivant : vous travaillez sur un site de e-commerce et vous devez développer un module qui va indiquer qu'une commande est prête à être expédiée. Cette dernière information intéresse plusieurs personnes/services bien différents :
- Le service comptabilité qui doit prélever le montant de la commande sur le compte du client
- Le service logistique qui doit prévenir le transporteur qu'il a un colis à expédier
- Le client a qui vous devez envoyer un email pour l'informer de l'expédition imminente de ses achats
Il est très probable que ces trois actions différentes imposent d'envoyer un message à des modules distincts basés sur des technologies distinctes et qui, potentiellement se trouvent sur des réseaux différents. Dans un tel scénario le multicasting de message à la sauce WCF 4 peut se révéler très utile et évite de coder des usines à gaz. D'autant plus que si demain ce message doit être envoyé à un quatrième destinataire, avec le " Routing Service vous n'aurez rien à recoder, seulement un fichier de configuration à modifier !
III. Implémentation du multicasting avec WCF 4▲
Implémenter le multicasting avec WCF 4 est simple puisqu'il ne repose que sur le fichier de configuration de votre service de routage, app.config ou web.config suivant le type de l'application qui héberge le service.
Vos services doivent implémenter le même contrat puisque c'est un même message qui va être envoyé à plusieurs services, il faut donc que ces services " parlent tous le même langage ".
using
System;
using
System.
Collections.
Generic;
using
System.
Linq;
using
System.
Runtime.
Serialization;
using
System.
ServiceModel;
using
System.
Text;
namespace
ServiceDeveloppez
{
[ServiceContract]
public
interface
IService
{
[OperationContract(IsOneWay = true)]
void
SendMessage
(
string
message);
}
}
Comme expliqué dans un précédent article sur le Routing Service, la magie s'opère par le biais d'un contrat spécifique qui va permettre de mettre en place le routage à proprement parlé. Ces contrats de routage sont aux nombres de quatre :
- IDuplexSessionRouter
- IRequestReplyRouter
- ISimplexDatagramRouter
- ISimplexSessionRouter
Ils permettent de couvrir des scénarios de services qui utilisent par exemple des sessions, font du callback... Pour les besoins de notre démonstration nous allons utiliser ISimplexDatagramRouter. Je vous renvoie à cette page MSDN pour prendre connaissance des spécificités de chacun de ces contrats.
III-A. Création des services▲
Nous allons créer quatre services utilisant différents types de bindings : basicHttpBinding, netTcpBinding et wsHttpBinding. Ces quatre services sont implémentés dans la même interface (contrat) qui se trouvera dans une dll pour plus de simplicité. Ce contrat ne comportera qu'une seule méthode pour plus de simplicité mais il n'y a aucune limitation provoquée par le " Routing Service ".
Ces quatre services exposeront donc la même méthode, contrat commun oblige. Attention aux problèmes de droit est de firewall, en effet, dès que l'on commence à s'amuser avec TCP Windows devient plus exigeant en matière de gestion des ports (et c'est bien mieux ainsi).
Voici à quoi devraient ressembler vos fichiers de configurations :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name
=
"ServiceS1.ServiceS1"
>
<endpoint
address
=
"http://localhost:8001/ServiceS1/"
binding
=
"basicHttpBinding"
contract
=
"ServiceDeveloppez.IService"
>
<identity>
<dns
value
=
"localhost"
/>
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata
httpGetEnabled
=
"False"
/>
<serviceDebug
includeExceptionDetailInFaults
=
"False"
/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name
=
"ServiceS3.ServiceS3"
>
<endpoint
address
=
"http://localhost:8003/ServiceS3/"
binding
=
"wsHttpBinding"
contract
=
"ServiceDeveloppez.IService"
>
<identity>
<dns
value
=
"localhost"
/>
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata
httpGetEnabled
=
"False"
/>
<serviceDebug
includeExceptionDetailInFaults
=
"False"
/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name
=
"ServiceS2.ServiceS2"
>
<endpoint
address
=
"net.tcp://localhost:8002/ServiceS2/"
binding
=
"netTcpBinding"
contract
=
"ServiceDeveloppez.IService"
>
<identity>
<dns
value
=
"localhost"
/>
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata
httpGetEnabled
=
"False"
/>
<serviceDebug
includeExceptionDetailInFaults
=
"False"
/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Comme j'héberge mes services dans des applications consoles j'ai un petit bout de code à écrire, mais rien ne vous empêche de les héberger directement dans IIS, dans ce cas, vous n'aurez que le fichier .svc à renseigner.
using
System;
using
System.
Collections.
Generic;
using
System.
Linq;
using
System.
Text;
using
System.
ServiceModel;
namespace
ServiceS1
{
class
Program
{
static
void
Main
(
string
[]
args)
{
Console.
WriteLine
(
"Démarrage du service S1"
);
Console.
WriteLine
(
);
using
(
ServiceHost host =
new
ServiceHost
(
typeof
(
ServiceS1)))
{
host.
Open
(
);
Console.
WriteLine
(
);
Console.
ReadLine
(
);
}
}
}
}
III-B. Création du service de routage▲
Le service de routage va lui, être très simple, un morceau de code pour l'héberger dans mon application console et un fichier de configuration pour lui indiquer comment router les messages.
using
System;
using
System.
Collections.
Generic;
using
System.
Linq;
using
System.
Text;
using
System.
ServiceModel;
namespace
RoutingService
{
class
Program
{
static
void
Main
(
string
[]
args)
{
ServiceHost svcRoutingHost =
new
ServiceHost
(
typeof
(
System.
ServiceModel.
Routing.
RoutingService));
svcRoutingHost.
Open
(
);
Console.
WriteLine
(
"Routing Service is running..."
);
Console.
ReadLine
(
);
}
}
}
Voyons plus en détail son fichier de configuration. Tout d'abord ce service de routage expose en EndPoint qui va permettre au client de se connecter, en l'occurrence il s'agit d'un EndPoint utilisant le basicHttpBinding, mais une fois de plus pas de restriction à ce niveau, tous les bindings de WCF sont disponibles.
<services>
<service
name
=
"System.ServiceModel.Routing.RoutingService"
behaviorConfiguration
=
"MyRoutingServiceBehavior"
>
<endpoint
address
=
"http://localhost:8000/RoutingService/applicationA"
binding
=
"basicHttpBinding"
contract
=
"System.ServiceModel.Routing.ISimplexDatagramRouter"
/>
</service>
</services>
Comme évoqué précédemment on retrouve bien notre contrat spécifique aux services de routage : System.ServiceModel.Routing.ISimplexDatagramRouter, vous voyez qu'il est contenu dans un nouveau namespace spécifique au routage WCF 4.
Définissons maintenant les EndPoints des services vers lesquels vont devoir être routés les messages reçus :
<client>
<endpoint
address
=
"http://localhost:8001/ServiceS1"
binding
=
"basicHttpBinding"
contract
=
"*"
name
=
"ServiceS1"
>
</endpoint>
<endpoint
address
=
"net.tcp://localhost:8002/ServiceS2"
binding
=
"netTcpBinding"
contract
=
"*"
name
=
"ServiceS2"
>
</endpoint>
<endpoint
address
=
"http://localhost:8003/ServiceS3"
binding
=
"wsHttpBinding"
contract
=
"*"
name
=
"ServiceS3"
>
</endpoint>
<endpoint
address
=
"http://localhost:8004/ServiceS4"
binding
=
"basicHttpBinding"
contract
=
"*"
name
=
"ServiceS4"
>
</endpoint>
</client>
Nous devons aussi spécifier les filtres à utiliser et la manière de router les messages. Grâce à ce système de configuration par fichier, il est possible d'avoir un niveau de granularité élevé et de définir des scénarios complexes, notamment en faisant du filtrage au niveau du contenu des messages via des requêtes XPath, cela sera l'objet d'un prochain article!
<behaviors>
<serviceBehaviors>
<behavior
name
=
"MyRoutingServiceBehavior"
>
<serviceMetadata
httpGetEnabled
=
"True"
httpGetUrl
=
"http://localhost:8000/RoutingService/applicationA"
/>
<routing
filterTableName
=
"RoutingServiceFilterTable"
/>
<serviceDebug
includeExceptionDetailInFaults
=
"true"
/>
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<filterTables>
<filterTable
name
=
"RoutingServiceFilterTable"
>
<add
filterName
=
"MyMulticastFilter"
endpointName
=
"ServiceS1"
/>
<add
filterName
=
"MyMulticastFilter"
endpointName
=
"ServiceS2"
/>
<add
filterName
=
"MyMulticastFilter"
endpointName
=
"ServiceS3"
/>
<add
filterName
=
"MyMulticastFilter"
endpointName
=
"ServiceS4"
/>
</filterTable>
</filterTables>
<filters>
<filter
name
=
"MyMulticastFilter"
filterType
=
"MatchAll"
/>
</filters>
</routing>
Voilà rien de particulier ni de différent de ce que l'on a vu dans le scénario du Message Broker. Le filtre utilisé indique MatchAll, ce qui va avoir pour effet de tout router sans ne pratiquer aucune sélection sur l'origine ou le contenu des messages.
III-C. Création du client▲
Pour finir, il faut bien évidemment créer le client. Il référence le End Point exposé par le service de routage sans avoir connaissance de l'architecture des services qui vont recevoir les messages qu'il va envoyer. Voici une copie du code de notre client :
using
System;
using
System.
Collections.
Generic;
using
System.
ComponentModel;
using
System.
Data;
using
System.
Drawing;
using
System.
Linq;
using
System.
Text;
using
System.
Windows.
Forms;
using
System.
ServiceModel;
namespace
ApplicationA
{
public
partial
class
Form1 :
Form
{
public
Form1
(
)
{
InitializeComponent
(
);
}
private
void
btSendS1_Click
(
object
sender,
EventArgs e)
{
BasicHttpBinding binding =
new
BasicHttpBinding
(
);
EndpointAddress endPoint =
new
EndpointAddress
(
"http://localhost:8000/RoutingService/applicationA"
);
ServiceDeveloppez.
IService serviceProxy =
ChannelFactory<
ServiceDeveloppez.
IService>.
CreateChannel
(
binding,
endPoint);
serviceProxy.
SendMessage
(
"Multicasting message from A"
);
}
}
}
Voilà, notre application utilisant le multicasting de WCF 4 est prête, il n'y a plus qu'à tester tout cela et apprécier le travail réalisé par Microsoft dans cette nouvelle version de WCF :).
IV. Conlcusion▲
J'espère au cours de cet article, vous avoir convaincu sur l'utilité du multicasting et montré à quel point il est facile à mettre en œuvre avec WCF 4. Vous trouverez à la suite de cet article la solution autour de laquelle j'ai construit cet article. Rendez-vous prochainement pour découvrir une autre nouveauté apportée par le " Routing Service " de " Windows Communication Foundation 4 ".