S52 - TP4 - Socket en python suite

Pour pouvoir traiter plusieurs clients en parallèle, il existe deux grandes familles de solutions.

La première la plus classique est qu’un processus serveur écoute les connexions et à chaque demande créée un autre processus pour assurer le traitement en lui passant la socket.

La seconde utilise une technique de pooling c’est-à-dire que le serveur va regarder périodiquement dans toutes les connections dont il dispose celles où il peut lire ou écrire.

L’exemple suivant montre comment on peut créer un serveur multitâche en python.

Etudier l’exemple suivant et le tester avec la commande telnet.
multiclientServer.py
import socket
import sys
import threading
 
## Le serveur se met en écoute 
HOST = '0.0.0.0'   
PORT = 2004
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error as msg:
    print("Bind failed. Error Code : " + str(msg[0]) + " Message " + msg[1])
    sys.exit() 
s.listen(10)
 
print("Serveur en écoute sur "+HOST+":"+str(PORT));     
 
#Cette fonction sera utilisée dans les threads qui traiteront les connections
def clientthread(conn):
    conn.send(("Hello from "+HOST+":"+str(PORT)+"\n").encode('UTF-8'))
 
    #Mettre ici l'automate du serveur.
    #Ici une boucle infinie arrêtée lors de l'envoi d'un chaîne vide
    while True:         
        #Receiving from client
        data = conn.recv(1024).decode('UTF-8')
        reply = 'Recu: ' + data
 
        #si la reponse est FIN ou un ligne vide, le dialogue d'arrête.
        if data.upper().strip() == "FIN" or data.strip() == "":
                break
 
        conn.sendall(reply.encode('UTF-8'))
    conn.close()
 
#La boucle d'attente des connexions
while True:
     try: 
       conn, addr = s.accept()
       print('Connection de ' + addr[0] + ':' + str(addr[1]))
 
       #Création de la thread qui prendra en charge la connection.
       #threading.Thread( group=None, target=None, name=None, args=(), kwargs={}) où :
	   #	group doit rester à None, en attendant que la classe ThreadGroup soit implantée.
	   #	target est la fonction appelée par le Thread.
	   #	name est le nom du Thread.
	   #	args est un tuple d'arguments pour l'invocation de la fonction target
	   #	kwargs est un dictionnaire d'argumens pour l'invocation de la fonction target
 
       t=threading.Thread(None, clientthread , None,(conn,), {})
       t.start()
 
     except KeyboardInterrupt:
            print("Stop.\n")
            break
 
s.close()
En vous inspirant de cet exemple, écrire un serveur qui comprend les commandes suivantes :
  • DATA (le serveur attend ensuite un liste d’entier sous la forme de chaîne de caractères sur une ligne, une ligne vide indique la fin des données). Plusieurs commandes DATA peuvent être envoyées.
  • SUM, MIN, MAX, … qui retourne le résultat de la fonction sur la liste des dernières données saisies. Il doit donc y avoir eu au moins un data.
  • HEAD X : retourne les entiers avant la position X
  • TAIL Y : retourne les entiers après la position X
  • INTERVAL X Y : retourne les entiers entre les positions X et Y
  • FIN termine la session

Voilà un exemple d’échange :

DATA
12
3
5
MIN
>3
MAX
>12
HEAD 2
>12 3
TAIL 1
> 5

La solution à base de pooling utilise la méthode suivante :

read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])
Etudier l’exemple suivante et le tester avec telnet.
chatServer.py
# Tcp Chat server
 
import socket
import select
import sys
 
CONNECTION_LIST = []
RECV_BUFFER = 4096
HOST = "0.0.0.0"
PORT = 5000
 
def broadcastToClients(sock, message):
    # Do not send the message to master socket and the client who has send us the message
    for socket in CONNECTION_LIST:
        if socket != server_socket and socket != sock:
            try:
                print(message)
                socket.send(message.encode('UTF-8'))
            except:
                # La ligne suivante permet d'afficher l'erreur
                # print("Unexpected error: "+sys.exc_info()[0])
                # En général, c'est le client qui n'est disponible
                # On ferme la connexion et on le supprime
                socket.close()
                CONNECTION_LIST.remove(socket)
 
 
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(10)
 
# On ajoute le serveur à la liste des connexions pour pouvoir "lire"
# la connexion d'un nouveau client
CONNECTION_LIST.append(server_socket)
 
print("Chat server started " + HOST + ":" + str(PORT))
 
while 1:
    try:
        # On recupère la liste des sockets dans lesquelles on peut lire dans read_sockets
        # écrire dans write_sockets...
        read_sockets, write_sockets, error_sockets = select.select(CONNECTION_LIST, [], [])
 
        for sock in read_sockets:
            # Si on peut lire dans celle du serveur c'est une nouvelle connexion
            if sock == server_socket:
                # On l'accepte et on ajoute la connexion à CONNECTION_LIST
                sockfd, addr = server_socket.accept()
                CONNECTION_LIST.append(sockfd)
                print("Client (" + addr[0] + ", " + str(addr[1]) + ") connected")
                # On envoie le message à tous les clients
                broadcastToClients(sockfd, "[" + addr[0] + ":" + str(addr[1]) + "] entered room\n")
 
            # Sinon c'est qu'un message à été reçu d'un client.
            else:
                try:
                    data = sock.recv(RECV_BUFFER).decode('UTF-8')
                    if data:
                        # Le message est envoyer à tous les clients.
                        # Le premiere paramètre est la socket dans laquelle le message a été reçu
                        #    pour ne pas lui retourner
                        broadcastToClients(sock, '<' + str(sock.getpeername()) + '> ' + data + '\n')
 
                except:
                    #La ligne suivante est utile pour savoir quelle erreur a été émise
                    # print("Unexpected error: " + sys.exc_info()[0])
                    broadcastToClients(sock, "Client [" + addr[0] + ":" + str(addr[1]) + "] is offline")
                    print("Client [" + addr[0] + ":" + str(addr[1]) + "] is offline")
                    sock.close()
                    CONNECTION_LIST.remove(sock)
                    continue
    except KeyboardInterrupt:
        print("Stop.\n")
        break
 
server_socket.close()
Mettez à jour l’exemple pour disposer d’un serveur de chat réaliste qui dispose des commandes :
  • NICKNAME qui permet de disposer d’un nom d’utilisateur
  • MSG nickname qui envoie un message au client d’un utilisateur dont le nickname est donné.
En utilisant l’un des modèles ci dessus, programmez les problèmes suivant dans l’ordre de votre choix.
Ecrire un serveur qui permet a client de gérer une pile d’entiers. Quand le serveur reçoit un entier il l’empile, quand le serveur reçoit l’une des commandes ADD, MUL, DIV, MOD il effectue le calcul avec les deux derniers entiers sur la pile et empile le résultat. Quand le serveur reçoit la commande LIST il transmet la pile sans la modifier, quand il reçoit la commande POP il dépile le dernier entier et le transmet au client.

Voilà un exemple d’échange :

3
2
5
LIST
> 5 2 3
ADD
LIST
> 7 3
POP
> 7
LIST 
> 3  
Ecrire un serveur qui attend que des clients se connectent, quand un des clients envoie la commande GO. Le serveur partage entre ses n clients le calcul de la somme des racines carrées des nombres entre 1 et 100*n.
Ecrire un serveur qui attend que des clients se connectent, quand un des clients envoie la commande GO, le serveur génère un grand tableau d’entier aléatoires, le découpe entre ses clients qui réalise un tri fusion, le serveur fusionne ensuite les résultats pour afficher le résultat.
Ecrire un programme de bataille navale en réseau. Le programme sera tour à tour client puis serveur. Le programme prendra trois paramètres : le numéro du joueur (1 ou 2), n le nombre de bateaux et le port du serveur. Les bateaux sont tous de taille 1 et la grille est de taille n*n. Le fonctionnement est le suivant :
  • les deux joueurs remplissent leur grille (des tableaux de caractères) avec '.’. Les n bateaux sont disposés aléatoirement (attention, à ne pas mettre deux bateaux au même endroit). Chaque joueur affiche sa grille.
  • le joueur 1 est le serveur, le joueur 2 est le client.
  • Le joueur 1 écoute le tir du joueur 2 (deux entiers) et répond avec le nombre de bateaux restants (1 entier).
  • Les rôles sont inversés, jusqu’à ce que le nombre de bateau restants de l’un des joueurs soit 0.
  • Chaque joueurs a donc successivement la fonction écouter et parler.

https://docs.python.org/3/library/socketserver.html

import socketserver
 
class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The RequestHandler class for our server.
 
    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """
 
    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())
 
if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
 
    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
 
    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()

—- dataentry page —- type : TP enseignement_tags : S52 technologies_tags : Socket, TCP, Python themes_tags : Réseaux