Mis à jour le mardi 23 mai 2017
  • 20 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Communication via sockets

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Commençons dès maintenant le développement des sockets. Nous allons voir différents types de sockets ; dans cette partie, ce seront des sockets de liaison directe entre client et serveur. Deux programmes destinés à communiquer, donc.

Client/serveur

Nous allons maintenant entrer dans le vif du sujet ! Attaquons-nous au développement de nos applications capables de communiquer.

Vous l'aurez sûrement compris, deux êtres humains sont nécessaires pour une communication humaine. Deux programmes seront donc nécessaires pour une communication informatique. :)

Nous allons appliquer ici le concept de client/serveur. Pour apprendre les sockets, nous allons écrire un client demandant l'heure à un serveur. Ce dernier lui donnera.

Nous avons donc deux programmes à créer, deux projets. Je nommerai le client SocketClient et le serveur SocketServeur.

Dans ce chapitre, nous devons donc travailler conjointement sur les deux projets pour implémenter la structure visible à la figure suivante et expliquée dans la liste à puces juste après.

Nous allons devoir implémenter cette structure
Nous allons devoir implémenter cette structure
  • 1. Le serveur démarre et se met en attente de connexion.

  • 2. Le client démarre et demande une connexion au serveur.

  • 3. Le serveur voit la connexion client et l'accepte.

  • 4. Le client se met en attente de réception des données.

  • 5. Le serveur envoie l'heure actuelle.

  • 6. Le client reçoit l'heure et l'affiche.

Voilà le concept de base de nos deux programmes. Commençons dès maintenant la phase d'écriture du code.

La connexion

Commençons par le commencement : la connexion.

En sockets, pour se connecter, il faut que le serveur soit en « attente de connexion » , et les clients viennent effectuer leurs demandes pendant cette période.

Il existe deux façons pour cette étape : synchrone ou asynchrone.

Synchrone signifie que l'étape d'attente de connexion (par le serveur) et l'étape de demande de connexion (par le client) sont toutes les deux bloquantes. Bloquante indique que la fonction bloquera le programme tant que la connexion ne sera pas effectuée. Nous allons travailler en synchrone pour cette partie, cela va nous permettre de bien comprendre à quelle étape du traitement notre programme se situe.

Asynchrone signifie que ces deux opérations ne sont pas bloquantes et seront passées s'il y a connexion ou non. Cela peut être bien si vous souhaitez effectuer de multiples connexions de clients : lorsqu'un client est connecté, il est envoyé dans un processus séparé et sera traité indépendamment. Si nous sommes en synchrone, un seul client peut être traité à la fois (sauf si nous créons un nouveau thread).

Voyons tout de suite la connexion synchrone.

L'IP et le port

Pour toute connexion, il faut connaitre l'IP et le port que nous allons utiliser. Le port est comme une porte. Il faut que nos deux programmes soient sur le même pour pouvoir communiquer. L'IP est comme un numéro de téléphone, le client doit entrer celle du serveur pour s'y connecter.

Nous devons donc commencer à définir notre IP et notre port. Personnellement j'utilise le port 8080, si une erreur s'affiche, c'est peut-être que ce port est déjà utilisé, essayez d'en changer.

Dim port As String = "8080"
Dim ip As String = "127.0.0.1"

Le serveur

On commence par importer le namespace contenant les sockets :

Imports System.Net.Sockets
Imports System.Net

Puis on crée notre socket serveur :

Dim MonSocketServeur As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

Il y a trois arguments :

  • La famille d'adresse. Nous utiliserons InterNetwork dans ce cours, libre à vous d'examiner la documentation si vous souhaitez en utiliser une plus spécifique.

  • Le type de transfert de données. Ici Stream signifie « flux », cela assure un transfert fiable dans les deux sens. Il existe d'autres types comme Raw ou DGram.

  • Le protocole, ici TCP, mais il peut être UDP comme je l'ai expliqué dans le dernier chapitre.

Nous avons instancié notre socket serveur. Maintenant, nous devons le configurer pour lui dire quel port utiliser.

Commençons par créer un objet IPEndPoint.

Dim MonEP As IPEndPoint = New IPEndPoint(IPAddress.Parse(ip), port)

Vous remarquez que j'ai effectué un IPAddress.Parse(ip), car le constructeur d'IPEndPoint demande un objet de type IpAddress et non pas String.

Pour appliquer la configuration d'IP au socket :

MonSocketServeur.Bind(MonEP) 'Lie le socket à cette IP

Ensuite, on se met en attente de connexion :

MonSocketServeur.Listen(1) 'Se met en mode écoute

Le paramètre que j'ai spécifié ici à 1 est la taille de la file d'attente. Ici je sais que j'aurai seulement un client à la fois qui viendra demander l'heure. Si vous souhaitez accepter plus de clients dans la file d'attente, augmentez ce nombre.

Finalement, pour que le serveur soit pleinement apte à recevoir les connexions entrantes et les accepter, il faut se mettre en mode « acceptation ».

Dim SocketEnvoi As Socket = MonSocketServeur.Accept() 'Bloquant tant que pas de connexion

Nous sommes en communication synchrone, donc la fonction Accept() sera bloquante, le programme n'ira pas plus loin tant qu'un socket client ne s'y sera pas connecté.

Vous remarquez que cette fonction nous retourne un socket. Ce socket va être très important, il va nous servir à communiquer. Je vais essayer d'expliquer cela en termes simples.
Le socket serveur est juste là pour accepter les connexions, c'est comme un réceptionniste dans un restaurant : il est à l’accueil et voit des gens entrer. Le socket client, lui, est un client qui entre dans ce restaurant. Le réceptionniste va donc lui attribuer une table et, pour ne pas le laisser seul, va lui attribuer un serveur pour le servir. C'est ce nouveau serveur qui est généré qui va servir de passerelle entre le réceptionniste et le client. Pour communiquer avec le client, le réceptionniste va communiquer avec le serveur et ce dernier communiquera alors avec le client.

Ce socket créé est donc la passerelle par laquelle les messages en provenance ou à destination du client seront envoyés.

Le client

Il faut aussi créer notre socket dans notre programme client :

Dim MonSocketClient As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
Dim MonEP As IPEndPoint = New IPEndPoint(IPAddress.Parse(ip), port)
MonSocketClient.Connect(MonEP)

Comme pour le serveur, on doit spécifier l'IPEndPoint, suivi de la méthode Connect prenant cet objet en argument.

Voilà, le client et le serveur doivent à cette étape avoir établi une connexion. N'oubliez pas de fermer la connexion client avec la méthode Close() afin de libérer la connexion lorsque tout est terminé.

Maintenant, le transfert de données !

Le transfert de données

Une fois notre socket connecté, on peut passer à l'étape qui doit vous intéresser : le transfert de données.

Lorsque nous communiquons par sockets, les données transférées seront de type Byte, plus précisément un tableau de Byte.

Petit rappel sur les types. Le type Byte peut contenir des entiers de 0 à 255 (voir figure suivante). On peut donc l'utiliser pour stocker des caractères au format ASCII (les caractères sont codés sur 8 bits, ce qui correspond à 256 valeurs).

Tableau des caractères ASCII
Tableau des caractères ASCII

Comme vous le voyez, sur 256 valeurs, on peut en stocker des choses.

Ne vous inquiétez pas, vous n'aurez pas à connaître ce tableau par cœur :) , des fonctions se chargeront de convertir tout ça pour vous. ;)

Bref, après ce petit quart d'heure théorique, revenons au concret.

Nous n'allons pas directement manipuler ces caractères, nous allons utiliser des fonctions pour convertir un caractère en byte, et par extension une chaîne de caractères en tableau de bytes.

Pour convertir un String en tableau de Byte :

Dim MesBytes As Byte() = System.Text.Encoding.ASCII.GetBytes(MonString)

Sachant que ASCII peut être remplacé au choix par UTF7, UTF8, Unicode, UTF32, etc.
Je vous conseille de rester en ASCII pour le moment, il gère les accents et pas mal de caractères spéciaux. Si vous souhaitez en savoir plus sur ces codages, je vous renvoie vers les pages Wikipédia correspondantes.

Maintenant, la réception. De la même manière, vous allez recevoir un tableau de Byte, il faut le retransformer en String.

Dim MonString As String = System.Text.Encoding.ASCII.GetString(MesBytes)

Le concept de cette fonction est la même que la précédente.

Bon. Ce petit entracte sur le codage de nos données est passé, attaquons le transfert pur et dur.

Pour la communication synchrone, deux fonctions vont suffire pour l'envoi et la réception de données.

La fonction Send permet d'envoyer des données (utilisable de la même manière pour le client ou pour le serveur). On lui passe simplement les bytes à envoyer. La fonction nous renvoie le nombre de bytes qui ont été envoyés.

Dim BytesEnvoyes As Integer = MonSocketServeur.Send(MesBytes)

Et la méthode Receive qui prend en paramètre un tableau de Byte à remplir cette fois, il faut donc déclarer ce dernier avant la fonction pour le traiter après. Elle renvoie le nombre de bytes lus.

Dim MesBytes(255) As Byte
Dim BytesRecus As Integer = MonSocketClient.Receive(MesBytes)

Mais il faut que ma réception et mon envoi soient simultanés ?

Pas forcément, il existe un tampon, je m'explique. La méthode de réception, elle, est bloquante, tant qu'aucun byte ne sera présent dans le tampon de réception des données, le programme sera bloqué dessus. La méthode d'envoi, quant à elle, envoie ses données quoi qu'il arrive. Toutefois, si aucun socket ne vient lire, les bytes envoyés resteront dans un tampon. Le tampon est un stockage temporaire de bytes. Il ne peut pas en contenir une infinité, donc il faut bien veiller à que ce tampon soit vidé par la fonction de lecture.

Donc pour résumer, l'envoi et la réception peuvent s'effectuer avec plusieurs secondes d'écart et fonctionner quand même. Attention toutefois à bien veiller à lire les données envoyées quand même à un moment.

Pour terminer notre procédure, bien penser à fermer les sockets quand les connexions sont terminées :

MonSocket.Close()

Mini-TP : demande d'heure

Bon, vous avez toutes les cartes en main pour terminer ce petit fil rouge : le client demande l'heure, le serveur lui donne.

Voici la correction :

Serveur
Imports System.Net.Sockets
Imports System.Net

Module Module1
    Dim port As String = "8080"
    Dim ip As String = "127.0.0.1"

    Sub Main()
        Dim MonSocketServeur As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        Dim MonEP As IPEndPoint = New IPEndPoint(IPAddress.Parse(ip), port)

        MonSocketServeur.Bind(MonEP) 'Lie le socket à cette IP
        MonSocketServeur.Listen(1) 'Se met en mode écoute

        Console.WriteLine("Socket serveur initialisé.")

        While True 'Boucle à l'infini
            Console.WriteLine("En attente d'un client.")
            'Se met en attente de connexion et appelle TraitementConnexion() lors d'une connexion.
            Dim SocketEnvoi As Socket = MonSocketServeur.Accept() 'Bloquant tant que pas de connexion
            TraitementConnexion(SocketEnvoi)
        End While

    End Sub

    Sub TraitementConnexion(ByVal SocketEnvoi As Socket)
        Console.WriteLine("Socket client connecté, envoi de l'heure.")
        Try
            Dim Heure As Byte() = System.Text.Encoding.ASCII.GetBytes(Now.ToLongTimeString) 'Convertit l'heure en bytes

            Dim Envoi As Integer = SocketEnvoi.Send(Heure) 'Envoie l'heure au client
            Console.WriteLine(Envoi & " bytes envoyés au client")
        Catch ex As Exception
            Console.WriteLine("Erreur lors de l'envoi du message au socket. " & ex.ToString)
        End Try
    End Sub

End Module
Client
Imports System.Net.Sockets
Imports System.Net

Module Module1

    Dim port As String = "8080"
    Dim ip As String = "127.0.0.1"

    Sub Main()

        Dim MonSocketClient As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        Dim MonEP As IPEndPoint = New IPEndPoint(IPAddress.Parse(ip), port)
        Console.WriteLine("Socket client initialisé.")

        Try
            Console.WriteLine("Connexion au serveur ...")
            MonSocketClient.Connect(MonEP)
            TraitementConnexion(MonSocketClient)
        Catch ex As Exception
            Console.WriteLine("Erreur lors de la tentative de connexion : " & ex.ToString)
        End Try

        Console.ReadLine()

    End Sub

    Sub TraitementConnexion(ByVal SocketReception As Socket)
        Console.Write("Connecté, réception de l'heure : ")
        Dim Heure(255) As Byte 'Création du tableau de réception
        Try
            SocketReception.Receive(Heure) 'Réception
            Console.WriteLine(System.Text.Encoding.ASCII.GetString(Heure)) 'Affichage
        Catch ex As Exception
            Console.WriteLine("Erreur lors de la réception des données : " & ex.ToString)
        End Try
    End Sub

End Module

Voilà le code complet et fonctionnel d'une simple communication client/serveur en utilisant les sockets.

Bien sûr, ce code est très sommaire, il est à agrémenter en fonction des besoins. Je pense notamment à la sécurité : les sockets sont très faibles en termes de sécurité, ne tentez pas de faire transiter par ces dernières des données sensibles, à plus forte raison si vous voulez les transmettre via l'internet ou un réseau public.

Toutefois, la rapidité de mise en œuvre, la simplicité d'utilisation et la robustesse des sockets en font de très bons outils pour la mise en place d'une communication de ce type.

Après, à vous d'analyser votre cahier des charges pour savoir si oui ou non elles peuvent êtres mises en œuvre.

Voilà, vous savez désormais faire communiquer deux programmes entre eux. Vous l'avez compris, cette partie est bien utile, car les fonctions sont très simples. Cependant, ce type de socket est, comme vous le voyez, plutôt « lourd ». Il y a beaucoup de configurations préalables et qui ne nous interessent pas forcément. Un autre type de sockets va entrer en jeu pour nous simplifier la vie. Allons dès maintenant apprendre à l'utiliser.

  • On se sert de l'IP de l'ordinateur que l'on souhaite contacter comme un numéro de téléphone.

  • Bien vérifier que le port utilisé par le client et le serveur est le même. Si la connexion échoue, ce port est peut-être déjà utilisé.

  • Le socket serveur se met en écoute avec la méthode Listen.

  • On envoie et reçoit les données avec Send et Receive. Ces fonctions sont bloquantes.

  • On communique avec des bytes, il faut donc effectuer les conversions à l'envoi et lors de la réception.

Exemple de certificat de réussite
Exemple de certificat de réussite