Servidor TCP de Python que acepta conexiones y comandos de difusión

He estado trabajando en un juego con una cantidad de Raspberry Pis, Python y algunos botones / interruptores. Mi juego requiere un servidor central que emita comandos a varios clientes.

No soy nuevo en progtwigción, pero sí en Python y en la comunicación de red de nivel inferior y me he perdido en la maleza durante los últimos 2 días sobre cómo escribir exactamente el código de mi servidor.

El progtwig cliente es un simple socket.connect y luego espera a que se envíen los datos. No hay problemas allí.

Me ha costado mucho determinar exactamente cómo escribir y cómo hacer funcionar el servidor.

Así es como se ve mi código de servidor en este momento:

import socket, time, sys import threading TCP_IP = '' TCP_PORT = 8888 BUFFER_SIZE = 1024 CLIENTS = {} clientCount = 0 def listener(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((TCP_IP,TCP_PORT)) s.listen(5) while True: conn, addr = s.accept() print("new connection from:"+ str(addr)) #print(len(CLIENTS)) global clientCount clientCount = clientCount+1 print (clientCount) # register client CLIENTS[conn.fileno()] = conn def broadcast(): for client in CLIENTS.values(): client.send('this is a broadcats msg') if __name__ == '__main__': listener() while clientCount > 0: broadcast() print(len(CLIENTS)) #print out the number of connected clients every 5s time.sleep(5) 

Aquí está el flujo deseado: 1. El servidor se inicia y espera la primera conexión o más. Creo que este “servidor” debería estar ejecutándose en un hilo de fondo? 2. Si connectionCount > 0 inicia el bucle de progtwig principal 3. Por ahora, el bucle de progtwig principal solo debe mostrar el número de clientes conectados y transmitir un mensaje a todos ellos cada 5 segundos.

Tengo alrededor de 5 versiones de este servidor. He intentado async, select.select y varios enfoques de subprocesos, pero no puedo precisar el comportamiento que busco. ¿Debo poner el servidor en un hilo de fondo? Si es así, ¿cómo transmitir a todas las conexiones?

Lo único que no he probado es Twisted y eso es porque no pude instalarlo en Windows … así que estoy descartando que optino por el momento. Si alguien tiene un puntero sobre dónde ir, ¡realmente lo apreciaría!

Actualizar

De acuerdo, según la sugerencia de @Armans, he actualizado mi código para que exista una clase de servidor, pero sigue funcionando igual.

 class server(): def __init__(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((TCP_IP,TCP_PORT)) s.listen(10) while 1: client_socket, addr = s.accept() print ('Connected with ' + addr[0] + ':' + str(addr[1])) global clientCount clientCount = clientCount+1 print (clientCount) # register client CLIENTS[client_socket.fileno()] = client_socket threading.Thread(target=self.handler, args=(client_socket, addr)).start() def handler(self, client_socket, addr): while 1: data = client_socket.recv(BUFFER_SIZE) print ('Data : ' + repr(data) + "\n") data = data.decode("UTF-8") def broadcast(self, message): for c in self.CLIENTS: c.send(message.encode("utf-8")) if __name__ == '__main__': s = server() #create new server listening for connections while clientCount > 0: s.broadcast('msg here') print(len(CLIENTS)) #print out the number of connected clients every 5s time.sleep(5) 

Puedo conectar varios clientes y la consola muestra lo siguiente:

 Connected with 10.0.0.194:38406 1 Connected with 10.0.0.169:36460 2 

Pero el código en el bucle “while clientCount” nunca se ejecuta. Esta es la zona en la que me he quedado estancado por algún tiempo, así que si tienes un par de ideas más, ¡me encantaría poder añadir alguna idea!

¡Finalmente lo puse en funcionamiento! Muchas gracias a @Arman por apuntarme en la dirección correcta con el enhebrado. ¡Finalmente siento que entiendo cómo funciona todo!

Aquí está mi código completo de Servidor y Cliente. Esperemos que esto ayude a alguien más con una configuración de cliente> maestra. La función _broadcast () funciona, ya que verá que solo transmite un mensaje estático en este momento, pero debería ser una actualización fácil.

Si alguien tiene algún consejo sobre la limpieza del código, las mejores prácticas de Python utilizan este código como el ejemplo que me gustaría escuchar y aprender más. Gracias de nuevo SE!

 ##Client import socket import sys import json #vars connected = False #connect to server client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect(('10.0.0.158',8888)) connected = True while connected == True: #wait for server commands to do things, now we will just display things data = client_socket.recv(1024) cmd = json.loads(data) #we now only expect json if(cmd['type'] == 'bet'): bet = cmd['value'] print('betting is: '+bet) elif (cmd['type'] == 'result'): print('winner is: '+str(cmd['winner'])) print('payout is: '+str(cmd['payout'])) ##Server import socket, time, sys import threading import pprint TCP_IP = '' TCP_PORT = 8888 BUFFER_SIZE = 1024 clientCount = 0 class server(): def __init__(self): self.CLIENTS = [] def startServer(self): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((TCP_IP,TCP_PORT)) s.listen(10) while 1: client_socket, addr = s.accept() print ('Connected with ' + addr[0] + ':' + str(addr[1])) global clientCount clientCount = clientCount+1 print (clientCount) # register client self.CLIENTS.append(client_socket) threading.Thread(target=self.playerHandler, args=(client_socket,)).start() s.close() except socket.error as msg: print ('Could Not Start Server Thread. Error Code : ') #+ str(msg[0]) + ' Message ' + msg[1] sys.exit() #client handler :one of these loops is running for each thread/player def playerHandler(self, client_socket): #send welcome msg to new client client_socket.send(bytes('{"type": "bet","value": "1"}', 'UTF-8')) while 1: data = client_socket.recv(BUFFER_SIZE) if not data: break #print ('Data : ' + repr(data) + "\n") #data = data.decode("UTF-8") # broadcast for client in self.CLIENTS.values(): client.send(data) # the connection is closed: unregister self.CLIENTS.remove(client_socket) #client_socket.close() #do we close the socket when the program ends? or for ea client thead? def broadcast(self, message): for c in self.CLIENTS: c.send(message.encode("utf-8")) def _broadcast(self): for sock in self.CLIENTS: try : self._send(sock) except socket.error: sock.close() # closing the socket connection self.CLIENTS.remove(sock) # removing the socket from the active connections list def _send(self, sock): # Packs the message with 4 leading bytes representing the message length #msg = struct.pack('>I', len(msg)) + msg # Sends the packed message sock.send(bytes('{"type": "bet","value": "1"}', 'UTF-8')) if __name__ == '__main__': s = server() #create new server listening for connections threading.Thread(target=s.startServer).start() while 1: s._broadcast() pprint.pprint(s.CLIENTS) print(len(s.CLIENTS)) #print out the number of connected clients every 5s time.sleep(5) 

Tengo un enfoque multithread aquí:

 s.listen(10) while 1: client_socket, addr = s.accept() print ('Connected with ' + addr[0] + ':' + str(addr[1])) threading.Thread(target=self.handler, args=(client_socket, addr)).start() def handler(self, client_socket, addr): while 1: data = client_socket.recv(BUFF) print ('Data : ' + repr(data) + "\n") data = data.decode("UTF-8") 

Les recomiendo que escriban una clase para Server y Client , para cada cliente, cree un object Cliente y conéctelo al Server , y almacene a cada Cliente conectado (su socket y un nombre, por ejemplo) en un diccionario como lo hizo, entonces desea para transmitir un mensaje, puede ir a través de todos los Clientes conectados en el Server y transmitir el mensaje que desee de esta manera:

 def broadcast(self, client_socket, message): for c in self.clients: c.send(message.encode("utf-8")) 

Actualizar

Debido a que tiene un thread que se ejecuta principal, necesita otro thread para ejecutar el servidor, le sugiero que escriba un método de start para el servidor y lo llame a un thread :

 def start(self): # all server starts stuff comes here as define socket self.s.listen(10) while 1: client_socket, addr = self.s.accept() print ('Connected with ' + addr[0] + ':' + str(addr[1])) threading.Thread(target=self.handler, args=(client_socket, addr)).start() 

ahora en la sección principal o en el archivo principal después de crear el objeto del servidor, ejecute el hilo de inicio:

 a = server() threading.Thread(target=a.start).start()