You are on page 1of 15

Dveloppement dun logiciel de messagerie instantane avec Dotnet (version simplifie)

Proprits Intitul long Formation concerne Matire Prsentation Notions Transversalit Pr-requis Outils Description

Dveloppement dun logiciel de messagerie instantane BTS Services informatiques aux organisations SLAM4 - Ralisation et maintenance de composants logiciels Dveloppement dun logiciel de messagerie instantane. La communication est base sur les sockets entre deux applications dotnet. Programmation laide dobjets Dveloppement orient rseau Les protocoles rseau Le langage C#, les applications WinForms. Les applications fournies ont t ralises avec Visual Studio 2008 et sont destines au framework dotnet 3.5, elles peuvent facilement tre recompiles avec un autre outil et/ou cibler une autre version du framework. - CoursSockets.doc : support de cours lve. - Exemple01 : un premier exemple dapplication simple. - Exemple02 : exemple de rception asynchrone. - Exemple03 : utilisation dun objet BackgroundWorker. - Chat : application simple de chat. C # , DotNet 10 heures Pierre Loisel v 1.0 Novembre 2008

Mots-cls Dure Auteur(es)

Version Date de publication

Ce document nest pas une tude exhaustive du domaine. Il prsente simplement une manire de raliser des applications communicantes en C#.

Lexcution dune application utilisant les sockets partir dun disque rseau peut poser des problmes de scurit. Si vous obtenez une exception concernant la scurit, configurez les paramtres de scurit du framework. Vous pouvez consulter la page http://msdn.microsoft.com/frfr/library/2bc0cxhc.aspx pour obtenir des informations concernant loutil .NET Framework Configuration (Mscorcfg.msc).

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 1/15

A/ Prsentation 1. Les sockets Les sockets fournissent un mcanisme gnrique de communication entre les processus. Elles sont apparues pour la premire fois en 1986 dans la version UNIX de luniversit de Berkeley. Un processus peut tre sommairement dfini comme une application (ou un service) en cours dexcution. Un socket permet un processus (le client) denvoyer un message un autre processus (le serveur). Le serveur qui reoit ce message peut alors accomplir toutes sortes de tche et ventuellement retourner un rsultat au processus client. Lors de la cration dun socket, il faut prciser le type dadressage, le type de message et le protocole transport utiliss. Nous utiliseront : IPV4, les datagrammes simples et le protocole UDP. 2. Point de terminaison Un point de terminaison (EndPoint) est dfini par une adresse IP et un numro de port. Une communication stablit entre deux points de terminaison. Un processus serveur reoit les messages destins un numro de port et un protocole transport (UDP, TCP, ...) dtermins. Un client dsirant envoyer un message un serveur doit donc crer un point de terminaison reprsentant le rcepteur du message en fournissant ladresse IP du serveur et le numro de port cout. Pour envoyer un message, un processus doit galement crer un point de terminaison reprsentant lmetteur du message en fournissant sa propre adresse IP. Il ne fournit pas de numro de port, cest le systme qui attribuera un numro de port libre pour la communication. 3. Principe de communication Lors de lenvoi dun message, il faut : - Crer un socket, - Crer le point de terminaison metteur, - Lier le socket au point de terminaison metteur (ce qui prcisera le protocole transport utilis pour lmission). - Crer le point de terminaison rcepteur, - Envoyer le message laide du socket vers le point de terminaison rcepteur. Pour recevoir un message, le processus serveur doit : - Crer un socket, - Crer le point de terminaison rcepteur (lui-mme donc), - Lier le socket au point de terminaison rcepteur (ce qui prcisera le protocole transport utilis pour la rception). - Crer le point de terminaison metteur (sans prciser ladresse IP ni le numro de port puisquils ne sont pas connus ce stade). - Mettre le socket en tat de rception en lui fournissant un buffer pour les donnes recevoir, et une rfrence au point de terminaison metteur. - Lorsque le message est effectivement reu, le point de terminaison metteur est renseign.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 2/15

4. Premier exemple (exemple01) Le client :

public Fm_client() { CheckForIllegalCrossThreadCalls = false; InitializeComponent(); adrIpLocale = getAdrIpLocaleV4(); } private IPAddress adrIpLocale; private IPAddress getAdrIpLocaleV4() { string hote = Dns.GetHostName(); IPHostEntry ipLocales = Dns.GetHostEntry(hote); foreach (IPAddress ip in ipLocales.AddressList) { if (ip.AddressFamily == AddressFamily.InterNetwork) { return ip; } } return null; // aucune adresse IP V4 } private void bt_envoyer_Click(object sender, EventArgs e) { byte[] message; Socket sock = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); IPEndPoint epEmetteur = new IPEndPoint(adrIpLocale, 0); sock.Bind(epEmetteur); IPEndPoint epRecepteur = new IPEndPoint( IPAddress.Parse(tb_ipDestinataire.Text), 33000); message = Encoding.Unicode.GetBytes(tb_message.Text); sock.SendTo(message, epRecepteur); sock.Close(); } Remarque : la chane envoyer est transforme en un tableau de bytes.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 3/15

Le serveur :

public Fm_serveur() { CheckForIllegalCrossThreadCalls = false; InitializeComponent(); adrIpLocale = getAdrIpLocaleV4(); } private IPAddress adrIpLocale; private IPAddress getAdrIpLocaleV4() { string hote = Dns.GetHostName(); IPHostEntry ipLocales = Dns.GetHostEntry(hote); foreach (IPAddress ip in ipLocales.AddressList) { if (ip.AddressFamily == AddressFamily.InterNetwork) { return ip; } } return null; // aucune adresse IP V4 } private void bt_recevoir_Click(object sender, EventArgs e) { byte[] message = new byte[40]; Socket sock = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); IPEndPoint epRecepteur = new IPEndPoint(adrIpLocale, 33000); sock.Bind(epRecepteur); EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sock.ReceiveFrom(message, ref epTemp); IPEndPoint epEmetteur = (IPEndPoint)epTemp; string strMessage = Encoding.Unicode.GetString(message); MessageBox.Show( epEmetteur.Address.ToString() + " -> " + strMessage); } Remarques : La fonction getAdrIpLocaleV4 retourne ladresse IP V4 de lhte en utilisant le service DNS. Le nom de lhte local est rcupr en utilisant le DNS. Elle est appele dans le constructeur du formulaire. La mthode ReceiveFrom de la classe Socket attend un paramtre de type EndPoint. Il faut donc oprer une conversion de type pour rcuprer le point de terminaison metteur. Le message est un tableau de bytes. Il faut le transformer en chane de caractres. Linstruction CheckForIllegalCrossThreadCalls = false; dans le constructeur des deux formulaires permet de saffranchir simplement des problmes lis la mise jour de linterface lors de la rception dun message.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 4/15

Mise en uvre : Crer lapplication client et lapplication serveur. Lancer les deux applications (sur le mme poste ou sur deux postes diffrents). Cliquer sur le bouton recevoir du serveur. Renseigner ladresse IP du serveur et le message envoyer dans lapplication client. Cliquer sur le bouton envoyer du client. Le serveur affiche ladresse IP et le message du client.

B/ Rception asynchrone 1. Principe Lorsque lon clique sur le bouton recevoir du serveur, celui-ci se trouve bloqu en attente du message et ne peut accomplir aucune autre tche. Pour y remdier, il est possible dutiliser le mcanisme de rception asynchrone fourni par Dotnet. Ce mcanisme est bas sur les threads. Un thread peut tre vu comme un mini processus interne une application. Une application peut avoir plusieurs threads et donc excuter plusieurs tches simultanment (cette simultanit nest que fictive, en tous cas sur une machine mono-processeur). Le principe est le suivant : > Mettre le socket en tat de rception en utilisant la mthode ci-dessous : sock.BeginReceiveFrom(message,0,40, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir),null);

Le socket est mis en tat de rception dans un thread de rception diffrent du thread principal. Le thread principal poursuit immdiatement son excution, le programme nest pas bloqu par cet appel. Lavant dernier paramtre indique que la mthode recevoir doit tre appele ds la rception dun message par le thread de rception.

> Grer le message reu : Quand le thread de rception reoit le message, il le prend en charge (laffiche par exemple), et se termine. Lidal est de remettre le socket en tat de rception immdiatement, de manire accepter plusieurs messages les uns aprs les autres.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 5/15

2. Modification du serveur (exemple02)

public Fm_serveur() { CheckForIllegalCrossThreadCalls = false; InitializeComponent(); init(); } private int lgMessage = 40; private IPAddress adrIpLocale; private Socket sock; private IPEndPoint epRecepteur; byte[] message; private IPAddress getAdrIpLocaleV4() { // Idem version pcdente } private void init() { message = new byte[lgMessage]; adrIpLocale = getAdrIpLocaleV4(); sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); epRecepteur = new IPEndPoint(adrIpLocale, 33000); sock.Bind(epRecepteur); EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sock.BeginReceiveFrom( message, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); } private void recevoir(IAsyncResult AR) { EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sock.EndReceiveFrom(AR, ref epTemp); IPEndPoint epEmetteur = (IPEndPoint)epTemp; string strMessage; strMessage = Encoding.Unicode.GetString(message, 0, message.Length); Array.Clear(message, 0, message.Length); sock.BeginReceiveFrom( message, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); MessageBox.Show( epEmetteur.Address.ToString() + " -> " + strMessage); }

Remarques : Le serveur se met en rception ds le lancement de lapplication, il ny a donc plus besoin dun bouton recevoir. Ds la rception dun message, le serveur relance un nouveau thread de rception. La rception ne sarrte qu la fermeture du programme. Vous pouvez tester lenvoi de messages partir de deux clients diffrents.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 6/15

C/ Un exemple plus complet 1. Amliorer linterface Modifiez votre application serveur de la manire suivante : Ajouter un contrle de type ListBox (lb_messages) sur le formulaire. Au lieu dafficher le message reu, la mthode recevoir doit maintenant ajouter ce message la liste lb_messages. Ajouter une proprit nbMessages la classe Fm_serveur. Cette proprit sera destine compter le nombre de messages reus. Ce nombre de messages sera affich dans un label lbl_nbMessages.

2. Grer un message plus complexe Le client va maintenant envoyer un message constitu de son adresse IP et du texte du message luimme. Une solution simple consiste envoyer une chane contenant les deux lments spars par un caractre particulier. Exemple : 192.168.200.3#Le texte du message# . A la rception de cette chane, le serveur doit retrouver les deux lments du message. Il peut le faire de la manire suivante : Rcuprer le message reu sous la forme dune chane de caractres : strMessage = Encoding.Unicode.GetString(message, 0, message.Length); Isoler les diffrents elements dans un tableau de chanes : string[] tabElements; char[] separateur = new char[1]; separateur[0] = '#'; tabElements = strMessage.Split(separateur); tabElements[0] contient ladresse IP. tabElements[1] contient le texte du message.

Remarques : La mthode Split dcoupe la chane strMessage en plusieurs lments et place ces lments dans le tableau tabElements. Elle prend en paramtre un tableau contenant les caractres jouant le rle de sparateur pour dcouper la chane strMessage. Il faut donc que ce tableau contienne le caractre #.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 7/15

3. Modification du serveur (exemple03)

private int nbMessages; // idem private void init() { nbMessages = 0; // idem } private void recevoir(IAsyncResult AR) { EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sock.EndReceiveFrom(AR, ref epTemp); string strMessage; strMessage = Encoding.Unicode.GetString( message, 0, message.Length); string[] tabElements; char[] separateur = new char[1]; separateur[0] = '#'; tabElements = strMessage.Split(separateur); nbMessages++; string affichage = tabElements[0] + " -> " + tabElements[1]; lb_messages.Items.Add(affichage); lbl_nbMessages.Text = "Nombre de messages reus : " + nbMessages.ToString(); Array.Clear(message, 0, message.Length); sock.BeginReceiveFrom( message, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); }

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 8/15

D/ Une vraie conversation 1. Transmettre un message tout le monde Pour transmettre un message plusieurs htes, il y a au moins deux solutions : Transmettre le mme message successivement aux diffrents destinataires, ce qui ncessite une simple boucle. Envoyer le message en broadcast.

Pour envoyer un message en broadcast, il suffit de modifier ainsi la procdure denvoi : private void bt_envoyer_Click(object sender, EventArgs e) { byte[] messageBytes; Socket sock = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); sock.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); IPEndPoint epEmetteur = new IPEndPoint(adrIpLocale, 0); sock.Bind(epEmetteur); IPEndPoint epRecepteur = new IPEndPoint(IPAddress.Broadcast, 33000); string leMessage = "Contenu du message"; messageBytes = leMessage.GetInfos(); sock.SendTo(messageBytes, epRecepteur); sock.Close(); } Le message sera reu par tous les htes en tat de rception sur le port UDP 33000. 2. Dfinir un protocole Il sagit de fixer les rgles de la communication entre les diffrentes applications. Imaginons lexemple dun pauvre chat : Nous avons un serveur et plusieurs clients. Lorsquun client se connecte, il en informe le serveur. Le serveur conserve la liste des clients connects. Lorsquun client envoie un message au serveur, celui-ci le transmet lensemble des clients. Lorsquun client se dconnecte, il en informe le serveur. Les clients sont reprs par leur adresse IP, lunicit des pseudos nest pas assure.

Nous avons besoin de dfinir plusieurs types de messages : Connexion (type C) Emetteur : un client qui se connecte Rcepteur : le serveur Contenu : Ladresse IP et le pseudo du client Raction du serveur : mmorisation du pseudo

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 9/15

Envoi (type E) Emetteur : un client qui envoi un texte sur le chat Rcepteur : le serveur Contenu : ladresse IP du client et le texte envoy Raction du serveur : rmission du texte tous les clients Rmission (type R) Emetteur : le serveur Rcepteur : les clients Contenu : ladresse IP du serveur, le pseudo de lauteur du texte et le texte lui-mme Raction des clients : affichage du pseudo et du texte Dconnexion (type D) Emetteur : un client qui se dconnecte, c'est--dire qui quitte lapplication Rcepteur : le serveur Contenu : ladresse IP du client et son pseudo Raction du serveur : mise jour de la liste des clients connects

E/ Le pauvre chat (Chat) 1. Le client

Gnralits : - Ladresse IP du serveur est suppose connue. - Il faut galement dterminer les ports utiliss par le serveur et par les clients.

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 10/15

public partial class Fm_client : Form { public Fm_client() { CheckForIllegalCrossThreadCalls = false; InitializeComponent(); adrIpLocale = getAdrIpLocaleV4(); separateur = new char[1]; separateur[0] = '#'; } private IPAddress adrIpLocale; private char[] separateur; private IPAddress ipServeur = IPAddress.Parse("192.168.3.3"); private int portServeur = 33000; private int portClient = 33001; private int lgMessage = 1000; private Socket sockReception; private IPEndPoint epRecepteur; byte[] messageBytes; private IPAddress getAdrIpLocaleV4() { // idem } Lenvoi dun message ne pose pas de problme particulier. Chaque message est constitu des lments suivants : - Le type de message (C, E, R, D). Le client peut envoyer des messages C, E ou D. - Le pseudo lorigine du message. - Le texte du message. private void envoyer(string typeMessage,string texte) { byte[] messageBytes; Socket sock = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); IPEndPoint epEmetteur = new IPEndPoint(adrIpLocale, 0); sock.Bind(epEmetteur); IPEndPoint epRecepteur = new IPEndPoint(ipServeur, portServeur); string infos = typeMessage + "#" + tb_pseudo.Text + "#" + texte + "#"; messageBytes = Encoding.Unicode.GetBytes(infos); sock.SendTo(messageBytes, epRecepteur); sock.Close(); tb_message.Clear(); tb_message.Focus(); }

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 11/15

Quand lutilisateur clique sur se connecter , le message de connexion est envoy au serveur (il ne contient pas de texte) et le client se met en tat de rception. Il devient ensuite possible de poster un texte sur le chat. private void bt_connecter_Click(object sender, EventArgs e) { if (tb_pseudo.Text != "") { bt_connecter.Enabled = false; tb_pseudo.Enabled = false; envoyer("C", ""); bt_envoyer.Enabled = true; tb_message.Enabled = true; initReception(); tb_message.Focus(); } } private void initReception() { messageBytes = new byte[lgMessage]; sockReception = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); epRecepteur = new IPEndPoint(adrIpLocale, portClient); sockReception.Bind(epRecepteur); EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sockReception.BeginReceiveFrom( messageBytes, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); } A la rception dun message, le client met jour son interface. private void recevoir(IAsyncResult AR) { EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sockReception.EndReceiveFrom(AR, ref epTemp); string strMessage = Encoding.Unicode.GetString( messageBytes, 0, messageBytes.Length); string[] tabElements; tabElements = strMessage.Split(separateur); switch (tabElements[0]) { case "R": lb_messages.Items.Add( tabElements[1] + " -> " + tabElements[2]); break; } Array.Clear(messageBytes, 0, messageBytes.Length); sockReception.BeginReceiveFrom( messageBytes, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); }

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 12/15

Quand lutilisateur clique sur le bouton envoyer , le texte saisi est envoy au serveur. private void bt_envoyer_Click(object sender, EventArgs e) { envoyer("E", tb_message.Text); } La dconnexion est gre par un message envoy la fermeture du formulaire. private void Fm_client_FormClosing( { envoyer("D", ""); } 2. Le serveur object sender, FormClosingEventArgs e)

Ds son lancement, le serveur se met en tat de rception sur son port. public partial class Fm_serveur : Form { public Fm_serveur() { CheckForIllegalCrossThreadCalls = false; InitializeComponent(); initReception(); } private int lgMessage = 1000; private int portServeur = 33000; private int portClient = 33001; private IPAddress adrIpLocale; private char[] separateur; private Socket sockReception; private IPEndPoint epRecepteur; byte[] messageBytes; private IPAddress getAdrIpLocaleV4() { // idem }

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 13/15

private void initReception() { messageBytes = new byte[lgMessage]; adrIpLocale = getAdrIpLocaleV4(); separateur = new char[1]; separateur[0] = '#'; sockReception = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); epRecepteur = new IPEndPoint(adrIpLocale, portServeur); sockReception.Bind(epRecepteur); EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sockReception.BeginReceiveFrom( messageBytes, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); } Une mthode envoyerBroadcast permet lenvoi dun message sur lensemble du rseau. private void envoyerBroadcast(string pseudo,string texte) { byte[] messageBroadcast; Socket sockEmission = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); sockEmission.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); IPEndPoint epEmetteur = new IPEndPoint(adrIpLocale, 0); sockEmission.Bind(epEmetteur); IPEndPoint epRecepteur = new IPEndPoint( IPAddress.Broadcast, portClient); string strMessage = "R" + "#" + pseudo + "#" + texte + "#"; messageBroadcast = Encoding.Unicode.GetBytes(strMessage); sockEmission.SendTo(messageBroadcast, epRecepteur); sockEmission.Close(); }

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 14/15

Les messages reus sont traits en fonction de leur type. private void recevoir(IAsyncResult AR) { EndPoint epTemp = (EndPoint)new IPEndPoint(IPAddress.Any, 0); sockReception.EndReceiveFrom(AR, ref epTemp); string strMessage = Encoding.Unicode.GetString( messageBytes, 0, messageBytes.Length); string[] tabElements; tabElements = strMessage.Split(separateur); switch (tabElements[0]) { case "C": lb_clients.Items.Add(tabElements[1]); break; case "E": envoyerBroadcast(tabElements[1], tabElements[2]); break; case "D": lb_clients.Items.Remove(tabElements[1]); break; } Array.Clear(messageBytes, 0, messageBytes.Length); sockReception.BeginReceiveFrom( messageBytes, 0, lgMessage, SocketFlags.None, ref epTemp, new AsyncCallback(recevoir), null); }

Finalement, il a du chien notre pauvre chat !

http://www.reseaucerta.org

CERTA - juin 2011 v1.0

Page 15/15

You might also like