« Programmation Python/Threads » : différence entre les versions

Contenu supprimé Contenu ajouté
Aucun résumé des modifications
Ligne 1 :
{{todo|
# ajouter images
# lier}}
<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.{{todo|-> livre}}
 
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>
{{todo|num linéaire}}
# 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
<pre>
 
1.# Définition d'un serveur réseau rudimentaire
HOST = '192.168.14.152'
2.# Ce serveur attend la connexion d'un client, pour entamer un dialogue avec lui
PORT = 50000
3.
 
4.import socket, sys
# 1) création du socket :
5.
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
6.HOST = '192.168.14.152'
 
7.PORT = 50000
# 2) liaison du socket à une adresse précise :
8.
try:
9.# 1) création du socket :
mySocket.bind((HOST, PORT))
10.mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
11.
12.# 2) print "La liaison du socket à une l'adresse précisechoisie :a échoué."
sys.exit()
13.try:
 
14. mySocket.bind((HOST, PORT))
while 1:
15.except socket.error:
# 3) Attente de la requête de connexion d'un client :
16. print "La liaison du socket à l'adresse choisie a échoué."
print "Serveur prêt, en attente de requêtes ..."
17. sys.exit()
mySocket.listen(5)
18.
19.while 1:
20. # 34) AttenteEtablissement de la requête de connexion d'un client :
connexion, adresse = mySocket.accept()
21. print "Serveur prêt, en attente de requêtes ..."
print "Client connecté, adresse IP %s, port %s" % (adresse[0], adresse[1])
22. mySocket.listen(5)
23.
24. # 45) EtablissementDialogue deavec lale connexionclient :
25. connexion,.send("Vous adresseêtes =connecté mySocketau serveur Marcel.accept( Envoyez vos messages.")
msgClient = connexion.recv(1024)
26. print "Client connecté, adresse IP %s, port %s" % (adresse[0], adresse[1])
27. while 1:
28. # 5) Dialogue avec leprint client"C>", :msgClient
if msgClient.upper() == "FIN" or msgClient =="":
29. connexion.send("Vous êtes connecté au serveur Marcel. Envoyez vos messages.")
break
30. msgClient = connexion.recv(1024)
msgServeur = raw_input("S> ")
31. while 1:
32. print "C>", msgClientconnexion.send(msgServeur)
msgClient = connexion.recv(1024)
33. if msgClient.upper() == "FIN" or msgClient =="":
 
34. break
# 6) Fermeture de la connexion :
35. msgServeur = raw_input("S> ")
36. connexion.send(msgServeur"Au revoir !")
print "Connexion interrompue."
37. msgClient = connexion.recv(1024)
connexion.close()
38.
 
39. # 6) Fermeture de la connexion :
ch = raw_input("<R>ecommencer <T>erminer ? ")
40. connexion.send("Au revoir !")
if ch.upper() =='T':
41. print "Connexion interrompue."
break
42. connexion.close()
</source>
43.
44. ch = raw_input("<R>ecommencer <T>erminer ? ")
45. if ch.upper() =='T':
46. break
</pre>
 
;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>
{{todo|num linéaire}}
# Définition d'un client réseau rudimentaire
# Ce client dialogue avec un serveur ad hoc
 
import socket, sys
 
HOST = '192.168.14.152'
<pre>
PORT = 50000
1.# Définition d'un client réseau rudimentaire
 
2.# Ce client dialogue avec un serveur ad hoc
# 1) création du socket :
3.
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4.import socket, sys
 
5.
# 2) envoi d'une requête de connexion au serveur :
6.HOST = '192.168.14.152'
try:
7.PORT = 50000
mySocket.connect((HOST, PORT))
8.
9.# 1) création duexcept socket .error:
print "La connexion a échoué."
10.mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sys.exit()
11.
print "Connexion établie avec le serveur."
12.# 2) envoi d'une requête de connexion au serveur :
 
13.try:
# 3) Dialogue avec le serveur :
14. mySocket.connect((HOST, PORT))
msgServeur = mySocket.recv(1024)
15.except socket.error:
 
16. print "La connexion a échoué."
while 1:
17. sys.exit()
if msgServeur.upper() == "FIN" or msgServeur =="":
18.print "Connexion établie avec le serveur."
break
19.
print "S>", msgServeur
20.# 3) Dialogue avec le serveur :
msgClient = raw_input("C> ")
21.msgServeur = mySocket.recv(1024)
mySocket.send(msgClient)
22.
msgServeur = mySocket.recv(1024)
23.while 1:
 
24. if msgServeur.upper() == "FIN" or msgServeur =="":
# 4) Fermeture de la connexion :
25. break
print "Connexion interrompue."
26. print "S>", msgServeur
mySocket.close()
27. msgClient = raw_input("C> ")
</source>
28. mySocket.send(msgClient)
29. msgServeur = mySocket.recv(1024)
30.
31.# 4) Fermeture de la connexion :
32.print "Connexion interrompue."
33.mySocket.close()
</pre>
 
;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>
{{todo|num à droite}}
# 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'
<pre>
port = 40000
1.# Définition d'un client réseau gérant en parallèle l'émission
 
2.# et la réception des messages (utilisation de 2 THREADS).
import socket, sys, threading
3.
 
4.host = '192.168.0.235'
class ThreadReception(threading.Thread):
5.port = 40000
"""objet thread gérant la réception des messages"""
6.
def __init__(self, conn):
7.import socket, sys, threading
threading.Thread.__init__(self)
8.
self.connexion = conn # réf. du socket de connexion
9.class ThreadReception(threading.Thread):
10. """objet thread gérant la réception des messages"""
11. def __init__run(self, conn):
while 1:
12. threading.Thread.__init__(self)
13. self.connexion = conn message_recu = # réfself. du socket de connexion.recv(1024)
print "*" + message_recu + "*"
14.
if message_recu =='' or message_recu.upper() == "FIN":
15. def run(self):
16. while 1: break
# Le thread <réception> se termine ici.
17. message_recu = self.connexion.recv(1024)
18. # On force la printfermeture "*"du +thread message_recu<émission> + "*":
th_E._Thread__stop()
19. if message_recu =='' or message_recu.upper() == "FIN":
20. print "Client arrêté. Connexion breakinterrompue."
self.connexion.close()
21. # Le thread <réception> se termine ici.
22. # On force la fermeture du thread <émission> :
class ThreadEmission(threading.Thread):
23. th_E._Thread__stop()
"""objet thread gérant l'émission des messages"""
24. print "Client arrêté. Connexion interrompue."
def __init__(self, conn):
25. self.connexion.close()
threading.Thread.__init__(self)
26.
self.connexion = conn # réf. du socket de connexion
27.class ThreadEmission(threading.Thread):
28. """objet thread gérant l'émission des messages"""
29. def __init__run(self, conn):
while 1:
30. threading.Thread.__init__(self)
message_emis = raw_input()
31. self.connexion = conn # réf. du socket de connexion
self.connexion.send(message_emis)
32.
 
33. def run(self):
# Programme principal - Établissement de la connexion :
34. while 1:
connexion = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
35. message_emis = raw_input()
try:
36. self.connexion.send(message_emis)
connexion.connect((host, port))
37.
except socket.error:
38.# Programme principal - Établissement de la connexion :
print "La connexion a échoué."
39.connexion = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sys.exit()
40.try:
print "Connexion établie avec le serveur."
41. connexion.connect((host, port))
42.except socket.error:
# Dialogue avec le serveur : on lance deux threads pour gérer
43. print "La connexion a échoué."
# indépendamment l'émission et la réception des messages :
44. sys.exit()
th_E = ThreadEmission(connexion)
45.print "Connexion établie avec le serveur."
th_R = ThreadReception(connexion)
46.
th_E.start()
47.# Dialogue avec le serveur : on lance deux threads pour gérer
th_R.start()
48.# indépendamment l'émission et la réception des messages :
</source>
49.th_E = ThreadEmission(connexion)
50.th_R = ThreadReception(connexion)
51.th_E.start()
52.th_R.start()
</pre>
 
;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>
{{todo|idem}}
# 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'
<pre>
PORT = 40000
1.# Définition d'un serveur réseau gérant un système de CHAT simplifié.
 
2.# Utilise les threads pour gérer les connexions clientes en parallèle.
import socket, sys, threading
3.
 
4.HOST = '192.168.0.235'
class ThreadClient(threading.Thread):
5.PORT = 40000
'''dérivation d'un objet thread pour gérer la connexion avec un client'''
6.
def __init__(self, conn):
7.import socket, sys, threading
threading.Thread.__init__(self)
8.
self.connexion = conn
9.class ThreadClient(threading.Thread):
10. '''dérivation d'un objet thread pour gérer la connexion avec un client'''
11. def __init__run(self, conn):
# Dialogue avec le client :
12. threading.Thread.__init__(self)
13. nom = self.connexiongetName() = conn # Chaque thread possède un nom
14. while 1:
msgClient = self.connexion.recv(1024)
15. def run(self):
if msgClient.upper() == "FIN" or msgClient =="":
16. # Dialogue avec le client :
17. nom = self.getName() # Chaque thread possède un nombreak
message = "%s> %s" % (nom, msgClient)
18. while 1:
19. msgClientprint = self.connexion.recv(1024)message
20. if# msgClient.upper()Faire ==suivre "FIN"le ormessage msgClientà tous les autres clients =="":
21. for cle in breakconn_client:
22. message = "%s> %s" %if cle != (nom,: # ne pas le renvoyer à msgClient)l'émetteur
23. print conn_client[cle].send(message)
24. # Faire suivre le message à tous les autres clients :
25. # Fermeture de la forconnexion cle in conn_client:
26. if cle != nom:self.connexion.close() # ne pascouper lela renvoyerconnexion àcôté l'émetteurserveur
27. del conn_client[nom] # supprimer son conn_client[cle].send(message)entrée dans le dictionnaire
28. print "Client %s déconnecté." % nom
29. # FermetureLe thread se termine ici de la connexion :
 
30. self.connexion.close() # couper la connexion côté serveur
# Initialisation du serveur - Mise en place du socket :
31. del conn_client[nom] # supprimer son entrée dans le dictionnaire
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
32. print "Client %s déconnecté." % nom
try:
33. # Le thread se termine ici
mySocket.bind((HOST, PORT))
34.
except socket.error:
35.# Initialisation du serveur - Mise en place du socket :
print "La liaison du socket à l'adresse choisie a échoué."
36.mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sys.exit()
37.try:
print "Serveur prêt, en attente de requêtes ..."
38. mySocket.bind((HOST, PORT))
mySocket.listen(5)
39.except socket.error:
 
40. print "La liaison du socket à l'adresse choisie a échoué."
# Attente et prise en charge des connexions demandées par les clients :
41. sys.exit()
conn_client = {} # dictionnaire des connexions clients
42.print "Serveur prêt, en attente de requêtes ..."
while 1:
43.mySocket.listen(5)
connexion, adresse = mySocket.accept()
44.
# Créer un nouvel objet thread pour gérer la connexion :
45.# Attente et prise en charge des connexions demandées par les clients :
th = ThreadClient(connexion)
46.conn_client = {} # dictionnaire des connexions clients
th.start()
47.while 1:
48. # Mémoriser la connexion, adressedans le dictionnaire =: mySocket.accept()
it = th.getName() # identifiant du thread
49. # Créer un nouvel objet thread pour gérer la connexion :
50. thconn_client[it] = ThreadClient(connexion)
print "Client %s connecté, adresse IP %s, port %s." %\
51. th.start()
(it, adresse[0], adresse[1])
52. # Mémoriser la connexion dans le dictionnaire :
# Dialogue avec le client :
53. it = th.getName() # identifiant du thread
connexion.send("Vous êtes connecté. Envoyez vos messages.")
54. conn_client[it] = connexion
</source>
55. print "Client %s connecté, adresse IP %s, port %s." %\
56. (it, adresse[0], adresse[1])
57. # Dialogue avec le client :
58. connexion.send("Vous êtes connecté. Envoyez vos messages.")
</pre>
 
;Commentaires
 
{{todo|puces qui semblent avoir été oubliées dans le livre}}
 
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>
{{todo|num à droite}}
*serveur OK*
 
client OK
<pre>
*canons,Thread-3;104;228;1;dark red,Thread-2;454;166;-1;dark blue,*
1.*serveur OK*
OK
2.client OK
3.*canonsnouveau_canon,Thread-3;104;228;1;dark red4,481,245,Thread-2;454;166;-1;,dark bluegreen,le_vôtre*
orienter,25,
4.OK
feu
5.*nouveau_canon,Thread-4,481,245,-1,dark green,le_vôtre*
*mouvement_de,Thread-4,549,280,*
6.orienter,25,
7.feu
8.*mouvement_de,Thread-4,549504,280278,*
*scores,Thread-4;1,Thread-3;-1,Thread-2;0,*
9.feu
10.*mouvement_deangle,Thread-42,504,27823,*
11.*scoresangle,Thread-4;12,Thread-3;-1,Thread-2;020,*
12.*angletir_de,Thread-2,23,*
13.*anglemouvement_de,Thread-2,20407,191,*
14.*tir_dedépart_de,Thread-2,*
15.*mouvement_denouveau_canon,Thread-25,407502,191276,-1,dark green*
</source>
16.*départ_de,Thread-2*
17.*nouveau_canon,Thread-5,502,276,-1,dark green*
</pre>
 
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>
{{todo|num à droite}}
#######################################################
# 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
<pre>
largeur, hauteur = 700, 400 # dimensions de l'espace de jeu
1.#######################################################
 
2.# Jeu des bombardes - partie serveur #
from Tkinter import *
3.# (C) Gérard Swinnen, Liège (Belgique)- Juillet 2004 #
import socket, sys, threading, time
4.# Licence : GPL #
import canon03
5.# Avant d'exécuter ce script, vérifiez que l'adresse #
from canon04 import Canon, AppBombardes
6.# IP ci-dessous soit bien celle de la machine hôte. #
 
7.# Vous pouvez choisir un numéro de port différent, ou #
class Pupitre(canon03.Pupitre):
8.# changer les dimensions de l'espace de jeu. #
"""Pupitre de pointage amélioré"""
9.# Dans tous les cas, vérifiez que les mêmes choix ont #
def __init__(self, boss, canon):
10.# été effectués pour chacun des scripts clients. #
canon03.Pupitre.__init__(self, boss, canon)
11.#######################################################
 
12.
def tirer(self):
13.host, port = '192.168.0.235', 35000
"déclencher le tir du canon associé"
14.largeur, hauteur = 700, 400 # dimensions de l'espace de jeu
self.appli.tir_canon(self.canon.id)
15.
16.from Tkinter import *
def orienter(self, angle):
17.import socket, sys, threading, time
"ajuster la hausse du canon associé"
18.import canon03
self.appli.orienter_canon(self.canon.id, angle)
19.from canon04 import Canon, AppBombardes
 
20.
def valeur_score(self, sc =None):
21.class Pupitre(canon03.Pupitre):
"imposer un nouveau score <sc>, ou lire le score existant"
22. """Pupitre de pointage amélioré"""
if sc == None:
23. def __init__(self, boss, canon):
return self.score
24. canon03.Pupitre.__init__(self, boss, canon)
else:
25.
26. def tirer( self):.score =sc
self.points.config(text = ' %s ' % self.score)
27. "déclencher le tir du canon associé"
 
28. self.appli.tir_canon(self.canon.id)
def inactiver(self):
29.
"désactiver le bouton de tir et le système de réglage d'angle"
30. def orienter(self, angle):
self.bTir.config(state =DISABLED)
31. "ajuster la hausse du canon associé"
32. self.appliregl.orienter_canonconfig(self.canon.id,state angle=DISABLED)
 
33.
34. def valeur_scoreactiver(self, sc =None):
35. "imposeractiver unle nouveaubouton scorede <sc>,tir ou lireet le scoresystème de réglage existantd'angle"
36. if scself.bTir.config(state == None:NORMAL)
37. return self.scoreregl.config(state =NORMAL)
38. else:
39. def reglage(self.score, =scangle):
"changer la position du curseur de réglage"
40. self.points.config(text = ' %s ' % self.score)
self.regl.config(state =NORMAL)
41.
42. def inactiver( self.regl.set(angle):
self.regl.config(state =DISABLED)
43. "désactiver le bouton de tir et le système de réglage d'angle"
</source>
44. self.bTir.config(state =DISABLED)
45. self.regl.config(state =DISABLED)
46.
47. def activer(self):
48. "activer le bouton de tir et le système de réglage d'angle"
49. self.bTir.config(state =NORMAL)
50. self.regl.config(state =NORMAL)
51.
52. def reglage(self, angle):
53. "changer la position du curseur de réglage"
54. self.regl.config(state =NORMAL)
55. self.regl.set(angle)
56. self.regl.config(state =DISABLED)
57.
</pre>
 
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 (voir aussi page {{todo}}).</ref> ses méthodes <code>tirer()</code> et <code>orienter()</code> :
 
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|
[[Catégorie:Apprendre à programmer avec Python (livre)|Communication]]
# ajouter des images
}}