domingo, 2 de junio de 2024

Redes con Python: Programación de Sockets para la Comunicación. 4.- Manejando múltiples conexiones

El modelo que hemos usado hasta ahora tiene sus limitaciones. El mayor es que el servidor atiende solamente a un cliente y luego finaliza la conexión. Para solventarlo usaremos una biblioteca de Python bastante similar, socketserver. Es muy útil para manejar sockets y nos ayudará a manejar múltiples conexiones. 

Vamos a verlo a través de un ejemplo. Crearemos un programa de preguntas y respuestas que se pueda jugar solo o bien entre dos jugadores. El servidor será el que envíe las preguntas y luego compruebe las respuestas de los clientes. Los clientes serán los jugadores.

Empecemos.

Vamos a empezar a crear el servidor importando las librerías que necesitaremos.

quiz_server.py

import socketserver
from collections import namedtuple
from random import choice
from threading import Event
import pickle

1.- socketserver. Es la librería que nos va a proporcionar la infraestructura básica para usar los servidores de red.  En el programa lo utilizaremos para crear un TCP multihilo que maneje las conexiones de los clientes (los jugadores)

2.- from collections import namedtuple. Es una extensión para poder utilizar las tuplas y acceder a sus elementos por su nombre, en vez de por su posición en la tupla.

3.- from random import choice. Proporciona funciones para generar números pseudo-aleatorios y seleccionar elementos aleatorios de una secuencia.

4.- from threading import Event. Con el crearemos diferentes hilos para cada cliente que se conecte. Utilizaremos instancias de Event para sincronizar el estado entre los diferentes hilos. Lo haremos explicando a medida que desarrollemos el programa.

5.- pickle. Se utiliza para serializar y deserializar datos en Python a una cadena de bytes que pueda ser enviada a través de sockets.

A continuación vamos a especificar los comandos que se podrán enviar entre servidor-cliente (luego veremos para que se usan) e iniciaremos las variables que vamos a necesitar posteriormente.

quiz_server.py

#...

''' Comandos: COLOCA TUS COMANDOS AQUÍ QUES - comando de pregunta UNIR - solicitud de unirse al principio 1 - Se utiliza para enviar una pregunta al cliente después de una solicitud 2 - Se utiliza para enviar "Espera el evento listo para empezar" al cliente. 4 - Se utiliza para enviar una respuesta al servidor después de una pregunta 7 - Se utiliza para enviar una respuesta "Correcta" o "Incorrecta" después de una respuesta

'''

NUMERO_DE_JUGADORES = 2 jugadores = [] listo_para_empezar = Event() contestadas = 0 esperando_respuestas = Event() pregunta_Actual = None Pregunta = namedtuple('Pregunta', ['p', 'respuesta'])
Para tener un buen concurso de preguntas y respuestas tendremos que crear unas cuantas. Puedes poner las preguntas y las respuestas que te parezcan. Yo crearé unas pocas para que el programa sea funcional.

quiz_server.py

#...
juego_Preguntas = [
    Pregunta("¿Como se llama un polígono de tres lados?", "Triángulo"),
    Pregunta("¿Cuál es el quinto planeta del sistema solar?", "Jupiter"),
    Pregunta("¿Cuál es la capital de Francia?", "Paris"),
    Pregunta("¿Quién dijo la frase: 'Solo se que no se nada'?", "Sócrates"),
    Pregunta("¿Cuál es el antónimo de rico?", "Pobre")
]
Vamos a detenernos un momento para ver el funcionamiento de namedtuple.

Podríamos haber guardado las preguntas y respuestas como una tupla directamente dentro de una lista, es decir del siguiente modo:

juego_Preguntas = [(pregunta, respuesta), (pregunta, respuesta),....(pregunta, respuesta)]

Si son pocas preguntas podríamos manejarlo pero si fueran 1000 preguntas la cosa se nos complicaría, ya que nos tendríamos que acordar de que la posición 0 de la tupla es la pregunta y la posición 1 es la respuesta.  Sin embargo al usar nametupled podemos acceder a los elementos por su nombre:

Pregunta.p = La pregunta en cuestión
Pregunta.respuesta = la respuesta a la pregunta.

Por ejemplo la primera de la lista es el elemento cero. Si añades un print con el siguiente código verás:

quiz_server.py

#...
juego_Preguntas = [
    Pregunta("¿Como se llama un polígono de tres lados?", "Triángulo"),
    Pregunta("¿Cuál es el quinto planeta del sistema solar?", "Jupiter"),
    Pregunta("¿Cuál es la capital de Francia?", "Paris"),
    Pregunta("¿Quién dijo la frase: 'Solo se que no se nada'?", "Socrates"),
    Pregunta("¿Cual es el antónimo de rico?", "Pobre")
]

print(f"La pregunta es {juego_Preguntas[0].p} y su respuesta: {juego_Preguntas[0].respuesta}")
print("\n")
print(juego_Preguntas)

La salida será algo como esto:

>>> %Run 
La pregunta es ¿Como se llama un polígono de tres lados? y su respuesta: Triángulo


[Pregunta(p='¿Como se llama un polígono de tres lados?', 
respuesta='Triángulo'), Pregunta(p='¿Cuál es el quinto planeta del sistema solar?', respuesta='Jupiter'), 
Pregunta(p='¿Cuál es la capital de Francia?', respuesta='Paris'), 
Pregunta(p="¿Quién dijo la frase: 'Solo se que no se nada'?", respuesta='Socrates'), 
Pregunta(p='¿Cuál es el antónimo de rico?', respuesta='Pobre')]

Para conocer más sobre como funciona las tuplas con nombre puedes acceder a su documentación en Python.

Ahora, comenta o elimina los print que hemos utilizado anteriormente (los que están en negrita arriba) para ver el funcionamiento de las namedtuple y sigamos con el desarrollo del ejemplo.


Crear un servidor multijugador en Python. Entendiento ThreadedTCPServer.

¿Te has preguntado cómo funcionan los servidores que permiten a múltiples personas conectarse al mismo tiempo, como en los juegos en línea o las aplicaciones de chat? Vamos a explorar cómo crear un servidor básico en Python que pueda manejar varias conexiones al mismo tiempo, usando algo llamado ThreadedTCPServer. Nos servirá para que los jugadores de nuestro juego se puedan conectar de forma simultánea.

¿Qué es un Servidor?

Un servidor es como un anfitrión de una fiesta. Imagina que organizas una fiesta en tu casa. Las personas (los clientes) llegan a tu puerta y tú las recibes, hablas con ellas, y las dejas entrar. En el mundo de la informática, un servidor hace algo similar: recibe conexiones de otros programas (los clientes) y les envía y recibe información. En nuestro programa les enviará las preguntas y los clientes, los jugadores, le enviarán sus respuestas.

¿Qué es ThreadedTCPServer?

ThreadedTCPServer es una forma especial de crear un servidor en Python que puede hablar con muchos clientes al mismo tiempo. Sin ThreadedTCPServer, nuestro servidor solo podría hablar con una persona a la vez, lo cual no sería muy eficiente para una fiesta concurrida.

Vamos a desglosar ThreadedTCPServer y ver cómo funciona:

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

Aquí estamos creando una nueva clase llamada ThreadedTCPServer. Esta clase combina dos cosas:

  1. socketserver.ThreadingMixIn: Esto le da al servidor la capacidad de manejar cada conexión en un hilo separado. Piensa en un hilo como un asistente personal. Si tienes muchos asistentes, puedes hablar con muchos invitados a la vez sin tener que esperar a que termines de hablar con cada uno.

  2. socketserver.TCPServer: Esto le da al servidor la habilidad básica de recibir conexiones de clientes y hablar con ellos usando un protocolo llamado TCP (es como el lenguaje que usan para comunicarse).

Cuando combinamos estas dos cosas, tenemos un servidor que puede hablar con muchos clientes al mismo tiempo, de manera eficiente.

¿Cómo Usamos ThreadedTCPServer?

Vamos a ver cómo ponemos todo esto en acción. Olvidaté de momento del código anterior. Aquí vamos a ver  un pequeño programa que crea un servidor usando ThreadedTCPServer. 

import socketserver
from threading import Event

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

class QuizGame(socketserver.BaseRequestHandler):
    def handle(self):
        self.request.sendall(b"Hello, world")

if __name__ == "__main__":
    HOST, PORT = "localhost", 2065
    with ThreadedTCPServer((HOST, PORT), QuizGame) as server:
        print(f"Server running on {HOST}:{PORT}")
        # Hace que el servidor funcione de forma indefinida
        server.serve_forever() 

¿Qué Hace Este Programa?

  1. Creamos ThreadedTCPServer: Esto define nuestro servidor que puede manejar múltiples conexiones al mismo tiempo.
  2. Definimos QuizGame: Esta es la parte del servidor que se encarga de lo que sucede cuando alguien se conecta. En este caso, solo envía un mensaje "Hello, world".
  3. Configuramos y Ejecutamos el Servidor: Le decimos a nuestro servidor que escuche en "localhost" (tu propia computadora) en el puerto 2065. Luego, lo ponemos a funcionar indefinidamente para que esté siempre listo para recibir conexiones.

Cuando ejecutas este programa, puedes conectar múltiples clientes (otros programas) al servidor, y todos recibirán el mensaje "Hello, world" sin tener que esperar uno por uno. Puedes abrir en tu navegador varias pesatañas y conectarte a "localhost:2065" para ver como funciona el servidor.



servidor multihilo en funcionamiento


Como te habrás dado cuenta en post anteriores para enviar los datos utilizábamos el método send. Sin embargo en esta ocasión hemos utilizado sendall. ¿En que se diferencian?

La diferencia principal entre sendall y send en Python radica en cómo envían los datos a través de un socket y cómo manejan el tamaño de los datos enviados. Aquí te explico cada uno:

send

  • Descripción: send es un método del objeto socket que envía datos a través del socket. Sin embargo, no garantiza que todos los datos sean enviados en una sola llamada. Puede enviar solo una parte de los datos, especialmente si la cantidad de datos es grande o si la red está congestionada.
  • Uso: Útil cuando se está dispuesto a manejar la lógica para asegurarse de que todos los datos se envíen completamente.
  • Retorno: Devuelve el número de bytes efectivamente enviados. Es posible que necesites llamar a send repetidamente para enviar todos los datos.

sendall

  • Descripción: sendall es una versión más conveniente de send que intenta enviar todos los datos en una sola llamada. Internamente, sendall usa un bucle para llamar a send repetidamente hasta que todos los datos hayan sido enviados o se produzca un error.
  • Uso: Ideal cuando quieres asegurarte de que todos los datos se envíen completamente sin tener que manejar la lógica de reintentos manualmente.
  • Retorno: No devuelve nada si tiene éxito. Si ocurre un error, se lanza una excepción.

  • send es más flexible y te da más control, pero necesitas manejar la lógica para asegurarte de que todos los datos se envíen.
  • sendall es más conveniente cuando quieres asegurarte de que todo el mensaje se envíe sin tener que manejar manualmente los reintentos.

  • Después de las explicaciones vamos a ver como implementar esto en nuestro programa de preguntas y respuestas. Volvemos al código que teníamos. Para centrarnos un poco voy a volver a ponerlo entero:

    quiz_server.py

    import socketserver
    from collections import namedtuple
    from random import choice
    from threading import Event
    import pickle
    
    '''
    Comandos:
    COLOCA TUS COMANDOS AQUÍ
    QUES - comando de pregunta
    UNIR - solicitud de unirse al principio
    1 - Se utiliza para enviar una pregunta al cliente después de una solicitud
    2 - Se utiliza para enviar "Espera el evento listo para empezar" al cliente.
    4 - Se utiliza para enviar una respuesta al servidor después de una pregunta
    7 - Se utiliza para enviar una respuesta "Correcta" o "Incorrecta" después de una respuesta
    '''
    
    NUMERO_DE_JUGADORES = 2
    jugadores = []
    listo_para_empezar = Event()
    contestadas = 0
    esperando_respuestas = Event()
    pregunta_Actual = None
    Pregunta = namedtuple('Pregunta', ['p', 'respuesta'])
    # Pregunta = namedtuple('Pregunta', 'p respuesta')
    juego_Preguntas = [
        Pregunta("¿Como se llama un polígono de tres lados?", "Triángulo"),
        Pregunta("¿Cuál es el quinto planeta del sistema solar?", "Jupiter"),
        Pregunta("¿Cuál es la capital de Francia?", "Paris"),
        Pregunta("¿Quién dijo la frase: 'Solo se que no se nada'?", "Socrates"),
        Pregunta("¿Cuál es el antónimo de rico?", "Pobre")
    ]
    
    Ahora aplicaremos lo hemos visto anteriormente que es la base para crear nuestro servidor. Crearemos el servidor, crearemos una clase que indicará al programa que hacer cuando el cliente se conecte y finalmente haremos un punto para ejecutar el programa. Vamos a ello. Añade el siguiente código al final del anterior.

    quiz_server.py

    #...
    # Creamos el servidor multihilo
    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
        pass
    
    # Creamos la clase que especificará lo que se haga cuando se conecte
    # el cliente.
    class QuizGame(socketserver.BaseRequestHandler):
        pass
    
    # Punto de entrada al programa.
    if __name__ == "__main__":
        HOST, PORT = "localhost", 2065
        with ThreadedTCPServer((HOST, PORT), QuizGame) as server:
            print(f"Servidor ejecutándose en {HOST}:{PORT}")
            server.serve_forever()
    
    
    Dentro de la clase QuizGame vamos a crear un módulo llamado "handle". El modulo "socketserver"  usará estos "handler" para interactuar con las conexiones que se realicen. Cuando un cliente se conecta, se crea una versión de esta clase para manejarlo. 

    Por otra parte también crearemos un módulo llamado "enviar_mensaje" que enviará un determinado mensaje desde el servidor al cliente.

    Actualicemos el código:

    quiz_server.py

    #...
    # Creamos el servidor multihilo
    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
        pass
    
    # Creamos la clase que especificará lo que se haga cuando se conecte
    # el cliente.
    class QuizGame(socketserver.BaseRequestHandler):
        def handle(self):
            pass
    
        def enviar_mensaje(self, mensaje):
            pass
    
    # Punto de entrada al programa.
    if __name__ == "__main__":
        HOST, PORT = "localhost", 2065
        with ThreadedTCPServer((HOST, PORT), QuizGame) as server:
            print(f"Servidor ejecutándose en {HOST}:{PORT}")
            server.serve_forever()
    

    Módulo enviar_mensaje.

    Para enviar los mensajes a los clientes vamos a utilizar el modulo pickle de Python. Pickle nos permite convertir objetos Python a una cadena de bytes y viceversa. Definiremos el método enviar_mensaje dentro de la clase QuizGame para encapsular la lógica de serialización y envio de datos.

    Si utilizamos self.request.sendall(pickle.dumps(mensaje)) convertiremos el mensaje en bytes y lo enviaremos al cliente. Vamos a implementarlo.

    quiz_server.py

    #...
    # Creamos el servidor multihilo
    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
        pass
    
    # Creamos la clase que especificará lo que se haga cuando se conecte
    # el cliente.
    class QuizGame(socketserver.BaseRequestHandler):
        def handle(self):
            pass
    
        def enviar_mensaje(self, mensaje):
            self.request.sendall(pickle.dumps(mensaje))
    
    # Punto de entrada al programa.
    if __name__ == "__main__":
        HOST, PORT = "localhost", 2065
        with ThreadedTCPServer((HOST, PORT), QuizGame) as server:
            print(f"Servidor ejecutándose en {HOST}:{PORT}")
            server.serve_forever()

    Ya nos queda solo el grueso del programa, que codificaremos a través de "handle".  Es importante que mantengas el nombre de este módulo tal como está "handle". Eso es así porque es el módulo predefinido que es invocado automáticamente cuando recibe una conexión del cliente.

    Manejo de Solicitudes del Cliente en el Servidor

    En esta parte del código, estamos viendo cómo el servidor maneja las solicitudes entrantes de los clientes. Vamos a ir añadiendo el código y desglosándolo paso a paso:

    1. Variables globales y Bucle Infinito

    quiz_server.py

    #...
    # Creamos la clase que especificará lo que se haga cuando se conecte
    # el cliente.
    class QuizGame(socketserver.BaseRequestHandler):
        def handle(self):
            global jugadores
            global contestadas
            global pregunta_Actual
    
            while True:
                pass
    
        def enviar_mensaje(self, mensaje):
           # ...
    Como estás variables van a ser usadas por múltiples clientes nos aseguraremos de que sean las mismas para todos y por ello las declararemos como globales.

    while True:

    Este bucle se ejecuta continuamente, lo que significa que el servidor estará siempre listo para recibir y manejar nuevas solicitudes de los clientes.

    2. Recepción de Datos del Cliente

    quiz_server.py

    #...
    # Creamos la clase que especificará lo que se haga cuando se conecte
    # el cliente.
    class QuizGame(socketserver.BaseRequestHandler):
        def handle(self):
            global jugadores
            global contestadas
            global pregunta_Actual
    
            while True:
                try:
                    data = self.request.recv(1024)
                    if not data:
                        break
                    request = pickle.loads(data)
                except EOFError:
                    break          
        def enviar_mensaje(self, mensaje):        # ...
    Aquí, el servidor espera recibir datos del cliente a través del socket de la conexión. `self.request.recv(1024)` recibe hasta 1024 bytes de datos del cliente. Si no se reciben datos o la conexión se cierra, el servidor sale del bucle con `break`. La función `pickle.loads()` se utiliza para deserializar los datos recibidos en un formato que el servidor pueda entender.

    3. Procesamiento de la Solicitud del Cliente

    Una vez que se recibe y deserializa la solicitud del cliente, el servidor procede a procesarla y tomar las acciones correspondientes según el contenido de la solicitud:

    quiz_server.py

    #...
    # Creamos la clase que especificará lo que se haga cuando se conecte
    # el cliente.
    class QuizGame(socketserver.BaseRequestHandler):
        def handle(self):
            global jugadores
            global contestadas
            global pregunta_Actual
    
            while True:
                try:
                    data = self.request.recv(1024)
                    if not data:
                        break
                    request = pickle.loads(data)
                except EOFError:
                    break
    if request[0] == "JOIN": nombre_equipo = request[1] jugadores.append(nombre_equipo) print(f'El Equipo {nombre_equipo} está conectado.')                 if len(jugadores) == NUMERO_DE_JUGADORES:                     # Si el número de jugadores es correcto listo_para_empezar.set()                     # activa el evento #Envia la respuesta de confirmación                 if listo_para_empezar.is_set() == False: mensaje = "[Server]...esperando que los otros jugadores se unan" else: mensaje = "[Server]...empezando" self.enviar_mensaje([2, mensaje])                 # Espera hasta que el evento este listo para comenzar listo_para_empezar.wait() if request[0] == "QUES": if pregunta_Actual == None: pregunta_Actual = choice(juego_Preguntas) esperando_respuestas.clear() self.enviar_mensaje((1, pregunta_Actual.p)) if request[0] == "4":                 # Envía si la respuesta es correcta o no if request[1] == pregunta_Actual.respuesta: self.enviar_mensaje((7, "Correcto")) else: self.enviar_mensaje((7, "Incorrecto")) contestadas += 1 if contestadas == len(jugadores): contestadas = 0 pregunta_Actual = None esperando_respuestas.set()                 esperando_respuestas.wait()          
        def enviar_mensaje(self, mensaje):        # ...


    - Si la solicitud es un comando `"JOIN"`, el servidor registra al equipo que se está uniendo, imprime un mensaje indicando que el equipo se ha conectado correctamente, y si el número de jugadores alcanza el número deseado (2 por defecto), activa un evento para indicar que el juego está listo para comenzar.
    - Si la solicitud es `"QUES"`, el servidor envía una pregunta al cliente.
    - Si la solicitud es `"4"`, el servidor verifica si la respuesta del cliente es correcta o incorrecta, actualiza el contador de respuestas y activa un evento si todas las respuestas han sido recibidas.

    Finalmente, después de enviar una pregunta al cliente o de recibir una respuesta, el servidor espera hasta que todas las respuestas de los jugadores hayan sido recibidas antes de continuar con el siguiente paso del juego.

    Este bloque de código es esencial para la comunicación bidireccional entre el servidor y los clientes en el juego, permitiendo que el servidor reciba y procese las acciones de los clientes de manera eficiente y oportuna.


    Los eventos (`Event`) en Python son una forma sencilla de coordinar la ejecución entre múltiples hilos. En el contexto de este programa, se utilizan dos eventos para sincronizar el flujo del juego entre el servidor y los clientes. Veamos cómo funcionan estos eventos:

    Eventos en Python

    - set(): Señala el evento, permitiendo que todos los hilos que están esperando en `wait()` continúen su ejecución.
    - clear(): Restablece el evento, haciendo que las llamadas a 'wait()' bloqueen nuevamente.
    - wait(): Bloquea el hilo actual hasta que el evento sea señalado.

    Uso de Eventos en el Programa

    1. 'listo_para_empezar'

    Este evento se utiliza para coordinar el inicio del juego. El juego no comenzará hasta que se hayan unido todos los jugadores necesarios.

    - listo_para_empezar.set(): Este método se llama cuando el número de jugadores alcanza el número requerido (`NUMERO_DE_JUGADORES`). Señala el evento, indicando que el juego está listo para empezar.
    - listo_para_empezar.wait(): Los hilos de los clientes llaman a este método para esperar hasta que el evento sea señalado, asegurándose de que todos los jugadores estén listos antes de comenzar.

    2. 'esperando_respuestas'

    Este evento se utiliza para sincronizar las respuestas de los jugadores a una pregunta.

    - esperando_respuestas.clear(): Este método se llama antes de enviar una nueva pregunta, restableciendo el evento y haciendo que las futuras llamadas a `wait()` bloqueen.
    - esperando_respuestas.set(): Este método se llama una vez que todas las respuestas han sido recibidas y procesadas. Señala el evento, permitiendo que los hilos de los clientes continúen.
    - esperando_respuestas.wait(): Los hilos de los clientes llaman a este método para esperar hasta que el evento sea señalado, asegurándose de que todas las respuestas hayan sido recibidas y procesadas antes de proceder.

    Ejemplo con Dos Clientes

    Supongamos que tenemos dos clientes (A y B) conectados al servidor. Aquí hay un flujo simplificado de cómo los eventos funcionan para coordinar el juego:

    1. **Inicio del Juego**:
        - Cliente A y Cliente B envían el comando `"JOIN"`.
        - El servidor agrega a ambos clientes a la lista `jugadores`.
        - Cuando el segundo cliente se une, el servidor llama a `listo_para_empezar.set()`.
        - Ambos clientes están esperando en `listo_para_empezar.wait()`, y ahora pueden continuar porque el evento ha sido señalado.

    2. **Enviar Pregunta**:
        - Un cliente envía el comando `"QUES"` para solicitar una pregunta.
        - El servidor elige una pregunta y la envía al cliente.
        - El servidor llama a `esperando_respuestas.clear()` antes de enviar la pregunta, asegurándose de que las futuras respuestas bloqueen.

    3. **Recibir Respuestas**:
        - Ambos clientes envían sus respuestas con el comando `"4"`.
        - El servidor recibe las respuestas y las procesa.
        - Cuando se reciben todas las respuestas, el servidor llama a `esperando_respuestas.set()`.
        - Los clientes que estaban esperando en `esperando_respuestas.wait()` ahora pueden continuar porque el evento ha sido señalado.

    4. **Repetir el Proceso**:
        - Este proceso se repite para cada pregunta, coordinando el flujo del juego entre el servidor y los clientes.

    Esta estructura asegura que el servidor y los clientes estén sincronizados, manteniendo el flujo del juego ordenado y manejando correctamente la concurrencia en un entorno multijugador.

    Una vez completado el código del servidor vamos con el del cliente.

    CLIENTE.

    quiz_client.py

    import socket
    import pickle
    
    jugando = True
    
    # Crear un socket TCP/IP
    servidor_preguntas = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # Pedir al usuario que ingrese el nombre del equipo
    nombre_equipo = input("¿Cual es el nombre de tu equipo? >>> ")
    
    # Pedir al usuario que ingrese la dirección IP del servidor
    serverIP = input("Introduce la dirección IP (e.j. 127.0.0.1) a la que quieres conectarte o presiona ENTER para usar LOCALHOST: ")
    if serverIP == "":
        serverIP = "127.0.0.1"
    
    # Conectar con el servidor
    print(f"\n¡Bienvenido Equipo {nombre_equipo} al Gran Juego de las Preguntas!")
    servidor_preguntas.connect((serverIP, 2065))
    
    # Enviar un comando al servidor para unirse al juego
    servidor_preguntas.sendall(pickle.dumps(["JOIN", nombre_equipo]))
    
    while jugando:
        # Recibir datos del servidor
        response_data = servidor_preguntas.recv(1024)
        if not response_data:
            break
        response = pickle.loads(response_data)
        
        # Manejar las respuestas del servidor
        if response[0] == 1: # Respuesta de pregunta
            print(response[1])
            respuesta = input('Respuesta : ')
            servidor_preguntas.sendall(pickle.dumps(["4", respuesta]))
        elif response[0] == 2: # Esperando que todos los jugadores estén listos
            print(response[1])
            servidor_preguntas.sendall(pickle.dumps(["QUES", ""]))
        elif response[0] == 7: # Respuesta de correcto o incorrecto
            print(response[1])
            servidor_preguntas.sendall(pickle.dumps(["QUES", ""]))
        elif response[0] == 3: # Esperando que los otros jugadores respondan
            print("...esperandos las respuestas de los otros jugadores")
            servidor_preguntas.sendall(pickle.dumps(["QUES", ""]))
    
    servidor_preguntas.close()
    El código del cliente ya lo hemos visto previamente. Se crea un socket y se conecta al servidor. El funcionamiento posterior dependerá de las respuestas del servidor.

    Vamos a ver si todo funciona. Ejecuta el servidor y dos clientes. Sigue las instrucciones. Deberías ver algo similar a esto:


    imagen final del programa


    Con esto ya tendríamos visto el funcionamiento básico del servidor multihilo y los cliente. Falta bastante para completar el juego porque de momento se repetirían de forma infinita las preguntas, no se mostraría quien es el equipo ganador y bastantes cosas más, pero no es el objeto de este post. Si quieres puedes acabar de desarrollar la lógica del programa ya que lo básico que es la conexión y envío de datos ya está en funcionamiento.

    Puedes encontrar el código de este capítulo en este enlace de este enlace de Github.

    Próximo Post.


    No hay comentarios:

    Publicar un comentario