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
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'])
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") ]
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)
>>> %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')]
¿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:
-
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. -
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?
-
Creamos
ThreadedTCPServer
: Esto define nuestro servidor que puede manejar múltiples conexiones al mismo tiempo. -
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". - 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.
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 desend
que intenta enviar todos los datos en una sola llamada. Internamente,sendall
usa un bucle para llamar asend
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.
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") ]
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()
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()
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()
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): # ...
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:breakrequest = pickle.loads(data)except EOFError:breakdef enviar_mensaje(self, mensaje): # ...
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:breakrequest = pickle.loads(data)except EOFError:breakif 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): # ...
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()
No hay comentarios:
Publicar un comentario