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 et RecvThread 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()

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *