Programmation Python/Réseau
Communication entre programmes
[modifier | modifier le wikicode]Le développement extraordinaire de l'internet a amplement démontré que les ordinateurs peuvent être des outils de communication très efficaces. Dans ce chapitre, nous allons expérimenter la plus simple des techniques d'interconnexion de deux programmes, qui leur permette de s'échanger des informations par l'intermédiaire d'un réseau.
Pour ce qui va suivre, nous supposerons donc que vous collaborez avec un ou plusieurs de vos condisciples, et que vos postes de travail Python sont connectés à un réseau local dont les communications utilisent le protocole TCP/IP. Le système d'exploitation n'a pas d'importance : vous pouvez par exemple installer l'un des scripts Python décrits ci-après sur un poste de travail fonctionnant sous Linux, et le faire dialoguer avec un autre script mis en œuvre sur un poste de travail confié aux bons soins d'un système d'exploitation différent, tel que MacOS ou Windows.
Vous pouvez également expérimenter ce qui suit sur une seule et même machine, en mettant les différents scripts en œuvre dans des fenêtres indépendantes.
Les sockets
[modifier | modifier le wikicode]Le premier exercice qui va vous être proposé consistera à établir une communication entre deux machines seulement. L'une et l'autre pourront s'échanger des messages à tour de rôle, mais vous constaterez cependant que leurs configurations ne sont pas symétriques. Le script installé sur l'une de ces machines jouera en effet le rôle d'un logiciel serveur, alors que l'autre se comportera comme un logiciel client.
Le logiciel serveur fonctionne en continu, sur une machine dont l'identité est bien définie sur le réseau grâce à une adresse IP spécifique[1]. Il guette en permanence l'arrivée de requêtes expédiées par les clients potentiels en direction de cette adresse, par l'intermédiaire d'un port de communication bien déterminé. Pour ce faire, le script correspondant doit mettre en œuvre un objet logiciel associé à ce port, que l'on appelle un socket.
Au départ d'une autre machine, le logiciel client tente d'établir la connexion en émettant une requête appropriée. Cette requête est un message qui est confié au réseau, un peu comme on confie une lettre à la Poste. Le réseau pourrait en effet acheminer la requête vers n'importe quelle autre machine, mais une seule est visée : pour que la destination visée puisse être atteinte, la requête contient dans son en-tête l'indication de l'adresse IP et du port de communication destinataires.
Lorsque la connexion est établie avec le serveur, le client lui assigne lui-même l'un de ses propres ports de communication. À partir de ce moment, on peut considérer qu'un canal privilégié relie les deux machines, comme si on les avait connectées l'une à l'autre par l'intermédiaire d'un fil (les deux ports de communication respectifs jouant le rôle des deux extrémités de ce fil). L'échange d'informations proprement dit peut commencer.
Pour pouvoir utiliser les ports de communication réseau, les programmes font appel à un ensemble de procédures et de fonctions du système d'exploitation, par l'intermédiaire d'objets interfaces que l'on appelle des sockets. Ceux-ci peuvent mettre en œuvre deux techniques de communication différentes et complémentaires : celle des paquets (que l'on appelle aussi des datagrammes), très largement utilisée sur l'internet, et celle de la connexion continue, ou stream socket, qui est un peu plus simple.
Construction d'un serveur élémentaire
[modifier | modifier le wikicode]Pour nos premières expériences, nous allons utiliser la technique des stream sockets. 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 :
Python 2
[modifier | modifier le wikicode]# 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:
print "La liaison du socket à l'adresse choisie a échoué."
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)
# 4) Etablissement de la connexion :
connexion, adresse = mySocket.accept()
print "Client connecté, adresse IP %s, port %s" % (adresse[0], adresse[1])
# 5) Dialogue avec le client :
connexion.send("Vous êtes connecté au serveur Marcel. Envoyez vos messages.")
msgClient = connexion.recv(1024)
while 1:
print "C>", msgClient
if msgClient.upper() == "FIN" or msgClient =="":
break
msgServeur = raw_input("S> ")
connexion.send(msgServeur)
msgClient = connexion.recv(1024)
# 6) Fermeture de la connexion :
connexion.send("Au revoir !")
print "Connexion interrompue."
connexion.close()
ch = raw_input("<R>ecommencer <T>erminer ? ")
if ch.upper() =='T':
break
Version en python 3
[modifier | modifier le wikicode]#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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 = 'localhost'
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:
print("La liaison du socket à l'adresse choisie a échoué.")
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)
# 4) Etablissement de la connexion :
connexion, adresse = mySocket.accept()
print("Client connecté, adresse IP %s, port %s" % (adresse[0], adresse[1]))
# 5) Dialogue avec le client :
msgServeur = "Vous êtes connecté au serveur Marcel. Envoyez vos messages."
connexion.send(msgServeur.encode("Utf8"))
msgClient = connexion.recv(1024).decode("Utf8")
while 1:
print("C>", msgClient)
if msgClient.upper() == "FIN" or msgClient =="":
break
msgServeur = input("S> ")
connexion.send(msgServeur.encode("Utf8"))
msgClient = connexion.recv(1024)
# 6) Fermeture de la connexion :
connexion.send("Au revoir !".encode("Utf8"))
print("Connexion interrompue.")
connexion.close()
ch = input("<R>ecommencer <T>erminer ? ")
if ch.upper() =='T':
break
Commentaires (Python 2)
[modifier | modifier le wikicode]- Ligne 4 : Le module socket contient toutes les fonctions et les classes nécessaires pour construire des programmes communiquants. Comme nous allons le voir dans les lignes suivantes, l'établissement de la communication comporte six étapes.
- Lignes 6 et 7 : Ces deux variables définissent l'identité du serveur, telle qu'on l'intégrera au socket.
HOST
doit contenir une chaîne de caractères indiquant l'adresse IP du serveur sous la forme décimale habituelle, ou encore le nom DNS de ce même serveur (mais à la condition qu'un mécanisme de résolution des noms ait été mis en place sur le réseau).PORT
doit contenir un entier, à savoir le numéro d'un port qui ne soit pas déjà utilisé pour un autre usage, et de préférence une valeur supérieure à 1024 (Cfr. votre cours sur les services réseau).
- Lignes 9 et 10 : Première étape du mécanisme d'interconnexion. On instancie un objet de la classe
socket()
, en précisant deux options qui indiquent le type d'adresses choisi (nous utiliserons des adresses de type « internet ») ainsi que la technologie de transmission (datagrammes ou connexion continue (stream) : nous avons décidé d'utiliser cette dernière).
- Lignes 12 à 17 : Seconde étape. On tente d'établir la liaison entre le socket et le port de communication. Si cette liaison ne peut être établie (port de communication occupé, par exemple, ou nom de machine incorrect), le programme se termine sur un message d'erreur.
Remarque : la méthodebind()
du socket attend un argument du type tuple, raison pour laquelle nous devons enfermer nos deux variables dans une double paire de parenthèses.
- Ligne 19 : Notre programme serveur étant destiné à fonctionner en permanence dans l'attente des requêtes de clients potentiels, nous le lançons dans une boucle sans fin.
- Lignes 20 à 22 : Troisième étape. Le socket étant relié à un port de communication, il peut à présent se préparer à recevoir les requêtes envoyées par les clients. C'est le rôle de la méthode
listen()
. L'argument qu'on lui transmet indique le nombre maximum de connexions à accepter en parallèle.
- Lignes 24 à 26 : Quatrième étape. Lorsqu'on fait appel à sa méthode
accept()
, le socket attend indéfiniment qu'une requête se présente. Le script est donc interrompu à cet endroit, un peu comme il le serait si nous faisions appel à une fonctioninput()
pour attendre une entrée clavier. Si une requête est réceptionnée, la méthodeaccept()
renvoie un tuple de deux éléments : le premier est la référence d'un nouvel objet de la classesocket()
[2], qui sera la véritable interface de communication entre le client et le serveur, et le second un autre tuple contenant les coordonnées de ce client (son adresse IP et le n° de port qu'il utilise lui-même).
- Lignes 28 à 30 : Cinquième étape. La communication proprement dite est établie. Les méthodes
send()
etrecv()
du socket servent évidemment à l'émission et à la réception des messages, qui doivent être de simples chaînes de caractères.
Remarques : la méthodesend()
renvoie le nombre d'octets expédiés. L'appel de la méthoderecv()
doit comporter un argument entier indiquant le nombre maximum d'octets à réceptionner en une fois (Les octets surnuméraires sont mis en attente dans un tampon. Ils sont transmis lorsque la même méthode recv() est appelée à nouveau).
- Lignes 31 à 37 : Cette nouvelle boucle sans fin maintient le dialogue jusqu'à ce que le client décide d'envoyer le mot « fin » ou une simple chaîne vide. Les écrans des deux machines afficheront chacune l'évolution de ce dialogue.
- Lignes 39 à 42 : Sixième étape. Fermeture de la connexion.
Construction d'un client rudimentaire
[modifier | modifier le wikicode]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é.
En python 2
[modifier | modifier le wikicode]# 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))
except socket.error:
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()
En Python 3
[modifier | modifier le wikicode]#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Définition d'un client réseau rudimentaire
# Ce client dialogue avec un serveur ad hoc
import socket, sys
HOST = '192.168.14.152'
HOST = "localhost"
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))
except socket.error:
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 = input("C> ")
mySocket.send(msgClient.encode("Utf8"))
msgServeur = mySocket.recv(1024).decode("Utf8")
# 4) Fermeture de la connexion :
print("Connexion interrompue.")
mySocket.close()
Commentaires (Python 2)
[modifier | modifier le wikicode]- Le début du script est similaire à celui du serveur. L'adresse IP et le port de communication doivent être ceux du serveur.
- Lignes 12 à 18 : On ne crée cette fois qu'un seul objet socket, dont on utilise la méthode
connect()
pour envoyer la requête de connexion.
- Lignes 20 à 33 : Une fois la connexion établie, on peut dialoguer avec le serveur en utilisant les méthodes
send()
etrecv()
déjà décrites plus haut pour celui-ci.
Récupérer une page Web en python
[modifier | modifier le wikicode]Python intègre le module httplib[3] qui permet d'émettre et de recevoir des requêtes HTTP.
Afficher une page Web
[modifier | modifier le wikicode]Le code suivant (src) permet de récupérer, à l'aide d'une requête HTTP, une page Web et affiche son code source à l'écran.
# On utilise le module httplib import httplib # Connexion au proxy # (si vous n'être pas derrière un proxy, alors mettre directement 'fr.wikibooks.org') conn = httplib.HTTP('proxy:3128') # Requête GET # (si vous n'être pas derrière un proxy, alors mettre directement # '/w/index.php?title=Programmation_Python_Le_r%C3%A9seau&action=edit' conn.putrequest('GET', 'http://fr.wikibooks.org/w/index.php?title=Programmation_Python_Le_r%C3%A9seau&action=edit') conn.putheader('Accept', 'text/html') conn.putheader('Accept', 'text/plain') # Décommenter les 2 lignes suivantes si votre proxy nécessite une authentification # auth = "Basic " + "username:password".encode('base64') # h1.putheader('Proxy-Authorization', auth) conn.endheaders() # Récupération de la réponse errcode, errmsg, headers = conn.getreply() # Affichage d'éventuelles erreurs print errcode print errmsg print headers # Affichage de la réponse ligne après ligne f=conn.getfile() for line in f: print line # fin de la connexion conn.close()
Références
[modifier | modifier le wikicode]- ↑ Une machine particulière peut également être désignée par un nom plus explicite, mais à la condition qu'un mécanisme ait été mis en place sur le réseau (DNS) pour traduire automatiquement ce nom en adresse IP. Veuillez consulter votre cours sur les systèmes d'exploitation et les réseaux pour en savoir davantage.
- ↑ . En bref, si nous voulons que notre serveur puisse prendre en charge simultanément les connexions de plusieurs clients, il nous faudra disposer d'un socket distinct pour chacun d'eux, indépendamment du premier que l'on laissera fonctionner en permanence pour réceptionner les requêtes qui continuent à arriver en provenance de nouveaux clients.
- ↑ http://docs.python.org/lib/module-httplib.html