UP | HOME

Programación de sockets en Python

Table of Contents

Los sockets constan de dos partes: un servidor que está a la espera de conexiones para responder a peticiones y un cliente que solicita conexión con el servidor para realizar solicitudes.

Resumen de la API de sockets para Python

Python ofrece un módulo llamado socket que prevee la funcionalidad para la programación de sockets. Las principales funciones que encontramos en este módulo son:

  • socket() que devuelve un objeto del tipo Socket, y que ofrece, a su vez, los siguientes métodos:

    • .bind(): asocia el socket a una dirección y puerto específicos.
    • .listen(): habilita el socket para escuchar conexiones entrantes.
    • .accepts(): acepta una conexión entrante y devuelve un nuevo socket para manejarla.
    • .connect(): establece una conexión con un socket remoto.
    • .connect_ex(): similar a .connect(), pero retorna un código de error en lugar de lanzar una excepción.
    • .send(): envía datos al socket conectado.
    • .recv(): recibe datos del socket conectado.
    • .close(): cierra el socket y libera los recursos asociados.

    Además de estos métodos, el módulo socket permite la creación de aplicaciones de red, como servidores y clientes, facilitando tanto la comunicación unidireccional como bidireccional entre dispositivos. La API es clave para desarrollar aplicaciones que requieren interacción en tiempo real.

    En los siguientes apartados y ejemplos veremos la utilidad de todas estas funciones/métodos.

Sockets TCP

Existen diferentes tipos de sockets pero nosotros nos vamos a centrar en los sockets TCP. Para crear este tipo de sockets tendrás que usar la función socket() del módulo socket y usando como tipo de socket SOCK_STREAM.

Como curiosidad: si lo que deseas es crear un socket de para usarlo a través del protocolo UDP tendrás que usar como tipo de socket SOCK_DGRAM.

El protocolo TCP es un protocolo confiable y orientado a la conexión, lo que significa que establece una conexión segura entre el emisor y el receptor antes de que comience la transmisión de datos. Esto incluye varias características clave:

  • Control de flujo: TCP regula la cantidad de datos que se pueden enviar sin confirmación, evitando la saturación de la red.
  • Control de errores: utiliza sumas de verificación para detectar errores en los datos enviados, y solicita retransmisiones si es necesario.
  • Segmentación: los datos se dividen en segmentos más pequeños, que se envían de forma independiente y se ensamblan al llegar a su destino.
  • Confirmación (ACK): cada segmento enviado requiere una confirmación del receptor, asegurando que todos los datos sean recibidos correctamente.

Gracias a estas características, TCP es ideal para aplicaciones donde la integridad y el orden de los datos son críticos, como en la transferencia de archivos o las comunicaciones por correo electrónico.

Para entender mejor cómo funciona un socket TCP, te muestro la secuencia de llamadas en la comunicación de un cliente y un servidor:

socket_tcp_flow_dominio_publico.png

Figure 1: Flujo en un socket TCP (imagen obtenida en Wikimedia)

Donde, como ves:

  • El servidor tiene que abrir un socket y escuchar peticiones, lo que hace abriendo un puerto en el que escuchar (bind y listen)
  • El cliente se conecta a un servidor especificando IP/dominio y puerto (connect)
  • A partir de ahí, tanto cliente como servidor "hablan" mediante los send y recv
  • El cliente cuando quiera terminar envía un close al servidor
  • Cuando el servidor cierre la conexión con close los clientes no podrán "hablar" con él

Ejemplo clásico de socket TCP: echo

El típico ejemplo de sockets consta de un servidor que escucha peticiones de un cliente, el cliente envía un texto y el servidor responde con el mismo texto (echo).

El servidor, en Python, escucha en la dirección de localhost o IP 127.0.0.1 en el puerto 65432 (los puertos mayores que 1023 son puertos no privilegiados, y están libres para escribir aplicaciones de este tipo):

import socket

HOST = "127.0.0.1"
PORT = 65432


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, addr = s.accept()
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)


if __name__ == "__main__":
    main()

El cliente, en Python, que se conecta al servidor anterior, envía un texto ("Hello World") y espera a recibir respuesta del servidor, es el siguiente:

import socket

HOST = "127.0.0.1"
PORT = 65432


def main():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((HOST, PORT))

    while True:
        message = input("Introduce un mensaje (o 'exit' para salir): ")
        if message.lower() == "exit":
            break
        client.send(message.encode("utf-8"))
        response = client.recv(1024)
        print(f"Respuesta del servidor: {response.decode('utf-8')}")

    client.close()


if __name__ == "__main__":
    main()

¡¡¡OJO!!! Si lees con detenimiento el código del cliente verás que se usan dos métodos de la clase str que son:

  • encode("utf-8") usado para convertir el string en binario, y
  • decode("utf-8") usado para convertir un valor en binario a string.

Esto es así porque los datos que se envían a través de un socket se tienen que enviar en binario.

Para probar este programa tienes que arrancar el servidor:

$ python server.py

Y ejecutar el cliente:

$ python client.py

Si intentas ejecutar el cliente sin el servidor arrancado obtendrías el error siguiente, y típico, que obtienes cuando se intenta establecer una conexión en un puerto donde no hay servidor escuchando:

$ python client.py
Traceback (most recent call last):
  File "./client.py", line 9, in <module>
    s.connect((HOST, PORT))
ConnectionRefusedError: [Errno 61] Connection refused

Ver el estado del socket

Existe una herramienta de red muy popular y usada por administradores de sistema, tanto en GNU/Linux, como en macOS, como en Windows, que se llama netstat -an, que te permite ver qué programas están escuchando en qué direcciones/puertos:

$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN

Otra herramienta que puedes usar en GNU/Linux y macOS es lsof que te permite ver y lista los ficheros abiertos. Por ejemplo, lo podrías usar así:

$ lsof -i -n
COMMAND     PID   USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
Python    67982 nathan    3u  IPv4 0xecf272      0t0  TCP *:65432 (LISTEN)

Manejar varias conexiones

El servidor echo anterior tiene una limitación importante: no puede manejar varias conexiones. Para resolverlo, vamos a usar hilos, manejando a cada cliente en un hilo separado:

import socket
import threading

HOST = "127.0.0.1"
PORT = 65432


def handle_client(client_socket):
    while True:
        message = client_socket.recv(1024)
        if not message:
            break
        print(f"Recibido: {message.decode('utf-8')}")
        client_socket.send(message)
    client_socket.close()


def main():
    try:
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.bind((HOST, PORT))
        server.listen(5)
        print(f"Servidor escuchando en el puerto {PORT}...")

        while True:
            client_socket, addr = server.accept()
            print(f"Conexión desde {addr}")
            client_handler = threading.Thread(target=handle_client, args=(client_socket,))
            client_handler.start()
    except Exception as e:
        print(e)
    finally:
        server.close()


if __name__ == "__main__":
    main()

Ahora, el cliente, pide al usuario que escriba un texto que enviar al servidor. Cuando el cliente escriba "exit" se cierra la conexión:

import socket

HOST = "127.0.0.1"
PORT = 65432


def main():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((HOST, PORT))

    while True:
        message = input("Introduce un mensaje (o 'exit' para salir): ")
        if message.lower() == "exit":
            break
        client.send(message.encode('utf-8'))
        response = client.recv(1024)
        print(f"Respuesta del servidor: {response.decode('utf-8')}")

    client.close()


if __name__ == "__main__":
    main()

Netcat: probar tu servidor

Para poder probar tu servidor sin necesidad de escribir un cliente puedes usar una herramienta de red llamada Netcat que es una utilidad en línea de comandos que permite abrir conexiones TCP y UDP.

Puedes leer los detalles de esta herramienta desde aquí.

Para lanzar Netcat solo tienes que especificar la IP y el puerto del servidor con el que quieres abrir una conexión. Por ejemplo, para probar el servidor del apartado anterior tienes que usar la IP 127.0.0.1 y el puerto 9002:

$ ncat 127.0.0.1 9002

Para usar Netcat en Windows tendrás que instalar nmap desde aquí.

Author: Román Ginés Martínez Ferrández

Created: 2026-02-09 lun 17:28

Validate