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

Documents pareils