Académique Documents
Professionnel Documents
Culture Documents
Durée : 2h
Objectif :
Prérequis
Python de base notamment les chaînes de caractères, tuples, les
dictionnaires et les fonctions
Fig 2
Le serveur :
1- attend une connexion de la part du client ;
2- accepte la connexion quand le client se connecte ;
3- échange des informations avec le client ;
4-ferme la connexion.
Le client :
Établissement de la connexion
Le numéro de port associé à l’application qui doit gérer les requêtes sur
le serveur.
Pour que le serveur réponde au client, il lui faut également deux
informations :
import socket
Application Serveur
socket_ecoute = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
fig 3
socket_ecoute.listen()
connexion_client, adresse_client =
socket_ecoute.accept()
connexion_client.close()
socket_ecoute.close()
Application Client
connexion_serveur = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
connexion_serveur.connect((nom_hote_serveur,
port_d_ecoute_serveur))
connexion_serveur.close()
NB
En Python, les chaînes d’octets (bytes) sont représentées comme des chaînes de
caractères (limité au codage ascii), préfixées par un b
: b'Bonjour !'
Conversion str
→ bytes
: bytes("donnée", "utf-8")
→ b'donn\xc3\xa9e'
Conversion bytes
→ str
: b'donn\xc3\xa9e'.decode()
→ "donnée"
Exemples
Activite
exoserer.py
import socket
socket_ecoute = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_ecoute.bind(('127.0.0.1', 63000))
socket_ecoute.listen(5)
while True:
client, infoclient = socket_ecoute.accept()
adresseIP = infoclient[0]
port = str(infoclient[1])
print("Message reçu du client " + adresseIP + ":" + port )
donnees=client.recv(255).decode("utf-8")
print(donnees)
donnees1=donnees + '\nPrésent !'
client.send(donnees1.encode("utf-8"))
client.close()
socket_ecoute.close()
1- crée un socket
2-demande une connexion au serveur d’adresse (‘localhost’, 63000)
‘localhost’ est le nom d’hôte qui désigne l’interface de bouclage (loopback
interface), c’est à dire la machine locale. Cela correspond à l’adresse IP ‘127.0.0.1’)
Le client et le serveur sont sur la même machine ici !
3- envoie un message « Serveur es-tu là ? »
4- affiche la réponse du serveur
ferme la connexion
exoclient.py
import socket
connexion_serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
adresseIP = "127.0.0.1" # Ici, le poste local
port = 63000
connexion_serveur.connect((adresseIP, port))
print("Connecté au serveur")
print("Tapez un message a envoyer au serveur. ")
message = input("> ")
connexion_serveur.send(message.encode("utf-8"))
reponse = connexion_serveur.recv(255)
print(reponse.decode("utf-8"))
print("Connexion fermée")
connexion_serveur.close()
Améliorations
Déclaration with … as … :
Le fait d’être obligé de fermer une connexion après l’avoir ouverte pose certains problèmes : si le
programme se termine anormalement, par exemple, la connexion peut ne pas se fermer
correctement et cela pourrait poser des problèmes.
Pour bien faire, il faudrait intercepter les éventuelles exceptions (erreurs) :
Méthode .sendall()
Supposons qu’une des applications envoie le message suivant :
client.send(b'Bonjour !')
m = serveur.recv(1024)
Il est possible que pour une raison ou une autre le message reçu soit incomplet (par exemple :
b'Bon'
)
Coté réception, il n’est pas possible de savoir si un message est complet ou non.
D’autre part, pour le même message envoyé, si l’application qui reçoit limite la taille maximale des
données reçues :
m = serveur.recv(5)
NB
Les applications sont chargées de vérifier que toutes les données ont été envoyées ; si
une partie seulement des données a été transmise, l’application doit tenter de
renvoyer les données restantes.
Par conséquent il faut gérer les échanges de cette manière (par exemple) :
Envoi
n = len(donnees)
while n > 0:
n = conn.send(donnees)
donnees = donnees[n:]
reception
donnees = b''
while True:
d = conn.recv(buff_size)
if not d or len(d) < buff_size:
break
donnees += d
NB
Coté « réception », il n’y a toujours pas d’information assurant que toutes les données
sont arrivées. Pour transférer de grandes quantités de données (de taille supérieure au
buffer), il faut utiliser un protocole qui annonce à l’avance la taille des données à
transférer.
Activités
Voici un des résultats possibles lors de l'exécution du script ci-dessus. On observe que
l'affichage du thread A et B sont confondus :
Thread A : 0
Thread A : 1
Thread B : 0
Thread B : 1
Thread A : 2
Thread B : 2
groupe
Doit être à None, réservé à un usage futur.
cible
Le nom de la fonction à exécuter.
nom
Le nom du thread (facultatif, peut être défini à None).
arguments
Un tuple donnant les arguments de la fonction cible.
dictionnaireArguments
Un dictionnaire donnant les arguments de la fonction cible. Avec l'exemple ci-
dessus, on utiliserait {nomThread="Thread A"}.
#!/usr/bin/env python3
import socket
import threading
threadsClients = []
def instanceServeur (client, infosClient):
adresseIP = infosClient[0]
port = str(infosClient[1])
print("Instance de serveur prêt pour " + adresseIP + ":" + port)
message = ""
while message.upper() != "FIN":
message = client.recv(255).decode("utf-8")
print("Message reçu du client " + adresseIP + ":" + port + " : " +
message)
client.send("Message reçu".encode("utf-8"))
print("Connexion fermée avec " + adresseIP + ":" + port)
client.close()
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur.bind(('', 50000)) # Écoute sur le port 50000
serveur.listen(5)
while True:
client, infosClient = serveur.accept()
threadsClients.append(threading.Thread(None, instanceServeur, None, (client,
infosClient), {}))
threadsClients[-1].start()
serveur.close()
Et voici le nouveau script client :
#!/usr/bin/env python3
import socket
adresseIP = "127.0.0.1" # Ici, le poste local
port = 50000 # Se connecter sur le port 50000
client = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
client.connect((adresseIP, port))
print("Connecté au serveur")
print("Tapez FIN pour terminer la conversation. ")
message = ""
while message.upper() != "FIN":
message = input("> ")
client.send(message.encode("utf-8"))
reponse = client.recv(255)
print(reponse.decode("utf-8"))
print("Connexion fermée")
client.close()
À chaque nouvelle connexion d'un client, le serveur crée un thread dédié à ce client,
ce qui permet au programme principal d'attendre la connexion d'un nouveau client.
Chaque client peut alors avoir une conversation avec le serveur sans bloquer les
autres conversations.
import http.server
portEcoute = 80 # Port Web par défaut
adresseServeur = ("", portEcoute)
serveur = http.server.HTTPServer
handler = http.server.CGIHTTPRequestHandler
handler.cgi_directories = ["/tmp"] # On sert le
dossier /tmp
print("Serveur actif sur le port ", portEcoute)
httpd = serveur(adresseServeur, handler)
httpd.serve_forever()
Voici le code source permettant de récupérer la donnée et en récupérer les parties utiles :
import urllib.request
import json
requete = urllib.request.Request('http://api.open-notify.org/iss-now.json') # La
requête de l'API
reponse = urllib.request.urlopen(requete) # Récupérer le fichier JSON
donneesBrut = reponse.read().decode("utf-8") # Décoder le texte reçu
donneesJSON = json.loads(donneesBrut) # Décoder le fichier JSON
position = donneesJSON["iss_position"]
print("La station spatiale internationale est située à une longitude " +
position["longitude"] + " et à une latitude " + position["latitude"] + ". ")
{
"iss_position": {
"longitude": "-100.8325",
"latitude": "-12.0631"
},
"timestamp": 1493971107,
"message": "success"
}
De plus, si l’on ne prévoit pas que d’autres clients se connectent sur le serveur, le
socket d’écoute n’est plus nécessaire.
On peut donc envisager un unique algorithme de connexion lancé sur deux
machines :
• Le programme A, qui démarre le premier, tente de se connecter à l’autre : la
connexion échoue car l’autre programme n’a pas démarré ou bien n’a pas de
socket d’écoute
• Le programme A ouvre un socket d’écoute …
• Le programme B, qui démarre ensuite, tente à son tour de se connecter
au programme A : la connexion réussit car le programme A est à l’écoute ;
• Le programme A obtient un socket pour communiquer avec le
programme B.
• Les échanges peuvent commencer …
fig 5
try:
tentative de connexion
except socket.timeout: # interception de l'exception socket.timout
création d'un socket d'écoute
...
Rappels sur quelques methodes dexploitation des resultats de requete sur une
base de donnees1
cursor.fetchall() récupère toutes les lignes du résultat d'une requête. Il renvoie toutes les
lignes sous forme de liste de tuples. Une liste vide est retournée s'il n'y a aucun enregistrement à
récupérer.
Exemples
import sqlite3
def getSingleRows():
try:
connection = sqlite3.connect('banque.db')
cursor = connection.cursor()
print("Connected to database")
cursor.close()
getSingleRows()
Reprenez des exos CRUD mais cette fois-ci en utilisant des fonctions
fonction insertion
fonction update
Fonction delete,py
def delete(id)
Exercices
Vous êtes nouvellement embauché dans une banque pour mettre au point le système
de communication entre les distributeurs automatiques et le système central de
gestion des comptes. Votre travail est de développer les applications sur ces deux
systèmes pour permettre aux distributeurs de communiquer avec le système central
pour effectuer les transactions.
On souhaite créer une base de données SQLite3 stockant les soldes des comptes, les
informations les concernant, ainsi que les transactions effectuées. On utilisera un
serveur de socket pour effectuer les communications entre les deux systèmes. Voici
les différents messages pris en charge avec (C→S) un message du client vers le
serveur et (S→C) un message du serveur vers le client :
• TESTPIN numeroCompte codePIN (C→S) : Vérifier si le code PIN saisi
est correct.
• TESTPIN OK (S→C) : La vérification du code PIN est validée.
• TESTPIN NOK (S→C) : La vérification du code PIN n'est pas valide.
• RETRAIT numeroCompte montant (C→S) : Demande un retrait du montant défini.
• RETRAIT OK (S→C) : Le retrait est validé.
• RETRAIT NOK (S→C) : Le retrait est refusé.
• DEPOT numeroCompte montant (C→S) : Demande un dépôt du montant défini.
• DEPOT OK (S→C) : Le dépôt est validé.
• TRANSFERT numeroCompteSource numeroCompteDestination montant
(C→S) : Demande un transfert du montant défini entre deux comptes.
• TRANSFERT OK (S→C) : Le transfert est validé.
• TRANSFERT NOK (S→C) : Le transfert est refusé.
• SOLDE numeroCompte (C→S) : Demande le solde du compte
• SOLDE solde (S→C) : Renvoie le solde du compte demandé
• HISTORIQUE numeroCompte (C→S) : Demande les 10 dernières opérations du compte
• HISTORIQUE operationsEnCSV (S→C) : Renvoie les 10 dernières opérations du
compte au format CSV (date, libellé, montant déposé ou retiré).
• ERROPERATION (S→C) : Signale que l'opération demandée n'est pas valide.
Toute opération doit être précédée d'une vérification du code PIN. Les exercices
suivants permettront d'effectuer cela en scindant le travail en différentes sous-tâches.
Chaque tâche fera l'objet d'un nouveau programme.
1. Écrivez un programme permettant de créer sur le serveur une base de données
SQLite3 nommée banque.db et de créer la structure de table adaptée au
stockage des données. Importez le contenu des fichiers CSV dans cette base.
2+-Écrivez le programme du serveur central de la banque écoutant sur le port
50000.
3- Écrivez le programme des distributeurs automatiques de la banque.
Guide du projet
def connexionBaseDeDonnees():
def testpin(nocompte, pinuser):
def solde(nocompte):
def retrait(nocompte, montant)
def transfert(nocompteSource, nocompteDestination, montant)
def depot(nocompte, montant)
def historique(nocompte):
def instanceServeur (client, infosClient):
Annexe
Les sockets
introduction
Une socket est connue comme un type de logiciel qui agit comme un point
d'extrémité qui fonctionne en établissant une liaison de communication
réseau bidirectionnelle entre l'extrémité du serveur et le programme de
réception du client.On l'appelle aussi souvent un point d'aboutissement dans
un canal de communication bidirectionnel. Ces sockets sont réalisés et
mobilisés en même temps qu'un ensemble de requêtes de programmation
identifiées comme appels de fonction, qui est techniquement appelé interface
de programmation d'application (API). Une socket est capable de simplifier le
fonctionnement d'un programme car les programmeurs n'ont plus qu'à se
soucier de manipuler les fonctions de la socket, ce qui leur permet de
compter sur le système d'exploitation pour transporter correctement les
messages sur le réseau
Socket UDP
Fonctionnement de l’UDP
Définition
L’UDP (User Datagram Protocol) est un protocole sans connexion de la suite
été défini en 1980 dans la RFC (Request for Comments) 768. En tant
l’UDP sont donc les requêtes DNS, les connexions VPN et le streaming audio
et vidéo.
Aperçu des propriétés de l’UDP
mentionnées.
à l’identification d’un paquet UDP en tant que tel. La structure est divisée
correctement.
La fin de l’entête UDP est constituée par la somme de contrôle qui sert à identifier les
erreurs lors de la transmission. De cette façon, les manipulations des données
transmises peuvent être identifiées, mais les paquets correspondants sont rejetés sans
nouvelle demande. Pour calculer la somme, on utilise des parties
de l’entête UDP,
des données utiles
et du pseudo entête (contient les informations de l’entête IP)
La somme de contrôle est facultative en IPv4, mais elle est toutefois utilisée par
défaut par la plupart des applications. En l’absence de somme de contrôle, ce champ
prend également la valeur « 0 ». Si l’UDP est utilisé en association avec IPv6, la
somme de contrôle est obligatoire.
plutôt été conçu pour les applications ne nécessitant pas (encore) de service
udpserver,py
import socket
localIP = "127.0.0.1"
localPort = 20001
bufferSize = 1024
print(clientMsg)
print(clientIP)
clientudp,py
import socket
bytesToSend = str.encode(msgFromClient)
UDPClientSocket = socket.socket(family=socket.AF_INET,
type=socket.SOCK_DGRAM)
UDPClientSocket.sendto(bytesToSend, serverAddressPort)
msgFromServer = UDPClientSocket.recvfrom(bufferSize)
print(msg)
Sockets TCP
où, dans presque tous les cas, le protocole TCP est basé sur le protocole
Internet (IP) et que cette connexion forme la base de la majorité des réseaux
simultanément des données, comme lors d’un appel téléphonique. Les unités
ce faire, chaque connexion doit toujours être identifiée par deux points (client
aucune importance. L’important est que le logiciel TCP dispose, pour chacun
Pour établir une connexion TCP valide, les deux points impliqués doivent
Pour la partie ayant envoyé le dernier segment ACK (dans notre cas : le
client), la connexion n’est toutefois pas immédiatement désactivée. En effet,
aucune garantie ne lui a été donnée que le dernier paquet envoyé a bien été
reçu. Le partenaire de communication passe alors en mode d’attente (« état
Time-Wait ») jusqu’à écoulement de la période de transmission maximale du
segment ACK et d’un éventuel nouveau segment FIN (soit deux minutes
selon la RFC 793).
Comment est formé l’en-tête TCP ?
Conformément au protocole, les données essentielles à l’établissement d’une
Control Protocol se trouvent dans l’en-tête d’un paquet TCP. Ces données
octets (160 bits) ; Elles sont suivies d’un maximum de 40 octets (320 bits)
fig
premier octet des données utiles jointes ou est envoyé lors de la procédure
confirmation attendu par l’expéditeur. Un Flag ACK doit être activé pour
blocs de 32 bits pour mettre en avant le point de départ des données utiles.
variable.
Réservé (6 bits) : RFC 793, réservé pour utilisation ultérieure, jusqu’ici non
désactivés ici :
Total de contrôle (16 bits) : le Transmission Control Protocol est doté d’une
et du pseudo-en-tête.
champ est uniquement valide et pertinent lorsque le Flag URG est activé.
à l’en-tête TCP et 20 autres à l’en-tête IP, laissant 1 460 octets pour les
avantages majeurs de TCP par rapport à des alternatives telles que l’UDP et