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.
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()
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,[],[])
# 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()
Voilà un exemple d’échange :
3 2 5 LIST > 5 2 3 ADD LIST > 7 3 POP > 7 LIST > 3
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