TP 10 - Communication en réseau

Présentation théorique

Pour pouvoir discuter de la communication dans un réseau, il est important de connaître les notions suivantes:

Adresse physique

L'adresse physique, également appelée adresse MAC, est un nombre de 48 bits qui identifie de manière unique une carte réseau. Ce numéro est inscrit sur la carte réseau par le fabricant, donc généralement il ne peut pas être modifié. L'adresse MAC est utilisée pour la transmission de données au niveau d'accès au réseau dans la pile TCP / IP.

Une adresse MAC est représentée en regroupant les 48 bits en 6 octets et en écrivant chaque octet dans la base 16 (ex: 00: A0: C9: 14: C8: 29 ou 00A0-C914-C829).

Adresse IP

Une adresse IP identifie une station dans un réseau et est utilisée pour la transmission de données au niveau du réseau à partir de la pile TCP / IP.

Fondamentalement, l'IP d'une station est un nombre, 32 bits dans le cas du protocole IPv4 ou 128 bits dans le cas du protocole IPv6. Habituellement, les adresses IP sont écrites sous une forme restreinte. Dans le cas d'IPv4, l'adresse IP est écrite sous la forme de 4 chiffres dans la base décimale, avec des valeurs comprises entre 0 et 255, séparées par. (ex: 192.168.0.14), et dans le cas d'IPv6, l'adresse IP est écrite sous la forme de 8 groupes de nombres dans la base hexadécimale, avec des valeurs comprises entre 0000 et ffff, séparées par : (ex: 2001:0db8:85a3:0000:0000:8a2e:0370:7334).

Port

Le port est un identifiant utilisé pour la transmission de données au niveau de l'application.

Pour garantir que deux applications communiquent dans un réseau, nous devons spécifier l'adresse IP des stations communicantes, ainsi que le port. Alors que l'adresse IP garantit que les paquets arrivent à destination, le port garantit que le paquet reçu est utilisé par l'application appropriée.

Par exemple, deux ordinateurs peuvent communiquer à la fois pour échanger des e-mails, mais aussi pour réaliser une video-conférence. Ainsi, les deux ordinateurs échangent des messages pour les deux applications (messagerie électronique et video-conférence). Alors que les adresses IP sont utilisées pour garantir que tous les paquets arrivent au bon endroit, les applications de messagerie électronique et de video-conférence auront un port différent, en fonction des messages qui seront distribués à la bonne application.

Dans les applications développées par nous, nous devons nous assurer que nous choisissons une valeur pour le port qui n'est pas utilisée par d'autres applications existantes sur le système. Sinon, nous risquons d'avoir des conflits. C'est pourquoi nous choisissons les valeurs 8000 ou 8080 , qui ne sont pas utilisées par les services connus.

Socket

Afin de pouvoir transmettre des messages sur le réseau, nous utiliserons des structures de socket.

Un socket permet la transmission de messages entre applications sur la même machine ou sur différentes machines physiques, d'une manière similaire à celle utilisant des descripteurs de fichiers.

En Python, on va utiliser le module socket pour les opérations de communication en réseau. Parmi les opérations que nous pouvons effectuer en utilisant le module “socket” figurent:

Obtenir des informations sur la station actuelle

La fonction gethostname renvoie l'adresse IPv4 de la station locale.

socket.gethostname()

Obtenir des informations sur d'autres éléments du réseau

La fonction getaddrinfo renvoie une liste de 5 éléments qui contiennent des informations sur une adresse et un port spécifiés. La liste renvoyée contient les informations suivantes: (family, type, proto, canonname, sockaddr), et sockaddr est une autre liste qui contient (address, port).

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)

La fonctiongethostbyname renvoie l'adresse IPv4 d'un hostname.

socket.gethostbyname(hostname)

Création de connexions TCP

En utilisant des sockets, nous pouvons réaliser deux types de transmission de données: UDP ou TCP.

Pour les deux types de transmissions, nous devons créer un nouvel objet socket à l'aide de la fonction socket (family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None). Selon les paramètres transmis au constructeur, le socket est configuré pour un certain type de transfert de données.

  • family - représente le type d'adressage utilisé (ex: pour IPv4, le type utilisé est AF_INET , pour IPv6 le type est AF_INET6 ); dans notre cas, nous utiliserons AF_INET (socket.AF_INET, en Python);
  • type - représente le type de transfert de données utilisé; en général, nous choisirons entre SOCK_STREAM (pour le transfert TCP) et SOCK_DGRAM (pour le transfert UDP);
  • proto - représente le protocole utilisé, dans notre cas nous laisserons la valeur par défaut;
  • fileno - représente un descripteur de fichier , et si ce paramètre est passé, toutes les 3 autres valeurs sont définies automatiquement en fonction de celui-ci.
import socket
s = socket.socket()

Pour le transfert TCP, on utilise le paradigme client-serveur, dans lequel un processus attend les connexions (serveur), tandis que d'autres processus peuvent s'y connecter (client). En utilisant TCP, les données transmises atteindront sûrement la destination ou une erreur sera reçue.

Initialisation du serveur

Pour démarrer un serveur en mode socket, nous devons effectuer les opérations suivantes:

  • Attribuer une adresse et un port au socket (bind)
  • Préciser que nous attendons des connexions et combien (listen)
  • Accepter les connexions entrantes (accept); la fonction renvoie un tuple avec deux valeurs: la connexion et une adresse; la connexion est une variable socket qui sera utilisée pour transmettre des messages; l'adresse est l'adresse du programme qui s'est connecté.

Si l'adresse IP transmise à la fonction bind est 0.0.0.0 , le socket prend en charge les connexions sur toutes les cartes réseau.

Exemple
import socket
s = socket.socket()
s.bind(('0.0.0.0', 8000))
s.listen(0)
conn, addr = serv.accept()

Initialisation du client

Pour établir une connexion à partir du serveur, en utilisant le mode socket, nous devons appeler la fonction connect.

Pour mettre fin à la connexion avec le serveur, nous utiliserons la fonction close.

Exemple
import socket
s = socket.socket()
s.connect(('localhost', 8000))
# send/receive data
s.close()

Pour se connecter à un service exécuté sur la machine locale, on peut utiliser l'adresse localhost ou 127.0.0.1 .

Échange de données en réseau

Envoi de données

Pour envoyer des données à l'aide du module socket, nous pouvons utiliser les fonctions send et sendto.

socket.send(bytes[, flags])
socket.sendto(bytes, address)
socket.sendto(bytes, flags, address)

La fonction send envoie des données uniquement si le socket est connecté à un autre socket, il ne peut donc être utilisé que pour une transmission TCP.

La fonction sendto envoie des données à une adresse passée en paramètre, sans nécessiter de connexion entre les deux objets socket, afin qu'elle puisse être utilisée pour la transmission UDP.

Le paramètre “address” est un tuple de type (adresse, port), par exemple:(“0.0.0.0”, 8000).

Réception de données

Pour recevoir des données en utilisant le module socket nous pouvons utiliser les fonctions recv et recvfrom.

socket.recv(bufsize[, flags])
socket.recvfrom(bufsize[, flags])

La fonction recv lit les données à leur taille maximale, spécifiée comme paramètre ( bufzise ), et les retourne comme un objet bytes. Cette fonctionnalité convient à la transmission TCP, où nous avons une connexion constante à une autre socket.

La fonction recvfrom lit des données similaires à la fonction recv , mais renvoie deux objets: le message et l'adresse source. Ainsi, nous pouvons utiliser l'adresse retournée pour renvoyer des messages. Cette fonctionnalité convient à la transmission UDP, où nous n'avons aucune connexion à une autre prise.

L'objet address renvoyé est un tuple de type (adresse, port), par exemple (“0.0.0.0”, 8000).

Exemples

Dans ces exemples, nous allons créer un processus qui attend et répond continuellement aux messages (serveur) et un autre processus qui envoie un message et attend une réponse (client).

Afin de tester la communication sur le réseau à l'aide d'un seul ordinateur, nous allons créer deux fichiers sources simulant chacun le comportement d'une station réseau différente. Pour tester la communication, nous exécuterons les deux programmes en parallèle.

Exemple UDP

Server

import socket
 
buffersize = 2048
# create socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# bind socket to address and port
s.bind (("0.0.0.0", 8000))
 
# listen for data forever and reply with "Message received"
while True:
    data, addr = s.recvfrom(buffersize)
    print ("Data from {} is {}".format (addr, data))
    msg = str.encode ("Message received")
    s.sendto (msg, addr)

Client

import socket
buffersize = 2048
 
# create socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# create bytes object
msg = str.encode("Hello")
# send message
s.sendto (msg, ("localhost", 8000))
# read message
data, addr = s.recvfrom (buffersize)
print ("Data from {} is {}".format (addr, data))

Pour tester l'exemple, exécutez d'abord le serveur puis le client sur un autre terminal.

Exemple TCP

Server

import socket
 
buffersize = 2048
# create socket
s = socket.socket (family=socket.AF_INET, type=socket.SOCK_STREAM)
# bind socket to address and port
s.bind (("0.0.0.0", 8000))
# wait for connections
s.listen (0)
# accept connections forever
while True:
    conn, addr = s.accept ()
    print ("Connected to {}".format (addr))
 
    # listen for data and reply with "Message received"
    while True:
        data = conn.recv(buffersize)
        # client finished sending message
        if not data:
            break
        print ("Received {}".format (data))
        msg = str.encode ("Message received")
        conn.send (msg)
    # close connection
    conn.close ()

Client

import socket
buffersize = 2048
 
# create socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# connect to server
s.connect (("localhost", 8000))
# create bytes object
msg = str.encode("Hello")
# send message
s.send (msg)
# read message
data = s.recv (buffersize)
# close connection
s.close ()
 
print ("Data from {} is {}".format (addr, data))

Exercices de laboratoire

Exercice 1 - Informations sur le réseau

  1. Créer un programme qui affiche l'adresse physique, l'adresse IP et le nom de la station.
  2. Afficher l'adresse IP de google.com.

Exercice 2 - Netcat

Netcat est un utilitaire de la ligne de commande qui permet des opérations réseau. Dans cet exercice, nous allons l'utiliser pour échanger des messages avec des programmes python dans les exemples ci-dessus.

  1. Exécutez l'application serveur à partir de l'exemple de transmission TCP, puis dans un autre terminal, en parallèle, exécutez la commande: netcat -z -v localhost 8000 . Notez la réponse reçue.
  2. Exécutez l'application serveur à partir de l'exemple de transmission UDP, puis exécutez une autre commande en parallèle, exécutez la commande: echo test | netcat -u localhost 8000 . Notez le comportement résultant (l'option -u spécifie le mode de transmission UDP).
  3. Envoyer un message au programme du serveur TCP en utilisant netcat.
  4. Exécutez la commande netcat -lu -p 8080, qui démarre un serveur UDP sur le port 8080. Modifiez l'exemple de laboratoire pour envoyer un message au serveur démarré.
  5. Exécutez la commande netcat -l -p 8080, qui démarre un serveur TCP sur le port 8080. Modifiez l'exemple de laboratoire pour envoyer un message au serveur en cours d'exécution.

Exercice 3 - UDP

Créez deux programmes qui communiquent en mode UDP via socket afin que l'un des programmes reçoive trois nombres séparés par ; et réponde avec leur moyenne arithmétique.

Exercice 4 - Serveur TCP

Simulez un serveur TCP qui reçoit des commandes bash et répond avec leur résultat. Créez un client qui envoie des commandes et affiche le résultat reçu du serveur. Astuce: utilisez un pipe pour stocker la sortie de la commande démarrée.

Exercice 5 - Serveur TCP multi-threaded

Modifiez le serveur créé au point précédent pour prendre en charge plusieurs connexions simultanées. Utilisez des threads dans la mise en œuvre. Astuce: Le programme principal accepte les connexions et une fois la connexion établie, démarrez un nouveau thread qui reçoit la connexion en tant que paramètre.

sde/laboratoare/10_fr_python.txt · Last modified: 2020/04/23 17:28 by ioana_maria.culic
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0