Sommaire
Introduction
Justification
Le problème consistant à faire communiquer deux programmes est l'un de ceux qui ont généré la plus grande littérature et le plus grand code de toute l'informatique (avec l'invalidation de cache, nommer des choses et les frameworks Web).
Face à un tel problème, un programmeur pense immédiatement « je vais utiliser un middleware ». Si vous ne savez pas vraiment ce qu'est un middleware, rassurez-vous, personne ne le sait vraiment. Aujourd'hui, vous avez peut-être entendu parler de leur dernier avatar : les services web. Comme notre programmeur va s'en rendre compte, on a maintenant deux problèmes. La charge d'écriture, d'utilisation et de maintenance du code utilisant des middlewares est toujours énorme.
Parce qu'ils sont conçus pour gérer un très grand nombre de fonctionnalités et de situations complexes - la plupart d'entre elles impliquant des utilisateurs adverses, des utilisateurs robots, ou les deux.
Mais la plupart du temps, le problème réel n'implique pas vraiment ces situations. Du moins pas au début (… ce qui signifie probablement jamais). Les personnes connaissant l'histoire des middlewares diront que leur caractéristique principale n'est pas seulement le passage de messages, mais aussi l'appel à distance, qui implique la sérialisation des objets. Mais la plupart du temps, les messages sont assez simples de toute façon, et utiliser un middleware pour implémenter la sérialisation d'une liste d'instances ayant trois membres de types fondamentaux n'est pas une bonne utilisation de votre temps.
Si vous construisez des (premières versions de) programmes communicants qui fonctionneront sur un réseau local (sûr), et pour lesquels les messages échangés sont connus et simples, alors j'ai une bonne nouvelle : vous n'avez pas besoin d'utiliser des services web (ou tout autre type de middleware).
VOUS DEVEZ JUSTE SAVOIR COMMENT LIRE/ÉCRIRE DEPUIS/VERS DES FICHIERS (SPÉCIAUX).
Vue d'ensemble
L'idée de base est qu'au lieu de programmer l'interface réseau de votre service avec des sockets de bas niveau ou toute autre bibliothèque de haut niveau, vous pouvez simplement mettre en œuvre des mécanismes de requête/réponse en utilisant des tubes nommés.
Les tubes nommés sont des fichiers spéciaux FIFO (First In, First Out) qui bloquent les E/S et mettent en œuvre une forme très basique de passage de messages, sans avoir à s'embarrasser avec le polling. De plus, ils sont très faciles à utiliser, puisqu'il s'agit simplement de fichiers dans lesquels vous lisez/écrivez.
Une fois que vous avez créé votre service au-dessus de ces tubes, il est facile de l'intégrer dans une interface réalisée avec d'autres langages/outils. Par exemple, il est très facile de l'exposer sur le réseau en utilisant des outils courants comme socat
.
Attention, ce n'est pas sécurisé, vous ne devez l'utiliser qu'à des fins de test dans un réseau local sûr.
Principe
Le principe théorique peut être représenté par ce diagramme de séquence UML :
Tubes nommés
┌────────┴────────┐
┌──────┐ ┌──────┐ ┌──────┐ ┌───────┐
│Client│ │SORTIE│ │ENTRÉE│ │Service│
└───┬──┘ └─┬────┘ └────┬─┘ └───┬───┘
│ │ │ │
│ │ │┌───────╢
│ │ bloque││attente║
│requête │ │└──────→║
├─────────────────────→│ │
╟───────┐│ ├───────→│
║attente││bloque │ ║traitement
║←──────┘│ │ ║
│ │←─────────────────────┤
│←───────┤ │ réponse│
│ │ │ │
Notes :
- Le service est démarré en premier et attend l'entrée, mais comme les processus sont bloquants, l'ordre de démarrage n'a pas toujours d'importance.
- Il y a deux tuyaux, ici (un pour l'entrée et un pour la sortie), pour des raisons de simplicité, mais vous pouvez tout aussi bien n'en utiliser qu'un seul.
Exemples
Pour créer les tuyaux nommés sous Linux ou MacOS, utilisez la commande mkfifo
, comme indiqué dans le script build.sh.
La création de tuyaux nommés sous Windows est plus complexe, vous pouvez consulter la question Stack Overflow correspondante.
Exemple trivial : un service de chat
L'exécutable pcat
implémente un service qui lit depuis un tuyau nommé et imprime son contenu sur la sortie standard. C'est comme une commande cat
, mais qui ne s'arrêterait pas après la première lecture, mais continuerait à lire depuis le tube.
Ce type de service est juste une simple boucle qui itère sur les appels d'E/S bloquants sur les tuyaux nommés, ayant ainsi un coût CPU nul pour l'interrogation.
Remarque : si cet exemple imprime « Hello World ! » en continu, c'est parce que vous n'avez pas créé le fichier de données
comme un tube nommé, mais comme un fichier normal. Par conséquent, au lieu de vider son contenu après la lecture, il continue à lire le même contenu.
#!/usr/bin/env python
import sys
if __name__ == "__main__":
while True:
with open(sys.argv[1]) as fin:
line = fin.readline()
sys.stdout.write(line)
Un service simple
Le premier exemple ./service in out
implémente un service qui lit depuis un tuyau nommé in
et écrit vers un autre tuyau out
.
Une fois lancé, le service va attendre que les tuyaux soient consommés, par exemple avec deux commandes. La première écrit une entrée dans le tuyau d'entrée :
echo "données" > in
Le second lit le résultat :
cat out
Notez que vous pouvez utiliser le même tuyau pour l'entrée et la sortie
: ./service1 data data
.
#!/usr/bin/env python
import sys
if __name__ == "__main__":
print("Start server")
while True:
with open(sys.argv[1]) as fin:
datas = fin.readline()
data = datas.strip()
print("Received: <",data,">", file=sys.stderr)
with open(sys.argv[2], 'w') as fout:
fout.write(data)
if data == "exit":
break
print("Stop server", file=sys.stderr)
Exposer ces services sur le réseau
Si vous souhaitez exposer un tel service en tant que serveur réseau, utilisez simplement socat.
Par exemple, pour obtenir une requête de données du réseau pour le service1
:
socat -v -u TCP-LISTEN:8423,reuseaddr PIPE :./data
(voir run_socat_server.sh
pour un exemple complet).
Vous pouvez le tester en envoyant quelque chose sur la connexion :
echo "Hello World !" > /dev/tcp/127.0.0.1/8423
Inversement, pour renvoyer automatiquement la réponse à un serveur :
socat -v -u PIPE :./out TCP2:8424:host
Sachez que socat
se termine dès qu'il reçoit la fin du message.
Ainsi, si vous souhaitez établir un portail permanent, vous devrez utiliser l'option fork
:
socat TCP-LISTEN:8478,reuseaddr,fork PIPE:/./data