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
SendThreadetRecvThreadpour 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()
