How to : Une petite application client / serveur avec WCF
Ce petit article a pour but de donner les étapes importantes pour écrire le squelette d'une application WCF client / serveur, avec un DuplexChannel, c'est à dire que le serveur peut appeler des méthodes sur le client (et pas seulement l'inverse). Avec les DuplexChannel, il devient facile d'écrire un programme client / serveur qui aurait nécessité autrefois d'aller chercher dans les Sockets.
Définir les contrats
Comme vous le savez surement, WCF est basé sur un système de contrats. Nous allons donc commencer par définir ces contrats dans un assembly qui sera réutilisé à la fois dans le client et dans le serveur. Commencez donc par créer une bibliothèque de classes qui contient le contrat du serveur, et le contrat du callback (tous deux sont des interfaces).
[ServiceContract(CallbackContract = typeof(IClient))]
public interface IServer
{
[OperationContract]
bool Login(string user, string password);
[OperationContract]
bool Say(string text);
}
[ServiceContract()]
public interface IClient
{
[OperationContract]
void ReceiveMessage(string message);
}
Si vous décidez d'utiliser des DataContract, c'est dans cet assembly qu'il faut les définir.
N'oubliez pas d'ajouter System.ServiceModel en référence du projet, sinon les attributs ServiceContract n'existeront pas.
Ecrire le serveur
Il faut maintenant fournir une implémentation de ce service (coté serveur). Nous allons prendre l'exemple d'un serveur console, mais rien ne vous empêche de le faire sous la forme d'un service Windows, ou d'une application WinForms par exemple.
Commencez par ajouter la référence au projet précédent, et à System.ServiceModel.
Nous allons déjà écrire l'implémentation du service. Il suffit pour cela de créer une classe qui implémente l'interface IServer, définie dans le 1er assembly.
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, InstanceContextMode = InstanceContextMode.PerSession)]
public class FxComServer : IServer
{
private string login;
private IClient client;
public bool Login(string user, string password)
{
login = user;
client = OperationContext.Current.GetCallbackChannel<IClient>();
return true;
}
public bool Say(string text)
{
client.ReceiveMessage(login + " said " + text);
return true;
}
}
Il ne faut pas oublier l'attribut ServiceBehavior. ConcurrencyMode indique la façon dont les threads de WCF seront gérés. Reentrant est un bon compromis, mais vous pouvez aller jeter un coup d'oeil sur MSDN à propos de Single et Multiple si vous avez d'autres besoins. Il peut aussi être utile de changer la valeur de InstanceContextMode selon si vous voulez gérer les sessions ou non.
Comme pour le client, si votre serveur est basé sur une application WinForms ou WPF, n'oubliez pas de mettre false à UseSynchronizationContext dans cet attribut.
Vous pouvez noter l'utilisation de la méthode GetCallbackChannel qui permet d'obtenir une instance d'un objet IClient par lequel vous pouvez appeler des méthodes du client. C'est de cette façon que vous pouvez appeler des méthodes du callback.
Il faut ensuite écrire un petit bout de code qui vous permettra de lancer le serveur et le stopper. Le voici :
internal static class Host
{
internal static ServiceHost myServiceHost = null;
internal static void StartService()
{
//Instantiate new ServiceHost
myServiceHost = new ServiceHost(typeof(FxCommunicatorServer.FxComServer));
//Open myServiceHost
myServiceHost.Open();
}
internal static void StopService()
{
//Call StopService from your shutdown logic (i.e. dispose method)
if (myServiceHost.State != CommunicationState.Closed)
myServiceHost.Close();
}
}
Cette classe à copier / coller, permet de lancer et stopper le serveur. Il suffit juste de changer le nom de la classe qui correspond à votre implémentation du service.
Tout le code du serveur est maintenant en place. Il ne reste plus que la touche finale : définir les EndPoints. Le plus pratique est de faire cela dans le fichier App.config. Ajoutez donc le fichier App.config à votre projet, et définissez des EndPoints de la façon suivante :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="FxCommunicatorServer.FxComServer">
<endpoint contract="FxCommunicatorService.IServer" binding="netTcpBinding" address="net.tcp://localhost:1303"/>
<endpoint contract="FxCommunicatorService.IServer" binding="wsDualHttpBinding" address="http://localhost:80"/>
</service>
</services>
</system.serviceModel>
</configuration>
Il suffit d'ajouter autant d'élément endpoint que vous voulez d'EndPoint pour votre serveur. Indiquez dans contract le contrat implémenté. Binding est un binding prédéfini dans WCF, mais attention, tous ne supportent pas le DuplexChannel. netTcpBinding (basé directement sur TCP donc relativement performant) et wsDualHttpBinding (basé sur HTTP donc mois performant mais plus interopérable) gèrent tous deux le DuplexChannel.
Le serveur est maintenant terminé. Il suffit juste d'appeler la méthode StartService et le tour est joué.
Ecrire le client
Pour le client, nous allons utiliser une application WinForms, mais une application WPF aurait très bien fait l'affaire.
Déjà, ajoutez le premier projet et System.ServiceModel en référence.
Ensuite, comme il s'agit d'un service qui utilise le DuplexChannel, le client doit avoir une classe qui implémente IClient (dans notre cas). C'est cette classe qui recevra les callbacks. Il est tout à fait possible que cette classe soit une Form :
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public partial class Form1 : Form, IClient
{
// [...]
public void ReceiveMessage(string message)
{
MessageBox.Show(message);
}
}
ConcurrencyMode permet d'éviter les deadlocks, comme pour le serveur. UseSynchronizationContext = false est indispensable lorsque l'application cliente est une application WinForms ou WPF. Sinon, l'application se bloquera au moindre appel du callback.
Pour terminer, il faut créer une instance du proxy vers le service :
IServer service;
private void button1_Click(object sender, EventArgs e)
{
service = DuplexChannelFactory<IServer>.CreateChannel(this, new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:1303"));
}
Le premier objet en argument de CreateChannel est l'objet qui recevra les callbacks. Il doit donc implémenter IClient, dans notre cas c'est la Form elle même, c'est pour ça que c'est this. Le second correspond au Binding utilisé et le 3e à l'adresse de l'EndPoint.
Voilà, le tour est joué. Il ne reste plus qu'à appeller des méthodes du service depuis le client :
private void button2_Click(object sender, EventArgs e)
{
service.Login("RaptorXP", "");
service.Say("Hello");
}
Et voilà...
Bien sur, il y a beaucoup d'autres moyens de créer une application client / serveur (sans utiliser App.config par exemple), mais dans tous les cas, WCF permet un gain de temps considérable pour le développeur.
Pour finir, voici quelques liens utiles :
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :