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.
Un serveur multitâche avec plusieurs fils
L’exemple suivant montre comment on peut créer un serveur multitâche en python.
- 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()
- 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
Un serveur qui écoute plusieurs clients
La solution à base de pooling utilise la méthode suivante :
read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])
- 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()
- 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é.
Applications
Serveur Calculette
Voilà un exemple d’échange :
3 2 5 LIST > 5 2 3 ADD LIST > 7 3 POP > 7 LIST > 3
Calcul distribué
Tri distribué
Bataille navale
- 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.
Un serveur "simplifié"
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