Introduction aux Sockets en .NET
Introduction aux Sockets en .NET
com
Socket en .NET
SUPINFO DOTNET TRAINING COURSE
1. INTRODUCTION ............................................................................................................................................. 3
1.1. HISTORIQUE DES SOCKETS ............................................................................................................................ 3
1.2. FONCTIONNEMENT ........................................................................................................................................ 3
1.3. LES MODES SYNCHRONE ET ASYNCHRONE .................................................................................................... 4
[Link]
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
[Link] 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.
[Link]
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.
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.
Demande de
connexion
port
Serveur Client
Si tout ce passe bien, le serveur accepte la connexion, et va alors créer une nouvelle Socket associée à
un nouveau port.
[Link]
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
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.
EndTraitement(ar);
}
[Link]
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 [Link].
La classe IPHostEntry située dans cet espace de nom, donne des informations sur un hôte.
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.
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 :
ResolutionHoteLocal();
// ResolutionParNom(hostname);
[Link]
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);
[Link]();
}
[Link]();
[Link]();
}
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 7 / 18
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 8 / 18
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.
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 9 / 18
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.
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 10 / 18
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.
[Link]é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([Link], [Link],
[Link]);
}
catch (SocketException)
{
[Link]("La combinaison de addressFamily, socketType et
protocolType crée un socket non valide.\r\n", "Erreur",
[Link], [Link]);
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.
[Link]édure de connexion
IPEndPoint ep;
string ipString = "[Link]";
try
{
// création du point de terminaison pour la communication
ep = new IPEndPoint([Link](ipString), 80);
}
catch (ArgumentNullException)
{
[Link]("Erreur peu probable, 'ipString' est une référence
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 11 / 18
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.
[Link](ep);
}
catch (ArgumentNullException)
{
[Link]("Le point de terminaison est une référence null.",
"Erreur", [Link], [Link]);
return;
}
catch (SocketException ex)
{
[Link]("Une erreur s'est produite lors de la tentative
d'accès au socket.\r\n" + [Link], "Erreur", [Link],
[Link]);
return;
}
catch (ObjectDisposedException)
{
[Link]("La socket a été fermée.", "Erreur",
[Link], [Link]);
return;
}
catch ([Link])
{
[Link]("Un appelant situé plus haut dans la pile des
appels n'a pas l'autorisation pour l'opération demandée.", "Erreur",
[Link], [Link]);
return;
}
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 [Link]. 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
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 12 / 18
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 = [Link]("[Link]");
}
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.
[Link] 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 = [Link](requete);
try
{
[Link](buffer, 0, [Link], [Link]);
}
catch (ArgumentNullException)
{
[Link]("Le point de terminaison est une référence null.",
"Erreur", [Link], [Link]);
return;
}
catch (ArgumentOutOfRangeException)
{
[Link]("Les paramètres offset et/ou size sont invalides.",
"Erreur", [Link], [Link]);
return;
}
catch (SocketException ex)
{
[Link]("SocketFlags n'est pas une combinaison de valeurs
valide ou une erreur du système d'exploitation s'est produite lors de
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 13 / 18
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.
3.3.2. Réception
int ret;
byte[] rBuf = new byte[1024];
[Link] pageContent = new [Link]();
try
{
while ((ret = [Link](rBuf, 0, 1024, [Link])) > 0)
{
[Link]([Link](rBuf,
0, ret));
}
}
catch (ArgumentNullException)
{
[Link]("Le point de terminaison est une référence null.",
"Erreur", [Link], [Link]);
}
catch (ArgumentOutOfRangeException)
{
[Link]("Les paramètres offset et/ou size sont invalides.",
"Erreur", [Link], [Link]);
}
catch (SocketException ex)
{
[Link]("SocketFlags n'est pas une combinaison de valeurs
valide ou une erreur du système d'exploitation s'est produite lors de
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 14 / 18
[Link] = [Link]();
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.
[Link] de la connexion
try
{
// coupe les flux d'entrée ET de sortie et vide les buffers.
[Link]([Link]);
}
catch (SocketException ex)
{
[Link]("Une erreur s'est produite lors de la tentative
d'accès au socket.\r\n" + [Link], "Erreur", [Link],
[Link]);
}
catch (ObjectDisposedException)
{
[Link]("La socket a été fermée.", "Erreur",
[Link], [Link]);
}
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.
[Link]();
Remarque : Des versions asynchrones des méthodes utilisées existent, comme par exemple
BeginConnect, BeginReceive et BeginSend.
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 15 / 18
try
{
// on lie notre socket à notre point d'attache
[Link](localEP);
// on active le serveur, des clients peuvent désormais se connecter
dessus.
[Link](1);
}
catch (ArgumentNullException) // Appel de Bind
{
[Link]("Erreur peu probable, localEP est une
référence null.");
return;
}
catch (SocketException ex) // Appel de Bind et Listen
{
[Link]("Une erreur s'est produite lors de la
tentative d'accès au socket.\r\n{0}", [Link]);
return;
}
catch (ObjectDisposedException) // Appel de Bind et Listen
{
[Link]("La Socket a été fermée.");
return;
}
catch ([Link]) // Appel de Bind
{
[Link]("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.
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 16 / 18
[Link]
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
[Link](100);
}
• 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.
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.
[Link] 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 = [Link]();
[Link](clientSocket);
[Link](serverSocket);
}
catch (SocketException ex)
{
[Link]("Une erreur s'est produite lors de la
tentative d'accès au socket.\r\n{0}", [Link]);
}
catch (ObjectDisposedException)
{
[Link]("La Socket a été fermée.");
}
catch (InvalidOperationException)
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs
Socket en .NET 18 / 18
{
[Link]("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.
[Link] le serveur
// coupe l'activité du serveur, en fermant la socket
[Link]();
foreach (Socket cSocket in clients)
{
[Link]([Link]);
[Link]();
}
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.
[Link]
Ce document est la propriété de SUPINFO et est soumis aux règles de droits d’auteurs