Socket TCP Client-Serveur avec Python

image_pdfimage_print

Un socket réseau est un point de communication inter-processus sur un réseau informatique. La bibliothèque standard Python possède un module appelé socket qui fournit une interface réseau Internet de bas niveau. Cette interface est commune à différents langages de programmation car elle utilise des appels système au niveau du système d’exploitation.

Pour créer un socket, il existe une fonction appelée socket(). Il accepte les arguments suivnats: famille, type et proto. Pour créer un socket TCP, vous devez utiliser socket.AF_INET ou socket.AF_INET6 pour la famille et socket.SOCK_STREAM pour le type.
 
 
Voici un exemple de socket Python:

import socket

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

Il renvoie un objet socket qui a les principales méthodes suivantes:

  • bind()
  • listen()
  • accept()
  • connect()
  • send()
  • recv()

bind(), listen() et accept() sont spécifiques aux sockets serveur. connect() est spécifique aux sockets client. send() et recv() sont communs aux deux types. Voici un exemple de serveur:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 9999))
s.listen(5)
conn, addr = s.accept()

while True:
    data = conn.recv(1024)
    if not data:
        break
    conn.sendall(data)
conn.close()

Ici, nous créons un socket de serveur, le lions à localhost et un port 9999 et commençons à écouter les connexions entrantes.

Ensuite, nous avons utilisé s.listen(5) pour écouter. Ici, le « 5 » représente le nombre de connexions entrantes que nous sommes prêts à mettre en file d’attente avant d’en refuser.

Pour accepter une connexion entrante, nous appelons la méthode accept() qui bloquera jusqu’à ce qu’un nouveau client se connecte. Lorsque cela se produit, il crée un nouveau socket et le renvoie avec l’adresse du client.

Ensuite, dans un boucle infini, il lit les données du socket par lots de 1024 octets en utilisant la méthode recv() jusqu’à ce qu’il retourne une chaîne vide. Après cela, il renvoie toutes les données entrantes en utilisant la méthode sendall() qui à l’intérieur appelle plusieurs fois la méthode send(). Et après cela, il ferme simplement la connexion du client.

Cet exemple ne peut servir qu’une seule connexion entrante car il n’appelle pas accept() dans la boucle.
 
 
Voici le code côté client:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 9999))
s.sendall('Welcome to WayToLearnX!')
data = s.recv(1024)
s.close()
print repr(data), 'Reçue'

Ici, au lieu d’utiliser bind() et listen(), il appelle uniquement connect() et envoie immédiatement des données au serveur. Il reçoit ensuite 1024 octets, ferme le socket et affiche les données reçues.

Toutes les méthodes de socket bloquent. Par exemple, lorsqu’il lit à partir d’un socket ou y écrit, le programme ne peut rien faire d’autre. Une solution possible consiste à déléguer le travail avec les clients à des threads séparés. Pourtant, la création de threads et la commutation de contextes entre eux n’est pas vraiment une opération si facile. Pour résoudre ce problème, on utilise la synchronisation. L’idée principale est de déléguer le maintien de l’état du socket à un système d’exploitation et de le laisser avertir le programme lorsqu’il y a quelque chose à lire sur le socket ou lorsqu’il est prêt pour l’écriture.

Il existe de nombreuses interfaces pour différents systèmes d’exploitation:

  • kqueue, kevent (pour le système BSD)
  • poll, epoll (pour le système linux)
  • select (multiplateforme)

Ils sont tous à peu près les mêmes, alors créons un serveur en utilisant Select.
 
 
Voici un exemple utilisant Select:

import select
import socket
import sys
import Queue

# Créer un socket TCP / IP
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)

# Liez le socket au port
server.bind(('localhost', 9999))
# Écoutez les connexions entrantes
server.listen(5)
# Sockets à partir desquels nous nous attendons à lire
inputs = [server]
# Sockets dans lesquels nous nous attendons à écrire
outputs = []
# Files d'attente de messages sortants
msg = {}

while inputs:
    # Attendez qu'au moins une des sockets soit prête pour le traitement
    readable, writable, exceptional = select.select(inputs, outputs, inputs)
	# Gérer les entrées
    for s in readable:
        if s is server:
            # Un socket serveur "readable" est prêt à accepter une connexion
            connection, client_address = s.accept()
            connection.setblocking(0)
            inputs.append(connection)
            # Donner à la connexion une file d'attente pour les données que nous voulons envoyer
            msg[connection] = Queue.Queue()
        else:
            data = s.recv(1024)
            # Un socket client "readable" contient des données
            if data:
                msg[s].put(data)
                if s not in outputs:
                    outputs.append(s)
            else:
                # Interpréter le résultat vide comme une connexion fermée
                if s in outputs:
                    outputs.remove(s)
                inputs.remove(s)
                s.close()
                del msg[s]

    # Gérer les sorties
    for s in writable:
        try:
            next_msg = msg[s].get_nowait()
        except Queue.Empty:
            # Aucun message en attente, alors arrêtez de vérifier l'écriture.
            outputs.remove(s)
        else:
            s.send(next_msg)

    #Gérer les exceptions
    for s in exceptional:
        # Arrêtez d'écouter les entrées sur la connexion
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()
        del msg[s]

Comme vous pouvez le voir, il y a beaucoup plus de code que dans le mode bloquant. C’est principalement parce que nous devons maintenir un ensemble de files d’attente pour différentes listes de sockets, c’est-à-dire l’écriture, la lecture et une liste séparée pour les sockets erronées.

server.setblocking(0): Ceci est fait pour rendre le socket non bloquant. Ce serveur est plus avancé car il peut desservir plusieurs clients.
 

Partagez cet article

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *