Applications en réseau
Voici une petite base de programme pour réaliser des applications qui doivent s’échanger des données entre ordinateurs en réseau.
Très utile pour programmer des jeux par exemple …
Le principe est simple :
- mise en relation de deux programmes sur postes distants avec une méthode P2P (voir les sockets de Python) pour créer les sockets,
- 2 threads
SendThread
etRecvThread
pour gérer les échanges de données (afin de ne pas interrompre le fonctionnement du programme principal, - échange des données par l’intermédiaire de deux files de chaînes de caractères (les données structurées peuvent facilement être mises au format JSON ou XML),
- le remplissage et le vidage des files peut se faire à tout moment par le programme principal, de chaque coté,
- dans cet exemple, l’adresse IP doit ici être passée en argument dans la ligne de commande de lancement du script par Python :
chemin_vers_python\python.exe p2p.py 192.168.0.1
from threading import Thread, Event import socket from time import sleep import sys from random import randint PORT_ECOUTE = 12345 # pour le serveur (le port du client est déterminé automatiquement) N_ESSAIS = 10 class File: def __init__(self): self._l = [] # attribut privé : on ne doit utiliser que l'interface élémentaire def __repr__(self): s = " ".join([str(e) for e in self._l]) h = "─"*len(s) return h+"\n"+s+"\n"+h def est_vide(self): return len(self._l) == 0 def defiler(self): return self._l.pop(0) def enfiler(self, n): self._l.append(n) ################################################################################################# # Thread dédié à la réception des données class RecvThread(Thread): def __init__(self, s, fin_connexion, toRecv): super().__init__() self.socket = s self.fin_connexion = fin_connexion self.toRecv = toRecv def run(self): while not self.fin_connexion.is_set(): try: d = self.socket.recv(1024).decode() except ConnectionAbortedError: print("Erreur de réception") d = "" except socket.timeout: d = "" if len(d) > 0: self.toRecv.enfiler(d) sleep(.1) ################################################################################################# # Thread dédié à l'envoi des données class SendThread(Thread): def __init__(self, s, fin_connexion, toSend): super().__init__() self.socket = s self.fin_connexion = fin_connexion self.toSend = toSend def run(self): while not self.fin_connexion.is_set(): if not self.toSend.est_vide(): d = self.toSend.defiler() d = bytes(d, "utf-8") try: self.socket.sendall(d) except ConnectionResetError: print("Erreur d'envoi") sleep(.1) def connexion(host, n=0): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) # Pour ne pas attendre trop longtemps try: # Client ? (un serveur 'host' existe déjà) # Tentative de connexion au serveur # ('host' sur le port par défaut) adresse_pair = (host, PORT_ECOUTE) s.connect(adresse_pair) print("Mode Client") except socket.timeout: # 1er arrivé --> Serveur with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_ecoute: socket_ecoute.bind(('', PORT_ECOUTE)) socket_ecoute.listen() socket_ecoute.settimeout(4) print("Mode Serveur : attente d'une connexion ...'") try: s, adresse_pair = socket_ecoute.accept() except socket.timeout: s = None if s is not None: print("Connexion établie avec", adresse_pair) else: print("Echec de connexion") return s, adresse_pair # Context manager # à utiliser avec with ... as ... class LiaisonP2P: def __init__(self, host): # Conteneurs de données à échanger self.toSend = File() self.toRecv = File() self.host = host def __enter__(self): # Création du socket i = 0 self.s = None while self.s is None and i < N_ESSAIS: self.s, a = connexion(self.host) i += 1 # Lancement des Threads self.fin_connexion = Event() self.tr = RecvThread(self.s, self.fin_connexion, self.toRecv) self.ts = SendThread(self.s, self.fin_connexion, self.toSend) self.tr.start() self.ts.start() return self def __exit__(self, exc_type, exc_val, exc_tb): # Fermeture de la connexion self.fin_connexion.set() if self.s: self.s.close() # Arrêt des threads for t in [self.tr, self.ts]: t.join() # Gestion des erreurs if exc_type is not None: print(f'an error occurred: {exc_val}') # Exemple d'utilisation if __name__ == "__main__": # Récupération de l'IP du pair # dans les arguments de la ligne de commande host = sys.argv[1] print(host) with LiaisonP2P(host) as link: while True: # Exemple d'utilisation des files # toSend est régulièrement et automatiquement envoyée et vidée # toRecv est automatiquement remplie et doit être vidée link.toSend.enfiler(f"{link.a} : {randint(0,99)}") sleep(1) while not link.toRecv.est_vide(): print(link.toRecv) link.toRecv.defiler()