Project

General

Profile

Communication entre deux ordinateurs avec le module socket de python » History » Version 27

« Previous - Version 27/34 (diff) - Next » - Current version
Jean-Michel Bain-Cornu, 04/24/2019 10:05 AM


Communication entre deux ordinateurs avec le module socket de python

Comment faire communiquer deux ordinateurs avec des programmes écrits en python ?
Il y a plein de moyens disponibles de réaliser cette communication, mais le plus simple et le plus souple est sans aucun doute d'utiliser le module socket.

Si vous êtes pressé.
Les programmes : comSock

Présentation

Deux scripts très simples, basés sur l'utilisation de deux modules utilisant socket . Ils fonctionnent quel que soit le système d'exploitation, ce qui permet de faire dialoguer entre elles des machines équipées de linux, de raspbian, de macOS, et bien sûr de windows.
socket est un module fourni en standard avec python. Il est relativement facile d'utilisation, mais pas pour les débutants. Voici donc deux petites classes réalisées par votre serviteur, une pour le client : ComSockCli, et une autre pour le serveur : ComSockSrv, ainsi qu'un exemple d'utilisation. Le but est de permettre des échanges basiques en simplifiant au maximum la "tuyauterie", et de proposer un mode de fonctionnement qui devrait déjà au moins satisfaire votre curiosité.
Si vous n'êtes pas à l'aise avec le concept de classe, pas de panique. Suivez le tuto, reprenez les exemples et modifiez-les. Le mode de fonctionnement s'éclaircira de lui-même au fil du temps.

De quoi avez-vous besoin ?

Deux ordinateurs pour commencer. Ce n'est pas obligatoire, vous pouvez faire les tests avec un seul pour commencer. D'ailleurs, il peut être utile de communiquer d'un process à un autre sur un système unique, et la solution présentée est tout à fait envisageable dans ce cas.
Un réseau tcp/ip fonctionnel ensuite. Ce qui veut dire que les machines doivent être capables de s'envoyer des ping. Notez que certains firewalls interdisent le ping, et si vos deux ordis communiquent par internet, cette commande peut ne pas fonctionner sans pour autant vous interdire la communication. Le port IP choisi dans la démo est le 8000. Il ne doit pas être utilisé par un autre logiciel sur l'ensemble des machines destinées à communiquer, et il doit être autorisé par le firewall s'il y en a un. On peut évidemment utiliser un autre port, il faut dans ce cas changer dans les deux scripts, côté client et côté serveur. Ceci serait largement configurable, au prix d'un petit supplément de complexité. Voir en fin de tuto pour savoir comment faire.
Enfin, vous avez besoin de python version 3. Sur linux, python est partie intégrante du système, donc rien à installer. Sous Windows, installer de préférence une version 64 bits, généralement référencée "amd64". La version 32 bits fonctionnerait, mais ne présente aucun intérêt dans le cas présent. Toujours sous Windows, veillez lors de l'installation à cocher la case concernant la modification des variables d'environnement (planquée dans les options "custom"). Ceci vous permettra d'accéder à python en ligne de commande depuis n'importe quel répertoire du système.

Tester la configuration

Vous devez choisir les rôles de vos ordinateurs. L'un d'entre eux sera considéré comme le serveur, et les autres seront considérés comme les clients.
Chaque client pourra envoyer un ou plusieurs messages sur le serveur, qui renverra une réponse. Il n'est pas possible avec les programmes présentés ici de dialoguer d'un client à l'autre si vous possédez plus d'un client.

Côté OS, j'ai utilisé linux et windows pour les exemples. Une fois python installé, le mode opératoire est le même sur les deux systèmes, à la nuance près que la commande pour lancer python est python3 sous linux, et simplement python sous windows.

Réseau : tester les accès :

ping nom_serveur

doit donner des messages du genre :
64 bytes from serveur (127.0.0.1): icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from serveur (127.0.0.1): icmp_seq=1 ttl=64 time=0.049 ms

et pareil pour tester l'accès au client depuis le serveur, en utilisant évidemment le nom du client.
Si les noms de machines ne fonctionnent pas, vous pouvez utiliser les adresses IP, que ce soit avec la commande ping ou dans les programmes. Je ne vous le conseille pas ; le jour ou votre réseau changera, plus rien ne fonctionnera... Faites plutôt marcher votre nommage réseau comme il faut : cela peut vous sembler difficile au premier abord, mais vous y gagnerez sur plein d'aspects.

Tester Python :
Sous linux :

python3 -V

Sous Windows :
python -V

doivent donner la version de python, 3.x .

Que font exactement les programmes ?

Un "serveur.py" qui attend des messages et renvoie des réponses, et un "client.py" qui fait l'inverse.
"serveur.py" doit être lancé sur le serveur.
Lançons une fenêtre de terminal (de commande sous Windows) et allons-y :

python3 serveur.py

Le programme ne rend pas la main, et rien ne se passe. C'est normal, le serveur attend un message. Pour l'arrêter, ctrl-c , et vous reprenez la main.
Il est possible d'automatiser le lancement du programme au démarrage du système, mais ce n'est pas l'objet ici. Soit dit en passant, sous windows, ce n'est pas de la tarte...

Rappel : sous linux, la commande est python3. Sous windows, c'est python

Une fois le serveur lancé, vous pouvez tester le client, avec une fenêtre de terminal ouverte sur le client :

python3 client.py

et vous obtenez :
['Salut les gars ! Tout va bien sur le serveur ?', 'On en attend un roman']
Ouais, ici ça tourne ! On en dira pas plus...

Le client a bien démarré, il a affiché le message envoyé au serveur (la première ligne), et la réponse reçue (la deuxième ligne). Côté serveur, rien n'a bougé en apparence.
Si par hasard le serveur ne tourne pas, vous obtenez :
['Salut les gars ! Tout va bien sur le serveur ?', 'On en attend un roman']
Traceback (most recent call last):
  File "client.py", line 21, in <module>
    reponse = maCom.envoi(msg)
  File "/home/jm/Documents/comSock/comSockCli.py", line 25, in envoi
    s.connect((self.host,port))
ConnectionRefusedError: [Errno 111] Connection refused

C'est normal, le client a affiché le message envoyé, et il s'est planté car aucun serveur n'était à l'écoute. Le message peut être différent, si par exemple le nom du serveur (ou l'adresse IP) n'est pas reconnu par le réseau.

À propos du message et de la réponse, ils peuvent être n'importe quel objet python : un entier, une chaîne, une liste, etc. C'est l'avantage, vous pouvez envoyer et recevoir des structures complètes, ce qui donne un grand confort de fonctionnement. On peut tout-à-fait imaginer qu'un client réclame périodiquement au serveur une liste de relevés de températures par exemple, ou la photo prise par une webcam. Les seules limites sont votre imagination ainsi que la capacité des systèmes.
Je vois des spécialistes qui objectent : pourquoi pas des structures json ? Je réponds : pas de gros mots siouplé. On est en python ici, on travaille sérieusement. Non mais... json n'a d'intérêt que si on veut communiquer avec javascript, ou un autre langage que python. Dans le cas présent, ce serait un facteur limitant. Ceci étant, python possède un module qui permet de faire du json. Avis aux amateurs...

Voyons maintenant en détail le fonctionnement de nos deux programmes exemples.

Le programme client : "client.py"

'''
Ce programme affiche et envoie un message au serveur,
puis reçoit et affiche la réponse.
Lancement : python3 client.py
Fonctionne avec python 3 et ses modules de base.
'''

# le module de gestion des échanges
from comSockCli import *

# nom du serveur qui attend les messages.
# Ce nom doit pouvoir répondre à une commande "ping nom_serveur" 
host = 'monServeur'
# instantiation de la classe
maCom = ComSockCli(host)
# prépare un objet à transmettre, en l'occurrence une liste
msg = ['Salut les gars ! Tout va bien sur le serveur ?','On en attend un roman']
#
print(msg)
# transmet l'objet et reçoit la réponse
reponse = maCom.envoi(msg)
#
print(reponse)

En ligne 9, nous avons :

from comSockCli import *

Rien de bien mystérieux, on importe le module "comSockCli.py", qui contient la tuyauterie. Allez-y voir quand vous aurez une minute ; ce n'est pas bien violent !
En ligne 13, on définit le nom du serveur :
host = 'monServeur'

Voir plus haut le commentaire sur le réseau. Si vous voulez tester sur une seule et unique machine, mettez 'localhost' à la place de 'monServeur'.
En ligne 15 :
maCom = ComSockCli(host)

Ici, on crée un objet 'maCom' qui est une instance de la classe 'ComSockCli' contenue dans le module importé en ligne 9. On en profite pour lui passer le nom du serveur en paramètre. Cet objet nous servira pour utiliser les capacités de sa classe d'origine. À propos des objets et des classes, le web regorge d'explications à ce sujet. La page wikipedia par exemple est très complète, mais il y a plus simple. Quoi qu'il en soit, la compréhension du fonctionnement des classes en python est un préalable à tout travail sérieux. Qu'on se le dise, et qu'on y passe une heure ou deux, ça vaut le coup.
En ligne 17, on se prépare un petit message.
msg = ['Salut les gars ! Tout va bien sur le serveur ?','On en attend un roman']

J'ai prévu une liste, mais comme on a dit, on peut prendre autre chose. Précision : on envoie un objet, et on récupère un autre objet. Bon, vous aviez compris je pense.
En ligne 21, on envoie "msg", et on récupère la réponse :
reponse = maCom.envoi(msg)

Et voilà ! Le type de la réponse dépend de ce qu'envoie 'serveur.py', et justement, voyons un peu ce qui se passe de ce côté.

Le programme serveur: "serveur.py"

Un peu plus subtil :

'''
Ce programme attend des messages de clients et les traite,
Lancement : python3 serveur.py
Arrêt : ctrl-c
Fonctionne avec python 3 et ses modules de base.
'''

# le module de gestion des échanges
from comSockSrv import *

class MonServeur(ComSockSrv):
    '''Classe de gestion des messages côté serveur.
    Hérite des fonctions de gestion de ComSockSrv .
    Traite les commandes de service des clients, et renvoie les résultats.'''

    def traiteMsg(self,objet):
        '''Votre traitement ici.
        "objet" contient l'objet python envoyé par le client.
        Vous devez renvoyer un autre objet python lorsque votre
        traitement est terminé'''

        # cet exemple renvoie simplement une chaîne
        return 'Ouais, ici ça tourne ! On en dira pas plus...'

# instantiation de la classe
monService = MonServeur()
# démarre l'écoute, et donc les traitements associés
monService.run()

Comme pour le client, on importe un module. "comSockSrv.py" cette fois, qui est le pendant de "comSockCli.py", côté serveur.
La comparaison s'arrête là. On va cette fois en ligne 11 dériver la classe "ComSockSrv" :
class MonServeur(ComSockSrv):

Ce qui signifie que l'on crée notre propre classe qui va hériter des comportements de "ComSockSrv".
On a juste à surclasser la méthode "traiteMsg", ce que l'on fait en ligne 16 :
def traiteMsg(self,message):

C'est ainsi la méthode de "serveur.py" qui va être appelée et non pas celle de "ComSockSrv".
À quel moment ? Et bien quand un message va être reçu, sous la forme du paramètre "objet", qui n'est autre qu'une copie de l'objet "msg" envoyé par "client.py".
En ligne 23, on renvoie une réponse :
return 'Ouais, ici ça tourne ! On en dira pas plus...'

dont "client.py" va recevoir une copie sous la forme de l'objet "reponse".
Voilà. Le reste est basique. En ligne 26, on crée une instance du serveur :
monService = MonServeur()

et en ligne 28, on démarre la boucle d'attente des messages :
monService.run()

qui se terminera quand le programme sera (brutalement) arrêté.

Et maintenant, à l'action...

Vous pouvez modifier les programmes à votre guise, et vous concentrer sur les échanges de données que vont pouvoir faire vos ordinateurs.

Comment faire ?
  • récupérez les quatre scripts ici : comSock
  • copiez-les dans un répertoire du serveur, puis dans un répertoire du client
  • changez le contenu de la variable host dans client.py
  • Lancez serveur.py sur le serveur
  • Lancez client.py sur le client

Le tout doit fonctionner tel que déjà décrit.
Il ne vous reste qu'à choisir ce que vous voulez transmettre comme données en envoi dans client.py en modifiant la ligne 21 (remplacez simplement la variable msg par celle de votre choix) :

reponse = maCom.envoi(maVariable)

De même, dans serveur.py, modifiez la fonction traiteMsg pour lui faire effectuer le traitement de votre choix, et retourner le résultat approprié.
    def traiteMsg(self,objet):
        '''Votre traitement ici.
        "objet" contient l'objet python envoyé par le client.
        Vous devez renvoyer un autre objet python lorsque votre
        traitement est terminé'''

        # cet exemple renvoie simplement une chaîne
        return maReponse

Et c'est tout.

Si le port IP ne vous convient pas, vous pouvez le modifier dans les deux scripts ComSockCli.py et ComSockSrv.py, à la ligne contenant port = 8000 .

Pour conclure...

Voilà, vous avez maintenant deux scripts qui peuvent communiquer n'importe quoi, n'importe où, pourvu qu'il aient un réseau à leur disposition.
Un mot sur les performances. Transmettez peu, et le moins souvent possible, et vous n'aurez que des satisfactions. Théoriquement, tout ceci est assez robuste et permet de nombreux clients simultanés sur un serveur modeste. Mais il y a des limites, et si vous envoyez des milliers de photos sur un raspberry zero, peut-être que ceci ne fonctionnera pas toujours très bien.
Personnellement, j'utilise ce genre de script depuis des années, et lorsqu'il n'y a pas de problème réseau, la fiabilité en est excellente.

Enfin, si vous souhaitez encrypter vos contenus, voyez du côté du module "hashlib" lui aussi fourni en standard, et qui permet par exemple le sha256.

Bon courage et à bientôt !