« Programmation Python/Threads » : différence entre les versions
Contenu supprimé Contenu ajouté
m JackPotte a déplacé la page Apprendre à programmer avec Python/Communications à travers un réseau vers Programmation Python/Communications à travers un réseau |
Aucun résumé des modifications |
||
Ligne 1 :
<noinclude>{{Python}}</noinclude>
Ligne 27 ⟶ 24 :
Celle-ci est en effet parfaitement appropriée lorsqu'il s'agit de faire communiquer des ordinateurs interconnectés par l'intermédiaire d'un réseau local. C'est une technique particulièrement aisée à mettre en œuvre, et elle permet un débit élevé pour l'échange de données.
L'autre technologie (celle des paquets) serait préférable pour les communications expédiées via l'internet, en raison de sa plus grande fiabilité (les mêmes paquets peuvent atteindre leur destination par différents chemins, être émis ou ré-émis en plusieurs exemplaires si cela se révèle nécessaire pour corriger les erreurs de transmission), mais sa mise en œuvre est un peu plus complexe. Nous ne l'étudierons pas dans ce cours.
Le script ci-dessous met en place un serveur capable de communiquer avec un seul client. Nous verrons un peu plus loin ce qu'il faut lui ajouter afin qu'il puisse prendre en charge en parallèle les connexions de plusieurs clients.
<source lang=python line>
# Définition d'un serveur réseau rudimentaire
# Ce serveur attend la connexion d'un client, pour entamer un dialogue avec lui
import socket, sys
HOST = '192.168.14.152'
PORT = 50000
# 1) création du socket :
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2) liaison du socket à une adresse précise :
try:
mySocket.bind((HOST, PORT))
except socket.error:
sys.exit()
while 1:
# 3) Attente de la requête de connexion d'un client :
print "Serveur prêt, en attente de requêtes ..."
mySocket.listen(5)
connexion, adresse = mySocket.accept()
print "Client connecté, adresse IP %s, port %s" % (adresse[0], adresse[1])
msgClient = connexion.recv(1024)
if msgClient.upper() == "FIN" or msgClient =="":
break
msgServeur = raw_input("S> ")
msgClient = connexion.recv(1024)
# 6) Fermeture de la connexion :
print "Connexion interrompue."
connexion.close()
ch = raw_input("<R>ecommencer <T>erminer ? ")
if ch.upper() =='T':
break
</source>
;Commentaires
Ligne 108 ⟶ 103 :
Le script ci-dessous définit un logiciel client complémentaire du serveur décrit dans les pages précédentes. On notera sa grande simplicité.
<source lang=python line>
# Définition d'un client réseau rudimentaire
# Ce client dialogue avec un serveur ad hoc
import socket, sys
HOST = '192.168.14.152'
PORT = 50000
# 1) création du socket :
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2) envoi d'une requête de connexion au serveur :
try:
mySocket.connect((HOST, PORT))
print "La connexion a échoué."
sys.exit()
print "Connexion établie avec le serveur."
# 3) Dialogue avec le serveur :
msgServeur = mySocket.recv(1024)
while 1:
if msgServeur.upper() == "FIN" or msgServeur =="":
break
print "S>", msgServeur
msgClient = raw_input("C> ")
mySocket.send(msgClient)
msgServeur = mySocket.recv(1024)
# 4) Fermeture de la connexion :
print "Connexion interrompue."
mySocket.close()
</source>
;Commentaires
Ligne 190 ⟶ 182 :
Le script ci-après définit le programme client. Le serveur sera décrit un peu plus loin. Vous constaterez que la partie principale du script (ligne 38 et suivantes) est similaire à celle de l'exemple précédent. Seule la partie « Dialogue avec le serveur » a été remplacée. Au lieu d'une boucle <code>while</code>, vous y trouvez à présent les instructions de création de deux objets threads (aux lignes 49 et 50), dont on démarre la fonctionnalité aux deux lignes suivantes. Ces objets threads sont créés par dérivation, à partir de la classe <code>Thread()</code> du module ''threading''. Ils s'occuperont indépendamment de la réception et de l'émission des messages. Les deux threads « enfants » sont ainsi parfaitement encapsulés dans des objets distincts, ce qui facilite la compréhension du mécanisme.
<source lang=python line>
# Définition d'un client réseau gérant en parallèle l'émission
# et la réception des messages (utilisation de 2 THREADS).
host = '192.168.0.235'
port = 40000
import socket, sys, threading
class ThreadReception(threading.Thread):
"""objet thread gérant la réception des messages"""
def __init__(self, conn):
threading.Thread.__init__(self)
self.connexion = conn # réf. du socket de connexion
while 1:
print "*" + message_recu + "*"
if message_recu =='' or message_recu.upper() == "FIN":
# Le thread <réception> se termine ici.
th_E._Thread__stop()
self.connexion.close()
class ThreadEmission(threading.Thread):
"""objet thread gérant l'émission des messages"""
def __init__(self, conn):
threading.Thread.__init__(self)
self.connexion = conn # réf. du socket de connexion
while 1:
message_emis = raw_input()
self.connexion.send(message_emis)
# Programme principal - Établissement de la connexion :
connexion = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
connexion.connect((host, port))
except socket.error:
print "La connexion a échoué."
sys.exit()
print "Connexion établie avec le serveur."
# Dialogue avec le serveur : on lance deux threads pour gérer
# indépendamment l'émission et la réception des messages :
th_E = ThreadEmission(connexion)
th_R = ThreadReception(connexion)
th_E.start()
th_R.start()
</source>
;Commentaires
Ligne 273 ⟶ 263 :
Ce serveur n'est pas utilisé lui-même pour communiquer : ce sont les clients qui communiquent les uns avec les autres, par l'intermédiaire du serveur. Celui-ci joue donc le rôle d'un relais : il accepte les connexions des clients, puis attend l'arrivée de leurs messages. Lorsqu'un message arrive en provenance d'un client particulier, le serveur le ré-expédie à tous les autres, en lui ajoutant au passage une chaîne d'identification spécifique du client émetteur, afin que chacun puisse voir tous les messages, et savoir de qui ils proviennent.
<source lang=python line>
# Définition d'un serveur réseau gérant un système de CHAT simplifié.
# Utilise les threads pour gérer les connexions clientes en parallèle.
HOST = '192.168.0.235'
PORT = 40000
import socket, sys, threading
class ThreadClient(threading.Thread):
'''dérivation d'un objet thread pour gérer la connexion avec un client'''
def __init__(self, conn):
threading.Thread.__init__(self)
self.connexion = conn
# Dialogue avec le client :
msgClient = self.connexion.recv(1024)
if msgClient.upper() == "FIN" or msgClient =="":
message = "%s> %s" % (nom, msgClient)
# Initialisation du serveur - Mise en place du socket :
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
mySocket.bind((HOST, PORT))
except socket.error:
print "La liaison du socket à l'adresse choisie a échoué."
sys.exit()
print "Serveur prêt, en attente de requêtes ..."
mySocket.listen(5)
# Attente et prise en charge des connexions demandées par les clients :
conn_client = {} # dictionnaire des connexions clients
while 1:
connexion, adresse = mySocket.accept()
# Créer un nouvel objet thread pour gérer la connexion :
th = ThreadClient(connexion)
th.start()
it = th.getName() # identifiant du thread
print "Client %s connecté, adresse IP %s, port %s." %\
(it, adresse[0], adresse[1])
# Dialogue avec le client :
connexion.send("Vous êtes connecté. Envoyez vos messages.")
</source>
;Commentaires
Lignes 35 à 43 : L'initialisation de ce serveur est identique à celle du serveur rudimentaire décrit au début du présent chapitre.
Ligne 386 ⟶ 373 :
Voici un exemple de dialogue type, tel qu'il peut être suivi du côté d'un client. Les messages entre astérisques sont ceux qui sont reçus du serveur ; les autres sont ceux qui sont émis par le client lui-même :
<source lang=text line>
*serveur OK*
client OK
*canons,Thread-3;104;228;1;dark red,Thread-2;454;166;-1;dark blue,*
OK
orienter,25,
feu
*mouvement_de,Thread-4,549,280,*
*scores,Thread-4;1,Thread-3;-1,Thread-2;0,*
</source>
Lorsqu'un nouveau client démarre, il envoie une requête de connexion au serveur, lequel lui expédie en retour le message : « serveur OK ». À la réception de ce dernier, le client répond alors en envoyant lui-même : « client OK ». Ce premier échange de politesses n'est pas absolument indispensable, mais il permet de vérifier que la communication passe bien dans les deux sens. Étant donc averti que le client est prêt à travailler, le serveur lui expédie alors une description des canons déjà présents dans le jeu (éventuellement aucun) : identifiant, emplacement sur le canevas, orientation et couleur (ligne 3).
Ligne 430 ⟶ 415 :
Vous trouverez dans les pages qui suivent le script complet du programme serveur. Nous vous le présentons en trois morceaux successifs afin de rapprocher les commentaires du code correspondant, mais la numérotation de ses lignes est continue. Bien qu'il soit déjà relativement long et complexe, vous estimerez probablement qu'il mérite d'être encore perfectionné, notamment au niveau de la présentation générale. Nous vous laisserons le soin d'y ajouter vous-même tous les compléments qui vous sembleront utiles (par exemple, une proposition de choisir les coordonnées de la machine hôte au démarrage, une barre de menus, etc.) :
<source lang=python line>
#######################################################
# Jeu des bombardes - partie serveur #
# (C) Gérard Swinnen, Liège (Belgique)- Juillet 2004 #
# Licence : GPL #
# Avant d'exécuter ce script, vérifiez que l'adresse #
# IP ci-dessous soit bien celle de la machine hôte. #
# Vous pouvez choisir un numéro de port différent, ou #
# changer les dimensions de l'espace de jeu. #
# Dans tous les cas, vérifiez que les mêmes choix ont #
# été effectués pour chacun des scripts clients. #
#######################################################
host, port = '192.168.0.235', 35000
largeur, hauteur = 700, 400 # dimensions de l'espace de jeu
from Tkinter import *
import socket, sys, threading, time
import canon03
from canon04 import Canon, AppBombardes
class Pupitre(canon03.Pupitre):
"""Pupitre de pointage amélioré"""
def __init__(self, boss, canon):
canon03.Pupitre.__init__(self, boss, canon)
def tirer(self):
"déclencher le tir du canon associé"
self.appli.tir_canon(self.canon.id)
def orienter(self, angle):
"ajuster la hausse du canon associé"
self.appli.orienter_canon(self.canon.id, angle)
def valeur_score(self, sc =None):
"imposer un nouveau score <sc>, ou lire le score existant"
if sc == None:
return self.score
else:
self.points.config(text = ' %s ' % self.score)
def inactiver(self):
"désactiver le bouton de tir et le système de réglage d'angle"
self.bTir.config(state =DISABLED)
"changer la position du curseur de réglage"
self.regl.config(state =NORMAL)
self.regl.config(state =DISABLED)
</source>
La classe <code>Pupitre()</code> est construite par dérivation de la classe de même nom importée du modune ''canon03''. Elle hérite donc toutes les caractéristiques de celle-ci, mais nous devons surcharger<ref>Rappel : dans une classe dérivée, vous pouvez définir une nouvelle méthode avec le même nom qu'une méthode de la classe parente, afin de modifier sa fonctionnalité dans la classe dérivée. Cela s'appelle surcharger cette méthode
Dans la version monoposte du logiciel, en effet, chacun des pupitres pouvait commander directement l'objet canon correspondant. Dans cette version réseau, par contre, ce sont les clients qui contrôlent à distance le fonctionnement des canons. Par conséquent, les pupitres qui apparaissent dans la fenêtre du serveur ne peuvent être que de simples répétiteurs des manoeuvres effectuées par les joueurs sur chaque client. Le bouton de tir et le curseur de réglage de la hausse sont donc désactivés, mais les indications fournies obéissent aux injonctions qui leur sont adressées par l'application principale.
Ligne 1 245 ⟶ 1 227 :
{{références}}
{{todo|
# ajouter des images
}}
|