Vous êtes sur la page 1sur 44

APPLICATIONS CLIENTS/SERVEURS

Chapitre 1 base de python


Durée: 10h
1,x : utilisation des modules python

1,y module de développement de clients web services


Cas1 client api d'information sur satellite de la station internationale
ISS(application des dictionnaires et format JSON)

Cas 2; client api pour gestion de conteneurs docker

Chapitre 2: utilisation de bases de données en python

Durée : 2h

2.1 CRUD avec python


2,2 fonctions CRUD en python

Utilisation de tuple pour passage des paramètres de fonctions aux requêtes


sql

Chapitre 3 : Sockets et multi thread


durée : 3h
3,1 socket tcp
Les méthodes de socket tcp
Exemple en python
3,2 socket udp
Les méthodes sockets udp
Exemple en python
3,3 exécutions en parallèle de thread
Principe de multithead
Exemple en python; serveur multi clients
Importance des dictionnaires

Chapitre 4 : Développement d'un projet complet d'application client


serveur
Durée: 9h

4,1 analyse de problèmes


4,2 algo des sous programmes serveurs
4,3 implémentation des sous programmes en python et coordination

Objectif :

Comprendre et mettre en œuvre les concepts de :


1- socket
2- thread
3- multi-thread
4- client web service
5- programmes serveurs multi-utilisateurs
6- programmes clients pour dialoguer avec un serveur

Prérequis
Python de base notamment les chaînes de caractères, tuples, les
dictionnaires et les fonctions

Savoir faire des requêtes sql de base

Dans l’architecture Client-Serveur, on trouve en général un serveur et


plusieurs clients. Le serveur est une machine qui traite les requêtes du
client et lui envoie éventuellement une réponse.
Il y a donc deux types d’application installés sur les machines :
• l’application « serveur » : écoute en attendant des connexions des clients ;
• les applications « client » : se connectent au serveur.
Fig1

Les étapes d’une communication Client-Serveur

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 :

1-se connecte au serveur ;


2-échange des informations avec le serveur ;
3- ferme la connexion.

Établissement de la connexion

Pour que le client se connecte au serveur, il lui faut deux informations :

L’adresse IP du serveur, ou son nom d’hôte (host name), qui identifie


une machine sur Internet ou sur un réseau local.

Les noms d’hôtes permettent de représenter des adresses IP de façon


plus claire
par exemple ‘google.fr’ est plus facile à retenir que l’adresse IP
correspondante 74.125.224.84

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 :

Le nom d’hôte (host name), du client ;

Le numéro de port associé à l’application qui doit recevoir les réponses


sur le client.

Serveur simple en Python


Le module socket de Python permet de gérer les connexions par socket.
Import de la bibliothèque socket

import socket

Les méthodes de l'objet socket:

accept() : accepte une connexion, retourne un nouveau


socket et une adresse client
bind(addr) : associe le socket à une adresse locale
close() : ferme le socket
connect(addr) : connecte le socket à une adresse distante
connect_ex(addr) : connect, retourne un code erreur au lieu
d'une exception
dup() : retourne un nouveau objet socket identique à
celui en cours
fileno() : retourne une description du fichier
getpeername() : retourne l'adresse distante
getsockname() : retourne l'adresse locale
getsockopt(level, optname[, buflen]) : retourne les options du socket
gettimeout() : retourne le timeout ou none
listen(n) : commence à écouter les connexions entrantes
makefile([mode, [bufsize]]) : retourne un fichier objet pour le
socket
recv(buflen[, flags]) : recoit des données
recv_into(buffer[, nbytes[, flags]]) : recoit des données (dans un buffer)
Un socket est un objet qui permet d’ouvrir une connexion avec une
machine, locale ou distante, et d’échanger avec elle.

Il faut maintenant créer les deux applications :

Application Serveur

socket_ecoute = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)

fig 3

Le constructeur de l’objet socket attend deux paramètres :

• la famille d’adresses : socket.AF_INET


= adresses Internet de type IPv4 ;
• le type du socket : socket.SOCK_STREAM
= protocole TCP.

Liaison du socket d’écoute à un port :


Le socket doit à présent être lié à un port de la machine, à l’aide de la
méthode
.bind()
, qui attend un tuple au format
(nom_d_hote, port)
.
socket_ecoute.bind(('', port_d_ecoute))

Mettre le socket d’écoute à l’état d’écoute (LISTEN) :

socket_ecoute.listen()

A partir de là, le serveur écoute, sur le port port_d_ecoute


, les demandes de connexion de la part des clients.

Accepter une connexion venant du client :

Lorsqu’un client se connecte, le serveur est sensé accepter la connexion,


avec la méthode .accept()
:

connexion_client, adresse_client =
socket_ecoute.accept()

L’exécution du programme est alors bloquée tant


qu’un client ne fait pas de demande de connexion.

Dès que cela se produit, .accept()


renvoie :
• connexion_client
: un nouvel objet socket = le socket de
communication, celui qui permet l’échange de
données avec client ;
• adresse_client
: l’adresse, au format (nom_d_hote, port)
du client.

C’est par ce nouveau socket que vont passer les


échanges de données.

Fermeture des connexions :


Lorsque les échanges sont terminés, il faut fermer les connexions :

connexion_client.close()
socket_ecoute.close()

Application Client

Coté client, il faut également créer le socket qui


va donner l’accès au serveur :

connexion_serveur = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)

Ensuite, il suffit de demander une connexion au serveur (dont on connait


le nom d’hôte et le port d’écoute) à l’aide de la méthode
.connect():

connexion_serveur.connect((nom_hote_serveur,
port_d_ecoute_serveur))

Remarque : il ne faut pas confondre :


• la méthode
.bind()
, coté serveur, qui établi le lien, localement entre le socket d’écoute et le port
de la machine ;
• la méthode
.connect()
, coté client, envoie une demande de connexion au serveur.
Fermeture de la connexion :
Lorsque les échanges sont terminés, il faut fermer la connexion avec le serveur :

connexion_serveur.close()

Envoi et réception des données


Une fois la communication entre client et serveur établie, chacun peut envoyer et recevoir des
données.

En Python, les deux méthodes pour faire cela sont :


• .send()
: pour envoyer (send) des données :
.send()
attend un argument de type
bytes
(chaîne d’octets) (à ne pas confondre avec une chaîne de caractères !!)
et renvoie le nombre d’octets effectivement envoyés.
• .recv()
: pour recevoir (receive) des données :
.recv()
attend comme argument un entier donnant la taille maximale de la chaîne
d’octets à recevoir
et renvoie la chaîne d’octets (type
bytes
) effectivement reçue.
Attention :
.recv()
est une méthode bloquante : l’exécution de programme s’arrête jusqu’à ce que des
données soient reçues.
• si les données envoyées sont de taille inférieure à la taille maximale spécifiée,
les données reçues feront exactement la taille des donnés envoyées ;
• si les données envoyées sont de taille supérieure à la taille maximale spécifiée,
les données reçues auront la taille maximale spécifiée et il faudra recommencer
un
.recv()
pour « récupérer » le reste des données envoyées;

NB

Les données échangées sont de type bytes


!!

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

connexion_serveur.send(b'Bonjour serveur !")


message = connexion_client.recv(1024)

Activite

Implémenter en Python un programme « Serveur » qui :

1-créé un socket d’écoute et l’associe au port 63000 ,


le met à l’état d’écoute et attend qu’un client s’y connecte
2- affiche l’adresse du client qui vient de se connecter
attend la réception d’un message de la part du client
le renvoie aussitôt au client en rajoutant à la fin : « \nPrésent ! »
3-ferme les connexions

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

Implémenter en Python un programme « Client » qui :

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) :

connexion_serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


try:
connexion_serveur.connect((nom_hote_serveur, port_d_ecoute_serveur))
connexion_serveur.send(donnees_envoyees)
donnees_recues = connexion_serveur.recv(1024)
print(donnees_recues)
finally:
connexion_serveur.close()

Mais il existe une structure plus simple à utiliser, la déclaration with … as … : :

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as connexion_serveur:


connexion_serveur.connect((nom_hote_serveur, port_d_ecoute_serveur))
connexion_serveur.send(donnees_envoyees)
donnees_recues = connexion_serveur.recv(1024)
print(donnees_recues)

Méthode .sendall()
Supposons qu’une des applications envoie le message suivant :
client.send(b'Bonjour !')

Et que l’autre application essaye de recevoir ce message à l’aide de l’instruction suivante :

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)

Alors le message reçu sera naturellement tronqué :


b'Bonjo'
. L’application qui reçoit doit s’assurer qu’il ne reste aucune données à recevoir.

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

Mais il existe une méthode d’envoi un peu plus simple : la méthode


.sendall()
envoie l’intégralité des données. Elle appelle
.send()
autant de fois que nécessaire pour cela.

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

Par groupe de 2 personnes, implémenter en Python deux programmes


(« Client » et « Serveur ») qui communiquent, entre deux machines différentes,
selon le protocole suivant :
Une fois la connexion établie, entre le client et le serveur, les deux utilisateurs
peuvent échanger des messages :
• coté client, l’utilisateur est invité à saisir un message, puis ce message est
envoyé au serveur
• coté serveur, l’utilisateur reçoit le message du client, puis est invité à son
tour à répondre
• la discussion continue ainsi jusqu’à ce qu’un des participants envoie le
message « fini »
• la connexion coté client est alors fermée et le serveur est remis dans l’état
d’attente

L'exécution de fonctions en parallèle : le multi thread


Le multi thread permet l'exécution de plusieurs opérations simultanément sur les
mêmes ressources matérielles (ici, l'ordinateur). Les différents threads sont traités à
tour de rôle par l'ordinateur pendant un temps très court ce qui donne cette impression
d'exécution parallèle.
Nous abordons cette technique pour pouvoir élaborer un serveur pouvant gérer
plusieurs connexions client en même temps. Tout d'abord, nous allons nous
familiariser avec le module threading qui met en œuvre le multi thread pour les
fonctions et les objets Python. Voici un exemple simple montrant le fonctionnement
de cette technique :
#!/usr/bin/env python3
import threading
def compteur(nomThread):
for i in range(3):
print(nomThread + " : " + str(i))
threadA = threading.Thread(None, compteur, None, ("Thread A",), {})
threadB = threading.Thread(None, compteur, None, ("Thread B",), {})
threadA.start()
threadB.start()

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

Nous allons détailler la fonction créant le thread : threading.Thread(groupe, cible,


nom, arguments, dictionnaireArguments) dont voici les arguments :

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"}.

Créer un serveur socket acceptant plusieurs clients


Voici donc la combinaison du serveur de socket vu précédemment et la technique du
multithreading pour obtenir un serveur plus complexe créant un nouveau thread à
chaque client connecté. Nous avons apporté une petite modification au client car
désormais, le client demande à l'utilisateur de saisir un message qui sera affiché sur le
serveur. Ce dernier répondra à tous les messages du client jusqu'à ce que l'utilisateur
saisisse le mot FIN sur le client ce qui termine la connexion. Voici le script serveur :

#!/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.

Créer un serveur Web


Nous allons ici utiliser la bibliothèque http.server pour créer rapidement un
serveur Web capable de servir des fichiers à un navigateur Web. Le script ci-dessous
montre comment créer cela en spécifiant le numéro de port sur lequel notre serveur
Web va écouter et quel dossier il va servir.

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

Ce serveur retournera par défaut le fichier index.html du dossier servi. Si ce


fichier n'existe pas, le serveur retournera la liste des fichiers du dossier.

Utiliser des services Web


De plus en plus de programmes utilisent et proposent aujourd'hui des interfaces de programmation
nommées . Cela permet de standardiser l'interaction entre les différentes applications et de découper
les différentes fonctionnalités d'une application en divers modules qui communiquent ensemble
avec ces interfaces.
Nous allons voir comment utiliser des services Web proposés pour enrichir nos applications Python
en utilisant le module urllib.request. Notre premier exemple sera d'afficher la position de la
Station Spatiale Internationale (ISS) qui nous est fournie par l'API http://api.open-notify.org/iss-
now.json qui nous renvoie un texte au format JSON sous cette forme :

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"] + ". ")

Quelques résultats renvoyés par le programme

{
"iss_position": {
"longitude": "-100.8325",
"latitude": "-12.0631"
},
"timestamp": 1493971107,
"message": "success"
}

Architecture Pair à Pair


Dans une architecture pair à pair (peer to peer ou P2P), les deux machines n’ont pas
de rôle différent : elles sont à la fois, ou alternativement, client et serveur.
Dans le programme précédent, les boucles d’échange des données sont très
semblables sur les deux machines :

• réception d’une requête


• traitement
• envoie d’une réponse

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

Écrire une fonction


connexion(host)
qui effectue la connexion avec une machine de nom d’hôte (ou adresse IP)
host
, et qui renvoie le socket de connexion avec cette machine.
Le programme qui démarre le premier ne pourra pas se connecter au
second : la méthode
.connect()
va donc provoquer une erreur identifiée par l’exception
socket.timeout
.

Pour éviter que le programme s’arrête il faut intercepter cette exception à


l’aide d’une structure
try: ... except: ...
:

try:
tentative de connexion
except socket.timeout: # interception de l'exception socket.timout
création d'un socket d'écoute
...

Utiliser cette fonction pour réaliser un programme de chat.

Fonctions avec paramètre utilisant une base de donnees1

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.

cursor.fetchmany(size) renvoie le nombre de lignes spécifié par


l'argument taille. Lorsqu'elle est appelée à plusieurs reprises,
cette méthode récupère le prochain ensemble de lignes d'un
résultat de requête et renvoie une liste de tuples. S'il n'y a
plus de lignes disponibles, elle renvoie une liste vide.

cursor.fetchone() renvoie un seul enregistrement ou None si aucune autre ligne n'est


disponible.

Exemples
import sqlite3

def getSingleRows():
try:
connection = sqlite3.connect('banque.db')
cursor = connection.cursor()
print("Connected to database")

sqlite_select_query = """SELECT * from clients"""


cursor.execute(sqlite_select_query)
print("Fetching single row")
record = cursor.fetchone()
print(record)

print("Fetching next row")


record = cursor.fetchone()
print(record)

cursor.close()

except sqlite3.Error as error:


print("Failed to read data from table", error)
finally:
if connection:
connection.close()
print("The Sqlite connection is closed")

getSingleRows()

NB : on peut fetchone() avec while pour parcourir lensemble des resultats

Reprenez des exos CRUD mais cette fois-ci en utilisant des fonctions

fonction insertion

def insert (id,prenom,nom,solde,code)

curseur.execute("INSERT INTO Operations (id, prenom, nom,solde, codet) VALUES


( ?, ?, ?, ?, ?)",(id, prenom, nom,solde,code))

fonction update

def update (id,solde)


curseur.execute("UPDATE clients SET solde = ? WHERE id = ?",(solde,compte))

Fonction delete,py

def delete(id)

curseur.execute("delete from client WHERE id = ?",(id,))


Projet

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.

Voici le schéma UML de la base de données de la banque :


rt

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

Écrire les fonctions suivantes

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

des protocoles Internet qui travaille au niveau de la couche transport et qui a

été défini en 1980 dans la RFC (Request for Comments) 768. En tant

qu’alternative au TCP fonctionnant de façon plus simple et quasiment sans

retard, l’UDP est utilisé pour la transmission rapide de paquets de

données dans des réseaux IP. Les domaines d’application typiques de

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

Afin de comprendre dans le détail comment la transmission des

paquets est effectuée avec ce protocole, il est judicieux de se pencher plus

précisément sur les caractéristiques du User Datagram Protocol déjà

mentionnées.

1. L’UDP est sans connexion : le transport des données via le protocole


UDP se démarque par le fait qu’il a lieu sans connexion existante entre
l’expéditeur et le destinataire. Les paquets concernés sont ensuite
envoyés à l’adresse IP privilégiée en indiquant le port de destination,
sans que l’ordinateur auquel cette adresse est attribuée n’ait à envoyer
une réponse. Si des paquets doivent être renvoyés à l’expéditeur,
l’entête UDP peut également contenir le port source.
2. Ports utilisés par l’UDP : à l’instar du TCP, l’UDP a recours à des
ports afin de remettre les paquets aux bons protocoles ultérieurs ou aux
applications souhaitées sur le système de destination. Comme le
modèle éprouvé, les ports sont définis à l’aide d’une numérotation, dont
les numéros compris entre 0 et 1023 sont attribués à des services fixes.
3. L’UDP permet une communication rapide, sans délai : ce protocole
de transport est adapté à une transmission rapide des données, car il
n’établit pas de connexion. Ceci résulte également du fait que la perte
de paquets individuels impacte uniquement la qualité de la
transmission. En cas de connexion TCP, il est en revanche procédé
automatiquement à une nouvelle demande des paquets perdus, ce qui
bloque l’intégralité du processus de transmission.
4. L’UDP n’offre aucune garantie quant à la sécurité et à l’authenticité
des données : le fait de renoncer à l’authentification mutuelle de
l’expéditeur et du destinataire permet au protocole UDP d’assurer une
vitesse de transmission exceptionnelle. Toutefois, le protocole ne peut
garantir l’intégrité et la sécurité des paquets de données. L’ordre dans
lequel les paquets ont été envoyés n’est pas non plus garanti. C’est
pourquoi les services faisant appel à l'UDP doivent mettre à disposition
des mesures de correction et de protection propres
Comment l’entête UDP est-il structuré ?
Comme c’est souvent le cas pour les protocoles, les paquets UDP sont

composés d’un entête, également appelée « header », et des données utiles

à proprement parler. L’entête UDP contient ici toutes les informations

nécessaires à la transmission des données avec un protocole de transport, et

à l’identification d’un paquet UDP en tant que tel. La structure est divisée

en deux blocs de 32 bits avec quatre champs de données distincts et se

présente comme suit :


Bits 0 à 15 Bits 16 à 31

zero Port source Port de destination

32 Longueur Somme de contrôle

Les 16 premiers bits de l’entête renvoient au port source par lequel le

paquet de données concerné est envoyé. Le destinataire a besoin de cette

information pour pouvoir répondre au paquet. Comme l’UDP est sans

connexion et ne prévoit en principe aucun échange entre l’expéditeur et le

destinataire, ce champ est facultatif. C’est pourquoi la valeur est

généralement définie sur « 0 » à cet endroit.

Dans le champ suivant, le port de destination, et donc le service contrôlé,

est renseigné. Contrairement au port source, cette information est obligatoire

puisque, dans le cas contraire, le datagramme ne pourrait pas être attribué

correctement.

La longueur du datagramme est définie dans le champ Longueur. Elle comprend la


longueur de l’entête (8 octets) et la longueur des données utiles (en théorie
maximum : 65 535 octets). En cas d’utilisation de IPv4, la limite effective pour les
données utiles est de 65 507 octets – après déduction des entêtes IP et UDP. Dans
IPv6, des paquets dépassant le maximum (« jumbograms ») sont par ailleurs
possibles. D'après la RFC 2675, la valeur du champ Longueur est dans ce cas définie
sur « 0 ».

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.

Par quelles applications l’UDP est-il utilisé ?


Du fait de sa structure minimaliste et de l’absence de mécanismes visant à

garantir une transmission complète et réussie, le User Datagram Protocol

n’est pas utilisé comme protocole de transport universel. Au départ, il a

plutôt été conçu pour les applications ne nécessitant pas (encore) de service

de transmission fiable. Par conséquent, le domaine d’application de l’UDP est

plutôt limité, mais souligne son énorme valeur comme le prouvent

les classes d’applicationssuivantes pour UDP :

• les applications « best effort delivery » (livraison des données best


effort) : le scénario d’utilisation classique de l’UDP porte sur les
applications basées sur une « livraison des données best effort ». Une
transmission peu fiable des informations suffit aux programmes utilisant
le User Datagram Protocol comme service « best effort », car ils
renouvellent régulièrement les informations quoi qu’il arrive. On peut
citer comme exemple les applications qui transmettent les valeurs
mesurées ou qui exécutent toujours les mêmes tâches.
• les applications légères : la faible surcharge de ce protocole de
transport offre une aide optimale aux applications construites de façon
extrêmement simple. En association avec une absence d’établissement
de connexion, ces programmes profitent d’une performance
particulièrement élevée dans le cadre du traitement et de la
transmission de paquets de données dans les réseaux.
• les applications disposant de mécanismes propres pour une
transmission fiable : l’UDP peut également s’avérer intéressant pour
les applications qui dépendent d’un échange d’informations fiable, mais
disposent de mécanismes propres pour répondre aux paquets.
L’avantage de tels services est qu’ils ne sont pas tenus à des modèles
fixes pour garantir l’intégrité et l’exactitude des paquets de données
envoyés. Ils peuvent décider de façon autonome comment et quand
réagir à des informations manquantes ou classées de façon arbitraire.
• les applications multicast : tandis que les protocoles de transport
fiables comme le TCP sont limités à l’utilisation d’une communication de
bout en bout, le protocole UDP supporte également les connexions IP
multicast. Si une application doit pouvoir envoyer simultanément des
paquets IP de façon rapide et efficace à de nombreux destinataires,
l’UDP constitue une base adaptée.
• applications en temps réel (real-time applications) : pour finir, l’UDP
est également adapté comme protocole de transport pour les services
travaillant avec des exigences temps réel – par exemple le streaming
audio ou vidéo. Ces derniers doivent pouvoir gérer de façon autonome
l’envoi, la réception et la diffusion de flux de données, ce qui est
parfaitement possible avec la transmission UDP sans connexion.

Communication client-serveur UDP


fig

Exemple d’un serveur UDP

udpserver,py

import socket

localIP = "127.0.0.1"
localPort = 20001
bufferSize = 1024

msgFromServer = "Bonjour Client UDP"


bytesToSend = str.encode(msgFromServer)

# Create a datagram socket


UDPServerSocket = socket.socket(family=socket.AF_INET,
type=socket.SOCK_DGRAM)
# Bind to address and ip
UDPServerSocket.bind((localIP, localPort))

print("UDP server up and listening")

# Listen for incoming datagrams


while(True):
bytesAddressPair = UDPServerSocket.recvfrom(bufferSize)
message = bytesAddressPair[0]
address = bytesAddressPair[1]
clientMsg = "Message from Client:{}".format(message)
clientIP = "Client IP Address:{}".format(address)

print(clientMsg)
print(clientIP)

# Sending a reply to client


UDPServerSocket.sendto(bytesToSend, address)

Exemple d’un client UDP

clientudp,py

import socket

msgFromClient = "Hello UDP Server"

bytesToSend = str.encode(msgFromClient)

serverAddressPort = ("127.0.0.1", 20001)


bufferSize = 1024

# Create a UDP socket at client side

UDPClientSocket = socket.socket(family=socket.AF_INET,
type=socket.SOCK_DGRAM)

# Send to server using created UDP socket

UDPClientSocket.sendto(bytesToSend, serverAddressPort)

msgFromServer = UDPClientSocket.recvfrom(bufferSize)

msg = "Message from Server {}".format(msgFromServer[0])

print(msg)

Sockets TCP

Transmission Control Protocol, soit TCP ou protocole TCP, est un accord


normalisé de transmission des données entre différents abonnés à un réseau
informatique. L’histoire de ce protocole remonte à l’année 1973, date à
laquelle Robert E. Kahn et Vinton G. Cerf, deux informaticiens, en ont publié
une première version dans le cadre de leur travail de recherche. Il fallut
attendre huit ans de plus avant la normalisation de TCP dans la RFC 793.
Depuis, bien que le noyau du protocole soit resté inchangé, il a subi un large
éventail d’améliorations et d’extensions. La version actuelle décrite dans
la RFC 7323 date de 2014.
L’état de développement actuel du protocole TCP permet d’établir un lien

de transmission de données bilatérale entre deux terminaux d’un même

réseau informatique. Toute éventuelle perte de données est automatiquement

détectée et solutionnée, c’est pourquoi ce protocole est considéré

comme fiable. Dans la suite de protocoles Internet, le TCP forme, avec

l’UDP et le SCTP, le groupe de protocoles de transport, intégrés à la couche

transport de l’architecture de réseau selon le modèle OSI. Dans la mesure

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

et services de réseaux publics et locaux, on parle également souvent de pile

de protocoles TCP/IP au sens de la suite de protocoles Internet.

Comment fonctionnent les connexions au protocole TCP ?


TCP permet la transmission bilatérale d’informations. Les systèmes

informatiques qui communiquent via le TCP peuvent ainsi envoyer et recevoir

simultanément des données, comme lors d’un appel téléphonique. Les unités

de transmission de base, sur lesquelles repose le protocole, sont des

segments (paquets) pouvant contenir des informations de commande en plus

de leurs données utiles et sont limités à un volume de 1 500 octets.

L’activation et la désactivation de connexions de bout-en-bout, ainsi que la

transmission de données même, sont prises en charge par le logiciel

TCP dans la pile de protocoles réseau du système d’exploitation utilisé.


Le logiciel TCP est commandé par différentes applications réseau, telles que

les navigateurs Web ou serveurs par le biais d’interfaces spécifiques. Pour

ce faire, chaque connexion doit toujours être identifiée par deux points (client

et serveur) clairement définis. Le rôle du client et le rôle du serveur n’ont

aucune importance. L’important est que le logiciel TCP dispose, pour chacun

de ces points, d’une paire ordonnée comprenant une adresse IP et un port

(également appelée « Couple » ou « Socket »).

La poignée de main en trois temps (« 3-Way Handshake ») : fonctionnement


détaillé d’une connexion TCP

Pour établir une connexion TCP valide, les deux points impliqués doivent

disposer d’une adresse IP univoque (IPv4 ou IPv6) et du port souhaité,

déclaré et validé pour la transmission de données. Tandis que la première

sert d’identifiant, le second permet d’affecter les connexions aux applications

client et serveur concrètes par le biais du système d’exploitation.

Le déroulement concret de la procédure de connexion via le protocole TCP


est le suivant :
1. Dans un premier temps, le client souhaitant établir la connexion au
serveur envoie un paquet ou segment SYN (de l’anglais synchronize =
« synchroniser ») portant un numéro séquentiel aléatoire individuel. Ce
numéro garantit la transmission intégrale et dans le bon ordre (sans
doublons) des données.
2. Lorsque le serveur reçoit le segment, il approuve la connexion en
renvoyant un paquet SYN-ACK (de l’anglais acknowledgement =
« confirmation »), ainsi que le numéro séquentiel du client augmenté de
1. Il transmet également aux clients son propre numéro séquentiel.
3. Pour finir, le client confirme la réception du segment SYN-ACK en
envoyant son propre paquet ACK qui, cette fois-ci, contient le numéro
séquentiel du serveur augmenté de 1. Simultanément, il peut
transmettre ses premières données au serveur.
fig

La transmission via le Transmission Control Protocol se fait en trois étapes,


d’où son nom le « 3-Way Handshake ».

TCP Teardown : fonctionnement de la déconnexion TCP


Les deux partenaires de communication peuvent désactiver une connexion

TCP établie. La déconnexion unilatérale est également possible. Cette

dernière est considérée comme une connexion semi-fermée : même

lorsqu’un abonné a désactivé sa connexion, l’autre partie peut encore lui

transmettre des données.

Les différentes étapes de la déconnexion bilatérale (initiée ici par le client

pour plus de simplicité) sont les suivantes :


1. Le client envoie un segment FIN au serveur et lui indique ainsi qu’il ne
souhaite plus envoyer de données. Tout comme lors de l’établissement
de la connexion, il envoie son propre numéro de séquence.
2. Le serveur confirme la réception du paquet par un segment ACK,
contenant le numéro séquentiel augmenté de 1.
3. Si, de son côté, le serveur a également terminé la transmission de
données, il envoie également un paquet FIN, auquel il adjoint son
numéro séquentiel.
4. C’est alors au tour du client d’envoyer un paquet ACK comprenant son
numéro séquentiel augmenté de 1. La connexion TCP est alors
officiellement désactivée pour le serveur.

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

connexion et à la transmission de données sur la base du Transmission

Control Protocol se trouvent dans l’en-tête d’un paquet TCP. Ces données

d’en-tête (ou informations de commande) précèdent la charge utile de

transmission (Payload) et représentent généralement un volume de 20

octets (160 bits) ; Elles sont suivies d’un maximum de 40 octets (320 bits)

d’informations supplémentaires en option, ne trouvant pas une application

dans tous les paquets.

Vous trouverez, ci-après, une description détaillée de l’en-tête TCP :

fig

Les différents composants ou champs de l’en-tête du protocole TCP sont


divisés comme suit :
Port Source (16 bits) : indique le numéro de port de l’expéditeur.

Port Destination (16 bits) : indique le numéro de port du destinataire.


Numéro de séquence (32 bits) : au choix, le numéro de séquence indique le

premier octet des données utiles jointes ou est envoyé lors de la procédure

d’activation ou de désactivation de la connexion. Il sert également à la

validation et au tri (au terme de la transmission) des segments.

Numéro d’accusé de réception (32 bits) : ce champ indique le numéro de

confirmation attendu par l’expéditeur. Un Flag ACK doit être activé pour

assurer sa validité (au champ « Flags »).

Offset (4 bits) : le champ « Offset » indique la longueur de l’en-tête TCP en

blocs de 32 bits pour mettre en avant le point de départ des données utiles.

Celui-ci peut varier de segment en segment en fonction du champ Options

variable.

Réservé (6 bits) : RFC 793, réservé pour utilisation ultérieure, jusqu’ici non

utilisé. Ce champ doit toujours indiquer la valeur « 0 ».

Flags (6 bits) : Les 6 bits individuels disponibles au champ Flags permettent

d’activer différentes actions TCP visant l’organisation de la communication et

de la transmission de données. Les Flags suivants peuvent être activés ou

désactivés ici :

• URG : le Flag « Urgent » signale à l’application TCP que les données


utiles doivent immédiatement être traitées jusqu’au pointeur Urgent
(voir ci-dessous).
• ACK : Combiné au numéro de confirmation, le Flag ACK accuse
réception des paquets TCP. Lorsque le Flag est désactivé, le numéro
de confirmation est automatiquement invalidé.
• PSH : le Flag « Push » garantit qu’un segment TCP soit immédiatement
mis à disposition sans atterrir d’abord dans la mémoire tampon de
l’expéditeur et du destinataire.
• RST : en cas d’erreur pendant la transmission, un paquet TCP portant
le Flag RST (« Reset ») permet de réinitialiser l’application.
• SYN : les messages portant un Flag SYN sont la première étape du 3-
Way Handshake. Ils initient donc l’activation de la connexion.
• FIN : le Flag « Finish » signale à son vis-à-vis qu’un partenaire de
communication a achevé la transmission.
Taille de fenêtre (16 bits) : ce champ indique aux partenaires de

communication le nombre d’octets que l’expéditeur est prêt à recevoir.

Total de contrôle (16 bits) : le Transmission Control Protocol est doté d’une

fonction fiable de détection des erreurs de transmission. Pour ce faire, il

utilise la somme de contrôle calculée à partir de l’en-tête, des données utiles

et du pseudo-en-tête.

Pointeur d’urgence (16 bits) : le pointeur d’urgence indique la position du

premier octet suivant les données utiles à traiter de manière urgente. Ce

champ est uniquement valide et pertinent lorsque le Flag URG est activé.

Options (0–320 bits) : le champ Options permet de préparer l’exécution de

fonctions TCP ne se trouvant pas dans l’en-tête général, la définition de la

longueur de segment maximum par exemple. La longueur des options doit

toujours être un multiple de 32 bits. Dans le cas contraire, il faut la compléter

de bits nuls (Padding).

Fonctionnement détaillé de la transmission de données via


le protocole TCP
Avant la transmission des premières données, l’expéditeur et le destinataire

conviennent généralement du volume maximum des segments TCP à

expédier (Maximum Segment Size – MSS). Par défaut, chaque segment


peut faire jusqu’à 1 500 octets, dont au moins 20 octets doivent être réservés

à l’en-tête TCP et 20 autres à l’en-tête IP, laissant 1 460 octets pour les

données utiles. Pour définir un volume individuel, il suffit de le préciser dans

le champ Options selon la procédure décrite ci-avant. Des ajustements

supplémentaires doivent alors être faits pour la partie Données utiles.

Considérant le volume de segment maximum, moins l’en-tête, un paquet TCP


peut uniquement transmettre des données de 1,46 kilooctets soit 0,00146
mégaoctets. Pour transmettre des contenus Web, telles que des images de
plusieurs centaines de kilooctets à l’aide du protocole TCP, on peut utiliser
la segmentation. Avant le transport, les données d’application sont alors
divisées en plusieurs blocs de données, numérotées, puis expédiées de
manière aléatoire. Le destinataire doit accuser réception de chaque segment
et peut remettre les paquets dans l’ordre à l’aide des numéros séquentiels. Il
peut ainsi recomposer facilement et dans leur intégralité les données utiles
reçues au terme de la transmission TCP.

Synthèse des faits les plus importants pour le Transmission


Control Protocol
Le protocole TCP marque, depuis près de 50 ans, l’histoire et le

développement des réseaux informatiques. Cela est en partie dû à sa bonne

compatibilité avec le protocole Internet historique IP, sans oublier les

avantages majeurs de TCP par rapport à des alternatives telles que l’UDP et

le SCTP. Ses caractéristiques principales peuvent être décrites comme suit :

• Le protocole TCP est un protocole de connexion permettant la


communication bilatérale entre deux points selon la procédure du 3-
Way Handshake.
• TCP est fiable. En effet, le protocole garantit la transmission de toutes
les données dans leur intégralité, ainsi que la possibilité, pour le
destinataire, de les remettre dans le bon ordre.
• TCP prévoit l’envoi des données par segments individuels d’un volume
maximum de 1 500 octets (en-tête compris).
• TCP est, dans le modèle OSI, intégré à la couche transport (couche
4).
• TCP se base, dans la plupart des cas, sur le protocole Internet (IP).
C’est pourquoi, on parle souvent de la pile de protocoles TCP/IP.
• Le volume par défaut de l’en-tête TCP est de 20 octets. Des options
supplémentaires pouvant atteindre jusqu’à 40 octets peuvent lui être
rajoutées.

Dans le diagramme ci-dessous, examinons la séquence des appels API de


socket et le flux de données pour TCP :
fig Fonctionnement de TCP

La colonne de gauche représente le serveur. La colonne de droite


représente le client.

En commençant par la colonne supérieure gauche, notez les appels API


effectués par le serveur pour configurer un socket "d'écoute" :
socket()
bind()
listen()
accept()

Un socket d'écoute fait exactement ce à quoi il ressemble. Il écoute les


connexions des clients. Lorsqu'un client se connecte, le serveur appelle
accept() pour accepter, ou compléter, la connexion.

Le client appelle connect() pour établir une connexion avec le serveur et


lancer la poignée de main à trois voies. L'étape de la poignée de main est
importante car elle garantit que chaque côté de la connexion est joignable
sur le réseau, en d'autres termes que le client peut atteindre le serveur et
vice-versa. Il se peut qu'un seul hôte, client ou serveur, puisse atteindre
l'autre.

Au milieu se trouve la section aller-retour, où les données sont échangées


entre le client et le serveur à l'aide d'appels à send() et recv().

En bas, le client et le serveur ferment leurs sockets respectifs.

Vous aimerez peut-être aussi