Network Programming

Ecrit par Daniel Campos.
Quelques corrections de Benoît Minisini./

Introduction

Le futur, au moins pour les années qui viennent, est fondé sur les réseaux et les standards. Même les grandes sociétés qui ont toujours essayé de cacher leurs formats internes de fichiers, on changé partiellement leur manière de penser, et maintenant il semble qu’elles aient découvert le feu, parlent de XML, SOAP, RPC, et de choses dans le genre, qui probablement, pourraient être des affaires courantes aujourd’hui si elles y avaient mis quelques efforts et quelqu’argent dans le passé.

Pensez que ...

Nul ne peut aujourd’hui imaginer isoler les périphériques : il vous faut en extraire des données, savoir ce qui se passe même si vous êtes à l’autre bout du monde. Les périphériques sont faits par différents manufacturiers, et les clients pensent que ce n’est pas leur affaire de les rendre compatibles. Aussi, les sociétés ont besoin de standards pour partager l’information. Les sociétés ont besoin de contrôler leurs, l’information c’est le pouvoir et une chance de grossir et de gagner de l’argent frais. Les sociétés on confiance en l’appui des grandes sociétés de «hardware» et de software.

Aussi...

Les nouvelles applications dites "d'arrière guichet" (back-office) fournies par les principales sociétés, sont basées sur des standards connus : base de données (SQL), serveurs http tels que Apache, XML, RPC et SOAP. Si les grandes sociétés agissent dans ce sens, les sociétés moyennes feront de même ... ou mourront.

XML, RPC et SOAP

Quand on étudie XML, la première idée est "quelle chose stupide". Bien, XML semble n’être fait que de balises et de texte aléatoire qu’on a mis dans un fichier. Et XML c’est exactement ça. La puissance de XML est que c’est un standard international, et qu’il peut être facilement employé par les gens et les ordinateurs. Beaucoup de gens peuvent l’apprendre, pas seulement des programmeurs, créer une implémentation, écrite un document XML avec un éditeur de texte, et un tas de librairies et autres "software" les aideront à partager leur document, ou à traduire en html ou PDF de magnifiques rapports. Et HTTP n’est qu’un Protocole, comme il y en a beaucoup. Les protocoles sont ce que les langues sont pour les gens, si deux personnes connaissent une même langue, elles peuvent partager l’information. Mais la naissance d’Arpanet et la croissance d’Internet ont rendu ce protocole quasi populaire : il est employé pour recevoir des pages web, des images, des fichiers, des données commerciales et tout ce que vous pouvez imaginer depuis des serveurs qui sont placés tout autour de la planète. Aussi, pratiquement tout le "hardware" et "software" des réseaux (routeurs, pare-feux, ordinateur de l’utilisateur final, serveurs...) sont parés pour gérer ce protocole. Pensez-y à nouveau : si vous utilisez HTTP pour envoyer et recevoir votre information, vous n’avez pas à vous soucier des imbrications physiques (n.d.t. : ubication dans le texte original,c’est plus savoureux) de votre cible : tout le "hardware" dans le monde entier, gros réseaux avec embranchements, transporteront l’information sans vous poser aucune question, pressez simplement le bon bouton (vous savez, habituellement, c’est le bouton gauche de la souris) , et laisser la grosse machinerie travailler.

Voilà où nous trouvons XML-RPC et SOAP. Ce sont des protocoles qui permettent aux ordinateurs de parler entre eux. En les utilisant, ils peuvent faire plus que de partager quelques documents : un ordinateur peut envoyer à un autre des ordres pour réaliser certaines tâches, et le second ordinateur renverra le résultat de son action. Vous avez déjà cela dans votre ordinateur : certains programmes appellent des bibliothèques et d’autres programmes pour effectuer leur travail. La seule chose nouvelle dans XML-RPC et SOAP, c’est que maintenant, les conversations ne sont plus seulement dans votre ordinateur, mais entre ordinateurs. Mais c’est une grande chose. L’idée n’est pas du tout nouvelle, mais maintenant, elle s’appuie sut le réseau et les standards de format de données : HTTP pour transporter l’information, XML pour écrire des ordres, des questions et des réponses. La finalité de tout cela est qu’aujourd’hui c’est très facile, pour les programmeurs et même pour quelques utilisateurs, d’écrire des programmes qui seront connectés à d’autres programmes tournant sur d’autres ordinateurs, dans différentes parties du monde. Et l’autre point important, c’est que vous n’avez pas besoin de disposer d’une plateforme "hardware-software" prédéfinie, ils sont standards : vous pouvez acheter le "hardware" ou le "software" chez de nombreux fournisseurs différents. Vous pouvez acheter ce dont vous avez besoin : pas cher ? très performant ? évolutif ? Regardez, comparez, et achetez ou même utilisez gratuitement ce qu’il y a de mieux pour vous.

Gambas se doit d’être là

Gambas a besoin d’implémenter les standards, et je dirais même plus : Linux a besoin de Gambas. Gambas est un interpréteur de langage BASIC. BASIC est un langage facile à apprendre, en fait le nom du language est un acronyme derivé de Beginner's All-purpose Symbolic Instruction Code (code d'instructions symboliques polyvalent pour débutant). Tout le monde peut en apprendre un peu, et commencer à faire des petits programmes et des scripts. Bon nombre de programmeurs ont commencé par apprendre le BASIC quand ils étaient enfants, peut-être sur des CPU 8-bits. Bon nombre de personnes travaillant dans différents départements d'administration de différentes sociétés et du gouvernement connaissent au moins un peu de BASIC. Ils ne sont pas du tout programmeurs, mais ils ont ce dont ils ont besoin et ils peuvent écrire des macros pour les aider dans leur travail avec de petites ou moyennes bases de données, des feuilles de calcul et des documents texte. Ils savent comment créer une formule, lire et écrire des données dans des tables, partager quelques informations en les envoyant par mail... D’un autre côté, il y a des légions de développeurs web, trop occupés pour apprendre les intrications des pointeurs et la gestion de mémoire des langages comme le 'C'. Ils doivent écrire de l’ html, créer de belles images, administrer des serveurs, s’occuper des pirates et beaucoup d’autres choses. Ils ont besoin d’un langage simple pour créer des interfaces CGI (Common Gateway Interface). Il y a des langages comme PHP ou Perl, et il y en a un appelé ASP (qui n’est autre qu’un BASIC mélangé à de l’HTML), donc ils connaissent déjà le BASIC sans le savoir.

Alors, la conclusion est que des tas de gens attendent le BASIC pour migrer vers Linux. Que pouvons-nous faire ici ? Nous sommes au milieu du gué : nous avons une interface GUI (Graphical User Interface = interface graphique) simple est bien conçue (peu de projets peuvent en dire autant), nous avons l’interpréteur, et nous avons nombre de bibliothèques écrites en C ou C++ attendant de nous aider, peut-être la plus grosse API (Application Programming Interface = Interface de fonctions) qu’aucun système d’exploitation ait jamais connue. Pour traverser vers l’autre rive nous n’avons besoin que d’implémenter ces standards qui permettront aux programmeurs et aux utilisateurs de se connecter à l’information dont ils ont besoin. Le composant réseau est une partie de cet objectif : ouvrons la porte qui nous offrira le reste du monde.

Le composant réseau

Ce n’est pas réellement un composant, il doit être implémenté en trois composants au moins : gb.net, gb.net.curl et gb.xml.

gb.net est à la base de tout :

Actuellement le web est basé sur les protocoles TCP/IP. Nous pouvons dire que la partie IP de ce protocole est du ressort du système d’exploitation, nous devons nous préoccuper de la partie TCP. Pour implémenter toutes les affaires nous avons les classes suivantes :
  • DnsClient: est une classe pour convertir les noms d’hôte (comme "Gambas.sourceforge.net"), qui sont compris par les humains, en adresses IP (comme 192.168.0.1) qui sont les noms réels des ordinateurs. Elle peut effectuer son travail en utilisant des ressources du système d’exploitation : DNS (Domain name server), NIS (Network Information Services Services d'information sur le réseau), fichiers hosts, NMB lookup (résolution de noms), LDAP (Lightweight Directory Access Protocol = Protocole de gestion d'annuaires de réseau), etc.

  • Socket : pour partager l’information avec les serveurs, nous avons besoin d’établir une connexion TCP. Cette classe peut faire ça, et envoyer ou recevoir des données au format "raw" (brut).

  • ServerSocket : parfois nous pouvons être le serveur, et nous avons besoin de permettre au client de se connecter avec nous. Voici la classe pour faire ceci en utilisant TCP/IP.

  • UdpSocket : quelques tâches internet, comme le transfert d’un flux multimédia, ne fonctionnent pas bien si elles utilisent une connexion, aussi utilisent-elles habituellement UDP, un moyen plus "primitif" de transférer des données, sans diagramme de contrôle de flux. La classe Datagram est dévolue à la fois à la création des clients et des serveurs UDP.

  • Les sockets Locaux ou Unix : nous n’avons parfois pas besoin de nous connecter à d’autres ordinateurs, mais avec d’autres programmes du même ordinateur. L’utilisation de TCP pour cela n’est pas efficace, et vous gaspillez une ressource limitée comme le sont les ports TCP. Les sockets Unix sont des sockets locaux implémentés par le système d’exploitation pour "émuler" les sockets TCP dans l’ordinateur (oh, d’accord, ce n’est pas une conférence technique, J’essaye simplement de vous donner une idée, vous savez ?), et le partage des données, en utilisant cette méthode, est assez rapide et économique. Les ClientSocket ? et ServerSocket ? peuvent aussi tous les deux établir des connexions socket-Unix.

  • Port série : maintenant parlons du reste du monde de l’ordinateur. Tous les périphériques, spécialement conçus pour les tâches industrielles ou le commerce, n’ont pas une connexion Ethernet. Peut-être 70% d’entre eux ont des ports série RS-232, RS-485, ou RS-422. Et les programmeurs doivent souvent en extraire ou leur envoyer des informations, puis traiter l’information et la convertir en donnée à insérer dans une base de données, des pages web, etc. Aussi y a-t-il également une classe appelée "SerialPort" pour permettre l’envoi et la réception de données via un port série.

Les classes SerialPort, Socket et UdpSocket héritent d’une classe générique Stream. Cela signifie que vous pouvez les utiliser comme descripteur de fichier, en utilisant les méthodes standard Gambas comme "read", "write", ou "close". Il vous sera donc facile d’apprendre à les utiliser.

gb.net.curl

'libcurl' est une librairie libre et portable écrite par des gens de http://curl.haxx.se. Elle fournit toutes les pièces nécessaires pour gérer les protocoles réseaux de haut niveau, comme HTTP, FTP et TELNET. Cette librairie sera utile pour écrire des composants avec les classes suivantes :
  • HttpClient : en utilisant ClientSocket ? et ServerSocket ? Vous ne recevrez que des données brutes, qu’il vous faudra traiter et vérifier. Ce client vous permet de vous connecter avec les serveurs HTTP (comme Apache, oui), s’occupant de tout le protocole HTTP, vous n’aurez qu’à vous préoccuper du document que vous recevez (ou objet, comme les DOM people aiment à le dire). (Cette classe est à l’état ALPHA)

  • FtpClient : le protocole FTP est l’un des outils de base pour certaines sociétés : ils peuvent envoyer et recevoir des données, par exemple au format ASCII, et les traiter pour consolider les données d’une base. (pas encore implémenté)

  • TelnetClient? : Permet aux programmes Gambas de contrôler à distance d’autres machines. (pas encore implémenté)

gb.xml sont les outils pour se connecter au monde

Notez que gb.xml n’est pas complètement implémenté à ce jour.

  • Il y aura au moins deux classes pure-XML différentes :
    • Une classe basée sur l’arborescence : elle sauvegarde toute l’information XML en mémoire, ce qui rend très facile le travail avec XML, vous pouvez vous déplacer dans n’importe quelle partie du document, et le modifier aisément, en très peu de temps.

    • Une classe XMLReader, plus conventionnelle : elle lit simplement des parties d’un document XML à partir d’un fichier, sans le conserver en mémoire. Peut être que c’est plus lent, mais quand vous avez un gros document, un modèle arborescent risque d’utiliser toutes vos ressources mémoire, et même de planter l’ordinateur.

    • Peut être une classe XML basée sur SAX: c’est utile pour analyser des documents pendant que vous les recevez depuis un flux. (c’est utile pour d’autres choses, également)

  • Il devrait y avoir une classe client XML-RPC : cela vous permettrait d’appeler des serveurs RPC distants et de recevoir les données comme si vous aviez juste appelé des méthodes depuis votre propre programme, ou au moins d’une façon quasi similaire. Rien à concernant TCP/IP ou XML, juste des entiers, des chaînes, des tableaux...

  • Il devrait y avoir aussi un serveur XML-RPC, pour deux raisons : la première, pour "décrypter" l’information entrante provenant d’un client en format XML vers des types de données, et la seconde, pour permettre la création de serveurs autonomes sans avoir besoin de "monstres" comme Apache.

Dans le futur il devrait y avoir du travail sur le frontal SOAP. L’idée de SOAP est pratiquement similaire à XML-RPC, il est aussi basé sur XML et HTTP, mais avec SOAP vous pouvez effectuer des tâches très complexes, il est puissant, mais d’un autre côté il est assez complexe.

Programmation avec le composant gb.net

Sockets Connectés: Clients et Serveurs

Un socket est juste un endroit depuis lequel vous pouvez lire et écrire des données. Cependant, la définition du socket ne parle pas de serveurs, clients, connexions, etc. C’est un concept placé à l’étage suivant. TCP et les sockets Locaux sont des implémentations particulières dans lesquelles il y a un contrôle de flux et des spécifications à propos des rôles des programmes : client et serveur.

Il y a deux manières de travailler avec des sockets connectés :
  • sockets TCP : vous pouvez les utiliser pour communiquer avec les programmes locaux ou distants.

  • sockets Locaux ou Unix : juste pour communiquer avec les programmes sur votre propre machine.

Maintenant implémentons deux programmes pour jouer avec les sockets TCP et Locaux :
  • Le client se connectera avec un serveur et enverra une chaîne 'hello' au serveur.

  • Le serveur retournera 'bye'.

  • Le client fermera la connexion après la lecture du message bye.

Côté Client : Agissant comme un Client TCP ou Local (Unix)

Pour créer un programme dans lequel vous vous connecterez à un serveur distant ou local en utilisant des sockets TCP ou des sockets Locaux, vous devez utiliser la classe "Socket". Nous utiliserons ici deux façons d’implémenter ça, la première implémentation n’utilise aucun évènement :
  1. Créer un nouveau projet appelé "Sock1".

  2. Aller dans "project","properties","components", enlever "qt" et ajouter "net", puis presser "OK".

  3. Ajouter un nouveau module appelé M1.

  4. Éditer le code suivant...

Avant la Méthode Main(), définissons un objet à partir de la classe 'Socket' :

' Fichier module Gambas
PUBLIC MySock AS Socket
...
Maintenant, la première chose que nous faisons est d’initialiser l’objet :
...
PUBLIC SUB Main()
  MySock=NEW Socket
...
Puis, nous devons nous connecter avec un serveur distant ou local, aussi avons-nous besoin d’utiliser la méthode Connect(). Si nous voulons nous connecter en utilisant le protocole TCP, nous devons spécifier le nom de l’hôte distant ou son IP, et le port auquel se connecter :
  ...
  MySock.Connect ( "name_of_host",3450 )
  ...
Si nous devons nous connecter à un serveur Local (sockets UNIX), il nous faut spécifier le chemin depuis ce socket :
  ...
  MySock.Connect ("/path/to/socket")
  ...
Le processus de connexion prend du temps, nous ne pouvons donc débuter les envois ou réceptions de données à la prochaine ligne de code, à la place, nous devons attendre jusqu’à ce que la connexion soit réalisée ou qu’il y ait une erreur dans la tentative de connexion. Si nous nous connectons réellement, la propriété "Status" de Socket changera sa valeur en Socket.Connected (valeur 7), mais si le processus de connexion échoue, la propriété Status changera pour une valeur inférieure à zéro.Nous allons donc attendre dans une boucle...
  ...
  DO WHILE (MySock.Status <> 7) And (MySock.Status >0 )
    WAIT 0.1
  LOOP
  ...
Notez que vous devez utiliser la méthode "Wait", pour laisser la boucle d’évènements mettre à jour Socket Status. Après ceci, indiquons un code d’erreur, quelque chose a échoué, ou, si la connexion a été établie, envoyons quelques données :
  ...
  IF MySock.Status <> 7 THEN
    PRINT "Erreur"
    QUIT
  END IF
  WRITE #MySock, "hello",5
...
Maintenant, lisons la réponse du serveur. D’abord, nous attendons jusqu’à ce qu’il y ait des données à lire, en le testant par la méthode Lof. Puis, nous lirons les données :
  ...
  DO WHILE Lof(MySock)=0
    WAIT 0.1
  LOOP
  READ #MySock, sBuf, Lof(MySock)
  PRINT sBuf
  ...
Finalement, nous fermons le socket :
  ...
  CLOSE #MySock
C’est tout.

Le code complet est :
PUBLIC MySock AS Socket

PUBLIC SUB Main()

  DIM sBuf AS String
  MySock = NEW Socket

  MySock.Connect("name_of_host", 3450)

  DO WHILE (MySock.Status <> 7) AND (MySock.Status > 0)
    WAIT 0.1
  LOOP

  IF MySock.Status<>7 THEN
    PRINT "Erreur"
    QUIT
  END IF

  WRITE #MySock, "hello",5

  DO WHILE Lof(MySock)=0
    WAIT 0.1
  LOOP

  READ #MySock, sBuf, Lof(MySock)
  PRINT sBuf
  CLOSE #MySock

END

Maintenant, implémentons une autre version, en utilisant les évènements et non pas une attente active.
  1. Créer un nouveau projet appelé "Sock2".

  2. Aller dans "project","properties","components", enlever "qt" et ajouter "net", puis presser "OK"

  3. Ajouter une classe appelée "ClsMain".

  4. Editer le code suivant...

Nous avons besoin d’une instance de notre classe, et un Socket :

' Fichier de classe Gambas
STATIC App AS ClsMain
PUBLIC  MySock AS Socket
...
Dans la méthode _New de notre classe, nous démarrerons la connexion avec le socket :
...
PUBLIC SUB _New()
  MySock=NEW Socket AS "MySock"
  MySock.Connect("name_of_host",3450)
END
...
Dans la méthode main, nos créons une nouvelle instance de notre classe, ainsi nous démarrons la connexion, car la méthode _New est appelée quand on crée un objet :
...
STATIC PUBLIC SUB Main()
  App=NEW ClsMain
END
...
Quand la connexion a été établie avec succès, l’évènement "Ready" du socket est levé, et nous envoyons notre chaîne au serveur :
...
PUBLIC SUB MySock_Ready()
    WRITE #MySock,"Hello",5
END
...
Mais, s’il y avait une erreur, l’évènement Error serait levé :
...
PUBLIC SUB MySock_Error()
    PRINT "Connexion impossible "
END
...
Quand les nouvelles données arrivent, l’évènement Read est levé, nous lisons donc les données, les portons à l’écran, et fermons le socket :
...
PUBLIC SUB MySock_Read()
  DIM sCad AS String
  READ #MySock,sCad,Lof(MySock)
  PRINT sCad
  CLOSE #MySock
END
....

Voilà le code complet :
' fichier de classe Gambas
STATIC App AS ClsMain
PUBLIC  MySock AS Socket

PUBLIC SUB MySock_Ready()

  WRITE #MySock,"Hello",5

END

PUBLIC SUB MySock_Read()

  DIM sCad AS String
  READ #MySock,sCad,Lof(MySock)
  PRINT sCad
  CLOSE #MySock

END


PUBLIC SUB _New()

  MySock=NEW Socket AS "MySock"
  MySock.Connect("name_of_host",3450)


END

PUBLIC SUB MySock_Error()

   PRINT " Connexion impossible "

END

STATIC PUBLIC SUB Main()

  App=NEW ClsMain

END

Coté Serveur : Agissant comme un Serveur TCP ou Local (Unix)

Pour faire marcher un serveur, nous avons besoin d’une classe 'ServerSocket'. Elle écoute les connexions, et retourne un nouvel objet Socket pour chaque connexion client, nous pouvons ainsi gérer de multiples clients.
  1. Crér un nouvel objet appelé "Srv".

  2. Aller dans "project","properties","components", enlever "qt" et ajouter "net", puis presser "OK".

  3. Ajouter une nouvelle classe appelée "ClsServer".

  4. Editer ce code :

Comme nous l’avons fait dans le deuxième exemple client, il nous faut une instance de notre classe. Nous avons besoin également d’un objet ServerSocket?, et d’un tableau d’objets pour placer les objets Socket nécessaires pour communiquer avec nos clients.

STATIC Server AS ClsServer
PUBLIC Clients AS Object[]
PUBLIC Srv AS ServerSocket
...
Au démarrage du programme, nos créons l’instance de notre classe.
...
STATIC PUBLIC SUB Main()
  Server=NEW ClsServer
END
...
Quand la classe est chargée par l’interpréteur, il appelle la méthode _New. Nous initialisons ici le tableau d’objets, et démarrons le serveur. Pour faire cela, nous devons spécifier le port TCP à écouter, le type de socket, et appeler la méthode 'Listen'.
...
PUBLIC SUB _New()
  Clients =NEW Object[]
  Srv=NEW ServerSocket AS "Srv"
  Srv.Port=3450
  Srv.Type=ServerSocket.Internet
  Srv.Listen()
END
...
Si nous voulions utiliser les sockets Locaux ou Unix, au lieu de sockets TCP, nous devrions spécifier le type de socket Local, et un chemin plutôt qu’un port.
...
PUBLIC SUB _New()
  Clients =NEW Object[]
  Srv=NEW ServerSocket AS "Srv"
  Srv.Path="/path/to/my/socket"
  Srv.Type=ServerSocket.Local
  Srv.Listen()
END
...
Quand le serveur écoute, chaque fois qu’un client tente de se connecter à notre service, l’évènement 'Connection' du serveur est levé. Ici nous devons accepter cette connexion en appelant la méthode 'Accept'. Elle nous retourne un objet 'Socket' pour prendre en charge cette connexion, il y aura donc un objet 'Socket' pour chaque connexion client, que nous enregistrons dans notre tableau d’objets.
...
PUBLIC SUB Srv_Connection(Host AS String)
  DIM MySock AS Socket
  PRINT "Acceptation de la connexion venant de --> " & Host
  MySock=Srv.Accept()
  Clients.Add(MySock)
END
...
Notre client nous enverra le message 'hello', et l’évènement 'Read' provenant de 'Socket' sera levé. Notez que nous utilisons ici le mot-clé 'LAST' pour savoir lequel de nos clients a envoyé ce message. Ici nous lisons cette chaîne, et envoyons notre chaîne 'bye'
...
PUBLIC SUB Socket_Read()
  DIM sCad AS String
  READ #LAST,sCad,Lof(LAST)
  PRINT "Received data -->" & sCad
  WRITE #LAST,"bye",3
END
...
Finalement, une fois que le client a clos la connexion, nous recevons l’évènement 'Closed', et, comme cette connexion n’est plus en vie, nous supprimons cet objet 'Socket' de notre tableau :
...
PUBLIC SUB Socket_Closed()
  PRINT "Connexion fermée"
  Clients.Remove(Clients.Find(LAST))
END
...
Le code complet est :
'Fichier classe Gambas class
STATIC Server AS ClsServer
PUBLIC Clients AS Object[]
PUBLIC Srv AS ServerSocket

PUBLIC SUB Socket_Read()

  DIM sCad AS String
  READ #LAST,sCad,Lof(LAST)
  PRINT "Received data -->" & sCad
  WRITE #LAST,"bye",3

END

PUBLIC SUB Socket_Closed()

  PRINT "Connexion fermée"
  Clients.Remove(Clients.Find(LAST))

END

PUBLIC SUB Srv_Connection(Host AS String)

  DIM MySock AS Socket
  PRINT " Acceptation de la connexion venant de --> " & Host
  MySock=Srv.Accept()
  Clients.Add(MySock)

END

PUBLIC SUB _New()

  Clients =NEW Object[]
  Srv=NEW ServerSocket AS "Srv"
  Srv.Port=3450
  Srv.Type=ServerSocket.Internet
  Srv.Listen()

END

STATIC PUBLIC SUB Main()

  Server=NEW ClsServer

END