Socket en .NET
Transcription
Socket en .NET
http://www.labo-dotnet.com Socket en .NET SUPINFO DOTNET TRAINING COURSE Auteurs : Gregory Ghez et Vincent Bourdon Version 2.0 – 6 août 2004 Nombre de pages : 18 Ecole Supérieure d’Informatique de Paris 23. rue Château Landon 75010 – PARIS www.supinfo.com Socket en .NET 2 / 18 Table des matières 1. INTRODUCTION ............................................................................................................................................. 3 1.1. HISTORIQUE DES SOCKETS ............................................................................................................................ 3 1.2. FONCTIONNEMENT ........................................................................................................................................ 3 1.3. LES MODES SYNCHRONE ET ASYNCHRONE .................................................................................................... 4 2. RESOLUTION DNS ......................................................................................................................................... 5 2.1. RESOLUTION DU NOM D’HOTE LOCAL : ......................................................................................................... 6 2.2. RECUPERATION DES INFORMATIONS D’HOTE : .............................................................................................. 6 2.3. RESOLUTION PAR NOM DNS : ....................................................................................................................... 7 2.3.1. Méthode synchrone ............................................................................................................................... 7 2.3.2. Méthode asynchrone ............................................................................................................................. 7 2.4. RESOLUTION PAR ADRESSE IP :..................................................................................................................... 8 2.4.1. 2.4.2. Méthode synchrone ............................................................................................................................... 8 Méthode asynchrone ............................................................................................................................. 9 3. ETABLISSEMENT D’UNE CONNEXION CLIENTE VERS UN SERVEUR ........................................ 10 3.1. CREATION DE LA SOCKET ............................................................................................................................ 10 3.2. PROCEDURE DE CONNEXION ........................................................................................................................ 10 3.3. TRANSFERT DE DONNEES ............................................................................................................................ 12 3.3.1. Envoi ................................................................................................................................................... 12 3.3.2. Réception............................................................................................................................................. 13 3.4. FERMETURE DE LA CONNEXION ................................................................................................................... 14 4. CREATION D’UN SERVEUR MULTI CLIENTS...................................................................................... 15 4.1. CREER ET LANCER LE SERVEUR ................................................................................................................... 15 4.2. GESTION DES CLIENTS ................................................................................................................................. 15 4.3. ACCEPTER UN CLIENT ................................................................................................................................. 17 4.4. TERMINER LE SERVEUR ............................................................................................................................... 18 http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 3 / 18 1. Introduction 1.1.Historique des Sockets Les sockets étaient au début un support de communication réseau qui avait été introduit en 1984 par Bill Joy, lors de la sortie de la version 4.2BSD d’Unix appelée Berkeley Unix. Ce support de communication est finalement devenu un standard et a été rapidement utilisé pour les communications réseau et interprocessus. Aujourd’hui un grand nombre de langages de développement utilisent les sockets, dont bien évidemment .NET Les sockets ont une très grande importance dans le concept .NET, puisque tous les principes de communications tels que le remoting ou les Services Web utilisent des sockets. 1.2.Fonctionnement Une Socket est un point de terminaison dans une communication bidirectionnelle entre deux programmes fonctionnant sur un réseau. Elle est associée à un numéro de port afin que la couche TCP ou UDP puisse identifier l’application vers laquelle les données doivent être transmises. Modèle des sockets Application utilisant les sockets TCP / UDP IP / ARP Ethernet, X 25, … Modèle OSI Application Présentation Session Transport Réseau Liaison Physique Dans un fonctionnement classique, l’une des applications appelée « serveur » possède une Socket associée à un port d’écoute. Le serveur attend une demande de connexion de la part d’un « client » sur ce port. port Serveur Demande de connexion Client Si tout ce passe bien, le serveur accepte la connexion, et va alors créer une nouvelle Socket associée à un nouveau port. http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 4 / 18 Port Serveur Client port port Connexion 1.3.Les modes synchrone et asynchrone Le mode synchrone est le mode le couramment utilisé en programmation. Les instructions et appels de méthodes se font séquentiellement, chaque instruction devant être terminée avant que la prochaine ne s’exécute. Les techniques de programmation asynchrones sont quant à elles beaucoup moins usuelles, mais souvent bien plus utiles lorsqu’il s’agit de longs traitement de données qui ne doivent pas entraver la bonne marche du programme. Le mode asynchrone est un mode prise en charge par de nombreuses classes du .NET Framework, et en ce qui nous concerne, la connectivité réseau en .NET. En mode asynchrone, la main est immédiatement rendue à l’appelant, ainsi les appels normalement bloquant comme la lecture sur une Socket, sont traités parallèlement dans un Thread. Il existe des méthodes asynchrones pour l’établissement d’une connexion à un hôte distant (BeginConnect) et également pour la prise en charge de nouveaux clients (BeginAccept). Les méthodes asynchrone en .NET commencent généralement par le préfixe Begin ou End. Begin pour le lancement de l’instruction en tache parallèle, et End pour la récupération des informations traitées. Voici la structure d’un code asynchrone : monObjet.BeginTraitement(…, new AsyncCallback(maMethodeDeRappel), stateObject); … void maMethodeDeRappel(IAsyncResult ar) { object o = ar.AsyncState; EndTraitement(ar); } http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 5 / 18 2. Résolution DNS Le Framework .NET dispose d’une série de classes permettant de retrouver les informations d’un hôte (adresses IP, nom DNS, alias) à partir de données primaires. Ces données primaires sont le nom de l’hôte, dans ce cas là on recherche les adresses IP associées, ou bien une adresse de l’hôte dont on souhaite connaître le nom DNS. Les classes traitées dans ce cours se trouvent dans l’espace de noms System.Net. La classe IPHostEntry située dans cet espace de nom, donne des informations sur un hôte. Voici la liste des propriétés accessibles de cette classe : Membres publics Description Obtient une liste d’adresses IP associées à hôte. AddressList Obtient une liste d’alias associés à l’hôte. Aliases Obtient le nom DNS de l’hôte. Hostname Source : MSDN français. Un objet du type IPHostEntry peut être obtenu en appelant certaines méthodes de la classe Dns. La classe Dns propose en effet des méthodes statiques permettant de résoudre des noms et/ou adresses IP. Voici la liste de ces méthodes : Nom de la méthode BeginGetHostByName BeginResolve EndGetHostByName EndResolve GetHostByAddress GetHostByName GetHostName Resolve Description Lance une demande asynchrone d'informations IPHostEntry sur le nom d'hôte DNS spécifié. Démarre une demande asynchrone pour résoudre un nom d'hôte DNS ou une adresse IP en instance de IPHostEntry. Achève une demande asynchrone d'informations DNS. Achève une demande asynchrone d'informations DNS. Obtient des données d'hôte DNS pour une adresse IP. Obtient les données DNS pour le nom d'hôte DNS spécifié. Obtient le nom d'hôte de l'ordinateur local. Résout un nom d'hôte DNS ou une adresse IP en instance de IPHostEntry. Source : MSDN français. Afin de mieux cerner le sujet, voici des exemples ciblés sur l’utilisation de la résolution DNS en C# .NET. Les méthodes présentées ci-dessous sont extraites d’une application Console, cela explique le mot clé statique en tête de déclaration. Voici le corps ainsi que les méthodes qui compose cette application : Corps (méthode Main) : static void Main(string[] args) { string hostname = "www.google.fr"; string ipString = "213.228.0.42"; // adresse IP de 'www.free.fr' // ResolutionHoteLocal(); ResolutionParNom(hostname); http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET // 6 / 18 ResolutionParIP(ipString); ResolutionAsyncParNom(hostname); ResolutionAsyncParIP(ipString); Console.In.ReadLine(); } 2.1.Résolution du nom d’hôte local : La méthode GetHostName renvoi le nom de l’ordinateur local. Cela peut être utile pour déterminer par la suite la liste des interfaces réseaux disponibles sur une machine en appliquant les techniques de résolution de noms présentes ci-dessous. private static void ResolutionHoteLocal() { try { // Retrouve les informations de l'hôte local string localhost = System.Net.Dns.GetHostName(); Console.Out.WriteLine("Nom de l'ordinateur local : {0}", localhost); } catch (System.Net.SocketException ex) { Console.Error.WriteLine("Une erreur s'est produite lors de la résolution du nom d'hôte local.\r\n{0}", ex.Message); } catch (System.Security.SecurityException) { Console.Error.WriteLine("L'appelant n'est pas autorisé à accéder aux informations DNS."); } } 2.2.Récupération des informations d’hôte : La méthode PrintHostEntry va permettre d’afficher les informations d’un hôte représentées par un objet de type IPHostEntry. private static void PrintHostEntry(IPHostEntry he) { Console.Out.WriteLine("Liste des alias pour '{0}':", he.HostName); foreach (string alias in he.Aliases) { Console.Out.WriteLine("\t- {0}", alias); } Console.Out.WriteLine(); Console.Out.WriteLine("Liste des adresses IP reconnues:"); foreach (IPAddress ia in he.AddressList) { Console.Out.WriteLine("\t -{0}", ia.ToString()); } Console.Out.WriteLine(); } http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 7 / 18 2.3.Résolution par nom DNS : 2.3.1. Méthode synchrone private static void ResolutionParNom(string hostname) { try { Console.Out.WriteLine("Résolution du nom '{0}' ...", hostname); // Retrouve les informations de l'hôte à partir du nom IPHostEntry he = Dns.GetHostByName(hostname); PrintHostEntry(he); } catch (ArgumentNullException) { Console.Error.WriteLine("La chaine 'hostname' est une référence null."); } catch (SocketException ex) { Console.Error.WriteLine("Une erreur s'est produite lors de la résolution de '{0}'.\r\n{1}", hostname, ex.Message); } catch (System.Security.SecurityException) { Console.Error.WriteLine("L'appelant n'est pas autorisé à accéder aux informations DNS."); } } 2.3.2. Méthode asynchrone La résolution de nom peut s’avérer coûteuse en temps, du fait de l’interrogation des différents serveurs DNS à la recherche d’informations sur l’hôte. C’est pour cela, que les méthodes de résolution disposent d’une équivalence en mode asynchrone pour éviter les appels bloquants. Comme pour tout appel asynchrone, il est nécessaire de créer une méthode dite de rappel qui sera appelée une fois la résolution terminée. private static void ResolutionAsyncParNom(string hostname) { try { // Retrouve les informations d’hôte à partir du nom DNS de manière asynchrone // Cette méthode retourne immédiatement. Dns.BeginGetHostByName(hostname, new AsyncCallback(ResolutionParNomCallback), null); } catch (ArgumentNullException) { Console.Error.WriteLine("La chaine 'hostname' est une référence null."); } catch (System.Security.SecurityException) { Console.Error.WriteLine("L'appelant n'est pas autorisé à accéder aux informations DNS."); } catch (SocketException ex) { http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 8 / 18 Console.Error.WriteLine("Une erreur s'est produite lors de la résolution de '{0}'.\r\n{1}", hostname, ex.Message); } } private static void ResolutionParNomCallback(IAsyncResult ar) { try { IPHostEntry he = Dns.EndGetHostByName(ar); PrintHostEntry(he); } catch (ArgumentNullException) { Console.Error.WriteLine("L'objet 'ar' est une référence null."); } } 2.4.Résolution par adresse IP : 2.4.1. Méthode synchrone Pour la résolution DNS par adresse IP, il faut passer par la méthode GetHostByAddress qui peut prendre en paramètre, soit un objet de type String représentant l’adresse IP, soit un objet de type IPAddress. private static void ResolutionParIP(string ipString) { try { Console.Out.WriteLine("Résolution de l'adresse '{0}' ...", ipString); // Retrouve les informations d'hôte à partir d'une chaine d'adresse IP IPHostEntry he = Dns.GetHostByAddress(ipString); PrintHostEntry(he); } catch (ArgumentNullException) { Console.Error.WriteLine("La chaine 'ipString' est une référence null."); } catch (SocketException ex) { Console.Error.WriteLine("Une erreur s'est produite lors de la résolution de '{0}'.\r\n{1}", ipString, ex.Message); } catch (FormatException) { Console.Error.WriteLine("Format de la chaine d'adresse '{0}' incorrect.", ipString); } catch (System.Security.SecurityException) { Console.Error.WriteLine("L'appelant n'est pas autorisé à accéder aux informations DNS."); } } http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 9 / 18 2.4.2. Méthode asynchrone Même façon de procéder que pour BeginGetHostByName. private static void ResolutionAsyncParIP(string ipString) { try { Dns.BeginResolve(ipString, new AsyncCallback(ResolutionParIPCallback), null); } catch (ArgumentNullException) { Console.Error.WriteLine("La chaine 'ipString' est une référence null."); } catch (System.Security.SecurityException) { Console.Error.WriteLine("L'appelant n'est pas autorisé à accéder aux informations DNS."); } catch (SocketException ex) { Console.Error.WriteLine("Une erreur s'est produite lors de la résolution de '{0}'.\r\n{1}", ipString, ex.Message); } } private static void ResolutionParIPCallback(IAsyncResult ar) { try { IPHostEntry he = Dns.EndResolve(ar); PrintHostEntry(he); } catch (ArgumentNullException) { Console.Error.WriteLine("L'objet 'ar' est une référence null."); } } Remarques : les méthodes Resolve et BeginResolve servent à la résolution par nom ou par adresse. N’existant pas de méthode « BeginGetHostByAddress », il faut utiliser BeginResolve pour la résolution asynchrone par adresse IP. Les méthodes asynchrones sont exécutées dans ce que l’on nomme le ThreadPool. Il n’est pas possible d’accéder à cet espace lors du déboguage, de même qu’il n’est pas possible d’influer sur la priorité du Thread exécutant le code de résolution. http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 10 / 18 3. Etablissement d’une connexion cliente vers un serveur Le namespace System.Net.Sockets contient une implémentation managée des sockets pour Windows. Toutes les autres classes utilisant le réseau dans le namespace System.Net l’utilisent. L’ensemble de ces classes est en fait une version managée de l’API Winsock32. On y retrouve donc à peu près les mêmes méthodes encapsulées dans des classes. Les différentes étapes de l’établissement d’une connexion à une application distante seront étidués à travers un exemple. Cet exemple a été réalisé dans une application Windows, d’où la MessageBox pour afficher les notifications. Le code se situe à l’intérieur d’une méthode de la classe du formulaire. La partie résolution de nom ne sera pas traitée, il s’agit d’une application concrète du précédent chapitre. 3.1.Création de la socket Socket s; try { // création de notre socket. // Le protocol sera TCP le type de socket sera donc Stream. // La famille d'adresse InterNetwork pour désigné les adresses Internet. s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } catch (SocketException) { MessageBox.Show("La combinaison de addressFamily, socketType et protocolType crée un socket non valide.\r\n", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } Le premier paramètre spécifie le schéma d'adressage utilisé par la Socket, le second paramètre spécifie le type de Socket et enfin le dernier paramètre spécifie le protocole utilisé par la Socket. Les trois paramètres ne sont pas totalement indépendants. Il existe des familles d’adresses ne supportant que certains protocoles et le type de Socket à utiliser est souvent implicitement lié à un protocole précis. Si la combinaison de ces 3 paramètres forme une Socket invalide, ce constructeur lève une exception SocketException. 3.2.Procédure de connexion IPEndPoint ep; string ipString = "66.102.11.99"; try { // création du point de terminaison pour la communication ep = new IPEndPoint(IPAddress.Parse(ipString), 80); } catch (ArgumentNullException) { MessageBox.Show("Erreur peu probable, 'ipString' est une référence http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 11 / 18 null.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (FormatException) { MessageBox.Show("'ipString' n'est pas une chaine d'adresse ip correcte.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (ArgumentOutOfRangeException) { MessageBox.Show("L'adresse et/ou le port sont invalides.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { // établissement de la connexion avec l'hôte 'hostname'. // Le protocol TCP se charge de toutes les opérations d'initialisation et de vérification. s.Connect(ep); } catch (ArgumentNullException) { MessageBox.Show("Le point de terminaison est une référence null.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (SocketException ex) { MessageBox.Show("Une erreur s'est produite lors de la tentative d'accès au socket.\r\n" + ex.Message, "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (ObjectDisposedException) { MessageBox.Show("La socket a été fermée.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (System.Security.SecurityException) { MessageBox.Show("Un appelant situé plus haut dans la pile des appels n'a pas l'autorisation pour l'opération demandée.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } MessageBox.Show("La connexion a été établie. La récupération des données va commencer."); Pour initialiser la connexion, il est nécéssaire de créer un canal entre les deux machines distantes. Par exemple dans le cas de TCP/IP, un numéro de port et une adresse IP permettent d’identifier un service distant. L’adresse spécifie une machine du réseau et le port identifie un service de cette machine. La combinaison de ces deux informations s’appelle un point de terminaison, et cela est représenté par la classe EndPoint du namespace System.Net. Cependant cette classe n’est pas utilisée directement : elle sert de classe de base. Pour chaque famille d’adresse, une classe spécifique a été créée http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 12 / 18 Exemple : pour la famille d’adresse IP la classe s’appelle IPEndPoint. Dans notre cas, le constructeur nécessite un paramètre de type IPAddress en paramètre. Il existe également une surcharge de ce constructeur prenant un long en paramètre représentant l’adresse IP de façon binaire (chaque bit des 4 composant le long représente une partie de l’adresse). L’objet IPAddress peut être obtenu à partir d’un objet String représentant une adresse IP comme dans l’exemple ci-dessous. try { IPAddress ia = IPAddress.Parse("192.168.0.1"); } catch (ArgumentNullException ae) { // La chaine passée en parameter est une référence null. } catch (FormatException fe) { // Le format de la chaine d’IP est incorrect. } La méthode Connect prend en paramètre notre objet IPEndPoint, c’est elle qui va effectuer la demande de connexion grâce au protocole TCP/IP. Il est pas nécéssaire de gérer les exceptions de manière détaillée : un seul catch avec un objet de type Exception pourrait suffire. Remarque : La liste des ports utilisés par les services standard est définie par l’Internet Assigned Numbers Authority (IANA) et utilise les ports jusqu’à 1024, les ports de 1024 à 65535 sont libres. 3.3.Transfert de données 3.3.1. Envoi string requete = "GET / HTTP/1.1\r\nHost: " + hostname + "\r\nConnection: Close\r\n\r\n"; byte[] buffer = System.Text.Encoding.ASCII.GetBytes(requete); try { s.Send(buffer, 0, buffer.Length, SocketFlags.None); } catch (ArgumentNullException) { MessageBox.Show("Le point de terminaison est une référence null.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (ArgumentOutOfRangeException) { MessageBox.Show("Les paramètres offset et/ou size sont invalides.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (SocketException ex) { MessageBox.Show("SocketFlags n'est pas une combinaison de valeurs valide ou une erreur du système d'exploitation s'est produite lors de http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 13 / 18 l'accès à Socket.\r\n" + ex.Message, "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } catch (ObjectDisposedException) { MessageBox.Show("La socket a été fermée.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } L’envoi des données (en considérant le protocole utilise comme étant TCP/IP) s’effectue grâce à la méthode Send. Cette méthode possède 4 surcharges, dont la plus complète, est celle utilisée dans l’exemple. Le premier paramètre est le buffer de bytes à envoyer à notre hôte. Le second est l’index du début des données dans ce buffer. Le troisième paramètre contient la taille des données à envoyer. Enfin le dernier paramètre spécifie quelques options d’envoi. Voici les différentes options disponble, dans l’exemple, aucune option n’est spécifiée. Détails de l’énumération SocketFlags : Nom de membre DontRoute MaxIOVectorLength None OutOfBand Partial Description Envoyer sans utiliser de table de routage. Fournit une valeur standard pour le nombre de structures WSABUF utilisées pour l'envoi et la réception de données. N'utiliser aucun indicateur pour cet appel. Traiter les données hors bande. Envoyer ou recevoir partiellement un message. 3.3.2. Réception int ret; byte[] rBuf = new byte[1024]; System.Text.StringBuilder pageContent = new System.Text.StringBuilder(); try { while ((ret = s.Receive(rBuf, 0, 1024, SocketFlags.None)) > 0) { pageContent.Append(System.Text.Encoding.ASCII.GetString(rBuf, 0, ret)); } } catch (ArgumentNullException) { MessageBox.Show("Le point de terminaison est une référence null.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (ArgumentOutOfRangeException) { MessageBox.Show("Les paramètres offset et/ou size sont invalides.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (SocketException ex) { MessageBox.Show("SocketFlags n'est pas une combinaison de valeurs valide ou une erreur du système d'exploitation s'est produite lors de http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 14 / 18 l'accès à Socket.\r\n" + ex.Message, "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (ObjectDisposedException) { MessageBox.Show("La socket a été fermée.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (System.Security.SecurityException) { MessageBox.Show("Un appelant de la pile des appels ne dispose pas des autorisations requises.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } textBox1.Text = pageContent.ToString(); Cette boucle while permet de récupérer les données de la réponse à la requête de l’exemple précédent. La méthode Receive renvoie un int indiquant le nombre de bytes effectivement lus à travers la Socket. Si ce nombre est égal à 0 cela indique que l’hôte a mis fin à la communication (comme demandé dans notre requête). Les données reçues sont converties en un objet String puis ajoutées successivement à un StringBuilder afin de l’afficher par la suite dans la TextBox de l’application Windows. 3.4.Fermeture de la connexion try { // coupe les flux d'entrée ET de sortie et vide les buffers. s.Shutdown(SocketShutdown.Both); } catch (SocketException ex) { MessageBox.Show("Une erreur s'est produite lors de la tentative d'accès au socket.\r\n" + ex.Message, "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (ObjectDisposedException) { MessageBox.Show("La socket a été fermée.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } L’appel à la méthode Shutdown met fin à une direction de communication (entrée et/ou sortie). Si elle utilise le paramètre Send, les buffers sont vidés, et les données sont envoyées à l’hôte. s.Close(); MessageBox.Show("La socket " + (!s.Connected ? "a bien été" : "n'a pu être") + " fermée."); La méthode Close ferme la connexion à l’hôte et libère les ressources non managées associées à la Socket. Une fois cette méthode appelée, la Socket n’est plus accessible. Remarque : Des versions asynchrones des méthodes utilisées existent, comme par exemple BeginConnect, BeginReceive et BeginSend. http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 15 / 18 4. Création d’un serveur multi clients Les méthodes et classes utilisées dans ce chapitre sont très sensiblement les mêmes que pour la création de client. Seul les nouveautés seront détaillées. Toujours sur le même principe que le chapitre précédent, les mécanismes de création d’un serveur seront étudiés grâce à un exemple concret. 4.1.Créer et lancer le serveur Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 4646); try { // on lie notre socket à notre point d'attache serverSocket.Bind(localEP); // on active le serveur, des clients peuvent désormais se connecter dessus. serverSocket.Listen(1); } catch (ArgumentNullException) // Appel de Bind { Console.Error.WriteLine("Erreur peu probable, localEP est une référence null."); return; } catch (SocketException ex) // Appel de Bind et Listen { Console.Error.WriteLine("Une erreur s'est produite lors de la tentative d'accès au socket.\r\n{0}", ex.Message); return; } catch (ObjectDisposedException) // Appel de Bind et Listen { Console.Error.WriteLine("La Socket a été fermée."); return; } catch (System.Security.SecurityException) // Appel de Bind { Console.Error.WriteLine("Un appelant situé plus haut dans la pile des appels n'a pas l'autorisation pour l'opération demandée."); return; } Le point de terminaison local doit être passé à la méthode Bind afin de lier la Socket à une interface réseau et un port sur la machine. Il est indispensable d’appeler la méthode Bind avant Listen. Si le port de communication a peu d’importance, il est possible d’affecter la valeur 0. Dans ce cas la, un port entre 1024 et 5000 sera assigné automatiquement pour la Socket. 4.2.Gestion des clients ArrayList clients = new ArrayList(); // Nous donnons un temps de vie à notre serveur pour ne pas // aborder une nouveau sujet que serait "Les Threads". DateTime now = DateTime.Now; http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 16 / 18 while (DateTime.Now < now.AddSeconds(30)) { ArrayList readList = new ArrayList(); // on ajoute à la liste la socket serveur afin de détecter les // demandes de connexion readList.Add(serverSocket); readList.AddRange(clients); // vérifie la présence de données en attente de traitement sur les sockets // de la liste readList Socket.Select(readList, null, null, 1000); // un client est dans la file d'attente (demande de connexion) if (readList.Contains(serverSocket)) { try { Socket clientSocket = serverSocket.Accept(); clients.Add(clientSocket); readList.Remove(serverSocket); } catch (SocketException ex) { Console.Error.WriteLine("Une erreur s'est produite lors de la tentative d'accès au socket.\r\n{0}", ex.Message); } catch (ObjectDisposedException) { Console.Error.WriteLine("La Socket a été fermée."); } catch (InvalidOperationException) { Console.Error.WriteLine("La socket n'est pas liée. Vous devez appeler la méthode Bind préalablement."); } } // on parcours la liste des clients ayant envoyer des données foreach (Socket cSocket in readList) { byte[] buffer = new byte[cSocket.Available]; int ret = cSocket.Receive(buffer); // si le retour du Receive est égal à 0, cela signifie que le client // s'est déconnecté if (ret == 0) { cSocket.Shutdown(SocketShutdown.Both); cSocket.Close(); clients.Remove(cSocket); } else { // on renvoi au client ce qu'il à envoyer au serveur cSocket.Send(buffer); } } // marque une courte pause afin de ne pas surcharger le processeur http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 17 / 18 avec // le Thread en cours System.Threading.Thread.Sleep(100); } La méthode Select permet de tester les clients en attentes de connexions ou de lecture. Elle prend 4 paramètres qui sont : • Le premier : Une IList (Arraylist implémente IList) contenant des objets de type Socket dont on souhaite connaître l’état en lecture. En ce qui concerne une Socket normal connectée à un hôte distant, il s’agit de vérifier la présence de données en attente de lecture. Si la socket est « bindée » et en écoute, il s’agira de vérifier la présence de client connecté dans la file d’attente des connexions. • Le second : Une IList également des Sockets devant être testées en écriture. Si l’écriture est non bloquante, la Socket sera alors marquée. • Le troisième : Une IList des Sockets ayant relevées des erreurs. • Le quatrième paramètre indique le délai d’attente de l’appel en microsecondes (1000 µs = 1ms) avant de rendre la main. Les test d’écriture ou les erreurs peuvent être ignorés, en passant la valeur null dans le paramètre de la méthode Select. Une fois la méthode exécutée la IList passée en paramètre ne contiendra plus que les Sockets ayant été marquées comme valides par le test (lecture, écriture, erreurs). Ainsi dans l’exemple, les Sockets restantes dans readList auront des données en attente de lecture immédiate, et la lecture sera alors non bloquante. La Socket serveur étant elle aussi présente dans readList, si elle est toujours dans la liste après l’appel, les clients viennent de se connecter. 4.3.Accepter un client Une fois le serveur lancé, grâce à la méthode Listen, ce dernier va pouvoir automatiquement recevoir des demandes de connexion, ces demandes seront mises dans la file d’attente des connexions (backlog défini en paramètre de la méthode Listen). La méthode Accept va chercher le premier client dans cette file d’attente, et créer un nouvel objet de type Socket pour prendre en charge la communication. try { Socket clientSocket = serverSocket.Accept(); clients.Add(clientSocket); readList.Remove(serverSocket); } catch (SocketException ex) { Console.Error.WriteLine("Une erreur s'est produite lors de la tentative d'accès au socket.\r\n{0}", ex.Message); } catch (ObjectDisposedException) { Console.Error.WriteLine("La Socket a été fermée."); } catch (InvalidOperationException) http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs Socket en .NET 18 / 18 { Console.Error.WriteLine("La socket n'est pas liée. Vous devez appeler la méthode Bind préalablement."); } La socket doit être en mode écoute pour pouvoir appeler la méthode Accept. Cette méthode est bloquante, et c’est pour cela qu’il faut la tester avant de l’appeler, si un client est en file d’attente de connexion. Elle retourne donc un objet initialisé de type Socket représentant le client qui vient de se connecter. 4.4.Terminer le serveur // coupe l'activité du serveur, en fermant la socket serverSocket.Close(); foreach (Socket cSocket in clients) { cSocket.Shutdown(SocketShutdown.Both); cSocket.Close(); } Tout d’abord fermer la Socket serveur, tout comme une Socket client normale, avec la méthode Close. Les communications en cours avec les différents clients sont alors stoppés. Remarques : Fermer la Socket serveur ne ferme pas automatiquement les connexions en cours. Vous devez le faire vous-même. D’autre part, il existe également des versions asynchrones pour les méthodes utilisées dans cet exemple, comme BeginAccept, BeginSend et BeginReceive. http://www.labo-dotnet.com Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs