viernes, 24 de mayo de 2024

Redes con Python: Programación de Sockets para la Comunicación. 2.- Enviar y Recibir datos.

En el post anterior hemos conseguido conectar dos sockets, así que en este nos vamos a centrar en como pueden comunicarse, enviando y recibiendo datos. Lo vamos a conseguir utilizando las funciones del modulo socket "send" (enviar) y "recv" (recibir).

Para comprender mejor como funciona esto, en el siguiente gráfico podemos ver las llamadas API del socket y el flujo de datos para TCP:

TCP socket flow


Ya estamos listos para crear una implementación simple de lo anterior. Empezaremos viéndolo desde el punto de vista del servidor y del cliente.

SERVIDOR.

El servidor escuchará las conexiones entrantes y los mensajes que envíe el cliente. Lo primero que haremos será crear un nuevo servidor, que será muy parecido al del post anterior. Lo llamaré socket_server.py. De momento lo único que hará será esperar una conexión y devolver un mensaje de bienvenida al cliente.

socket_server.py

import socket

HOST = '127.0.0.1' # Standard loopback interface address (localhost)
PORT = 8081 # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    # Vincula el socket a una dirección y puerto específicos
    s.bind((HOST, PORT))

    # Escucha conexiones entrantes (el argumento especifica el tamaño máximo de la cola de conexiones)
    s.listen()

    print("Servidor escuchando en el puerto 8081...")

    # Aceptar una conexión
    client_socket, client_address = s.accept()
    with client_socket:
        print(f"Conexión aceptada de {client_address}")
        
        mensaje = "Hola,¡Gracias por conectarte!"
        data = mensaje.encode()
        client_socket.send(data)

Vamos a pararnos un momento a comentar algunas cosas.

1.- Nuestro programa necesita codificar el mensaje para poder ser enviado. 

    data = mensaje.encode()

    La función encode() utiliza el estándar de codificación de caracteres utf-8 para convertir cada carácter, es decir cada letra o símbolo, que son datos de tipo string, en una serie de bytes que son los que se pueden enviar a través de la red. Puedes codificar los datos a enviar utilizando otro tipo de codificación. Por ejemplo:

    data = mensaje.encode("base64")

Es útil para enviar datos que no sean texto.

2.- Una vez que los datos han sido codificados por el programa, pueden ser enviados usando el client_socket.

    client_socket.send(data)

    client_socket es el socket que está emparejado y concectado con el socket del cliente. Ten en cuenta que esto es diferente del server_socket, que se usa para escuchar y aceptar conexiones, pero no para usarlas.

3.- Hemos usado with para que las conexiones se cierren automáticamente y no tener que hacer al final del programa.

client_socket.close()
server_socket.close()


CLIENTE.

Crea un nuevo archivo de Python y llámalo socket_client.py, por ejemplo. Añade el siguiente código.

client_server.py

import socket

HOST = "127.0.0.1"  # El nombre del servidor o IP del mismo
PORT = 8081  # El puerto usado por el servidor

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
    client_socket.connect((HOST, PORT))
    data = client_socket.recv(1024)
    mensaje = data.decode()
    print(mensaje)

El código para recibir los datos es muy parecido al de enviarlos.

1.- Una vez establecida la conexión, los datos se reciben desde el client_socket. 

data = client_socket.recv(1024)

Los datos se reciben o leen desde el client_socket usando client_socket.recv(1024). El valor 1024 es el número máximo de bytes que se deberían leer de una sola vez. Es decir creamos un buffer, si más de 1024 bytes han sido enviados, las llamadas posteriores a recv deberían recibir el resto de los datos.


2.- Los datos recibidos son decodificados e impresos.

    mensaje = data.decode()
    print(mensaje)

Los datos recibidos por el socket son un flujo de bits y deben decodificarse en una cadena usando decode() para que puedan ser impresos por pantalla.

En comparación con el servidor el código del cliente es muy sencillo. Crea un objeto socket, usa .connect() para conectarse al servidor y .recv() para recibir el mensaje.

Vamos a probarlo todo. Abre dos terminales y ejecuta el programa servidor y después el programa cliente. Si todo ha ido bien verás una imagen parecida a esta.

servidor y cliente comunicandose

Un error común que te puedes encontrar es cuando una conexión intenta conectarse a un puerto en el que no esta escuchando ningún socket. Si ejecutas client_socket.py sin haber ejecutado previamente server_socket.py tendrás este bonito error:

$ python3 client_socket.py 
Traceback (most recent call last):
  File "/home/chema/Escritorio/socket/2.- Enviando y Recibiendo datos/client_socket.py", line 7, in <module>
    client_socket.connect((HOST, PORT))
ConnectionRefusedError: [Errno 111] Connection refused
Si el puerto usado es erróneo, el servidor no se está ejecutando o si hay un firewall en el camino que bloquee la conexión te encontrarás con este error.

DESAFIO.

Actualmente en el programa del servidor y del cliente solamente se envían datos en una dirección, del servidor al cliente. ¿Puedes modificar el programa de forma que el cliente responda con un mensaje después de recibir el mensaje "Hola,¡Gracias por conectarte!" y el servidor lo reciba y lo muestre por pantalla?

Pista. Puedes encontrar el código tanto del servidor, como del cliente en Pastebin.

Puedes encontrar el código de este post en este enlace de GITHUB

Próximo capitulo 

Redes con Python: Programación de Sockets para la Comunicación. 3.- Streaming


No hay comentarios:

Publicar un comentario