Cliente TCP en Python con sockets: guía práctica

Publicado el: 11/07/2026
Tempo de leitura: 4 minutos
Criação de cliente TCP simples usando Python

Un cliente TCP en Python se conecta a un servidor, envía bytes y recibe una respuesta mediante el módulo estándar socket. Este proyecto ayuda a comprender redes, direcciones IP, puertos, codificación, timeouts, protocolos y manejo de errores.

TCP proporciona una conexión orientada a flujo: entrega los bytes en orden y vuelve a transmitir información perdida. No conserva automáticamente los límites entre mensajes, por lo que la aplicación debe definir cómo termina cada solicitud y respuesta.

Qué necesitas entender antes de comenzar

  • Host: nombre de dominio o dirección IP del servidor.
  • Puerto: número que identifica el servicio.
  • Socket: punto de comunicación entre procesos.
  • Protocolo de aplicación: reglas que definen el formato de los mensajes.

La documentación oficial del módulo socket describe familias de direcciones, tipos de socket y métodos disponibles.

Crear el cliente más pequeño

import socket

HOST = "127.0.0.1"
PORT = 5000

with socket.socket(
    socket.AF_INET,
    socket.SOCK_STREAM,
) as client:
    client.connect((HOST, PORT))
    client.sendall(b"Hello server\n")
    response = client.recv(1024)

print(response.decode("utf-8"))

AF_INET selecciona IPv4 y SOCK_STREAM selecciona TCP. El bloque with cierra el socket al finalizar.

La guía de with en Python explica cómo los context managers administran recursos.

Enviar bytes, no strings

Los sockets transmiten bytes. Convierte texto con encode() y la respuesta con decode():

message = "Hola desde Python\n"
client.sendall(message.encode("utf-8"))

response = client.recv(1024)
text = response.decode("utf-8")

Ambos extremos deben utilizar la misma codificación y el mismo protocolo. La guía de strings en Python explica Unicode, bytes y UTF-8.

Por qué utilizar sendall()

send() puede enviar solamente una parte del buffer y devolver la cantidad transmitida. sendall() continúa hasta enviar todo o producir una excepción:

payload = b"important message\n"
client.sendall(payload)

Para mensajes pequeños y completos, sendall() suele ser la opción más segura.

recv() puede devolver datos parciales

TCP es un flujo. Una llamada a recv(1024) puede devolver menos bytes que el mensaje completo. Debes definir un límite, una longitud o una condición de cierre.

def receive_until_newline(sock):
    chunks = []

    while True:
        chunk = sock.recv(1024)

        if not chunk:
            raise ConnectionError(
                "El servidor cerró la conexión"
            )

        chunks.append(chunk)

        if b"\n" in chunk:
            break

    data = b"".join(chunks)
    line, _, remaining = data.partition(b"\n")
    return line, remaining

Un protocolo basado en líneas funciona bien para ejemplos sencillos, pero no sirve para cualquier contenido binario.

Protocolo con longitud prefijada

Otra estrategia consiste en enviar primero el tamaño:

import struct


def send_message(sock, data):
    header = struct.pack("!I", len(data))
    sock.sendall(header + data)

El receptor lee exactamente cuatro bytes para la longitud y después la cantidad indicada.

def receive_exactly(sock, size):
    chunks = []
    remaining = size

    while remaining:
        chunk = sock.recv(remaining)

        if not chunk:
            raise ConnectionError(
                "Conexión cerrada antes del mensaje"
            )

        chunks.append(chunk)
        remaining -= len(chunk)

    return b"".join(chunks)


def receive_message(sock):
    header = receive_exactly(sock, 4)
    size = struct.unpack("!I", header)[0]
    return receive_exactly(sock, size)

Este diseño permite transmitir texto o datos binarios sin depender de un carácter delimitador.

Configurar un timeout

import socket

with socket.create_connection(
    ("127.0.0.1", 5000),
    timeout=5,
) as client:
    client.settimeout(10)
    client.sendall(b"status\n")
    response = client.recv(1024)

El primer timeout limita el establecimiento de la conexión. El segundo limita operaciones posteriores. Sin límites, un cliente puede quedar esperando indefinidamente.

Utilizar create_connection()

socket.create_connection() simplifica la resolución del host y prueba direcciones compatibles:

import socket

with socket.create_connection(
    ("example.com", 80),
    timeout=5,
) as client:
    request = (
        "HEAD / HTTP/1.1\r\n"
        "Host: example.com\r\n"
        "Connection: close\r\n"
        "\r\n"
    )
    client.sendall(request.encode("ascii"))
    response = client.recv(4096)

print(response.decode("iso-8859-1"))

Este ejemplo ilustra el formato básico de HTTP, pero para aplicaciones reales utiliza una biblioteca como Requests. Implementar correctamente TLS, redirecciones, compresión y parsing HTTP es mucho más complejo.

Manejar errores de red

import socket

try:
    with socket.create_connection(
        ("127.0.0.1", 5000),
        timeout=5,
    ) as client:
        client.sendall(b"ping\n")
        response = client.recv(1024)
except socket.timeout:
    print("La operación superó el tiempo máximo")
except ConnectionRefusedError:
    print("El servidor rechazó la conexión")
except socket.gaierror:
    print("No fue posible resolver el host")
except OSError as error:
    print(f"Error de red: {error}")
else:
    print(response.decode("utf-8"))

La guía de try y except en Python explica captura específica, else y conservación del traceback.

Crear una clase de cliente

import socket


class TcpClient:
    def __init__(self, host, port, timeout=5):
        self.address = (host, port)
        self.timeout = timeout

    def request(self, message):
        data = message.encode("utf-8")

        with socket.create_connection(
            self.address,
            timeout=self.timeout,
        ) as client:
            client.settimeout(self.timeout)
            send_message(client, data)
            response = receive_message(client)

        return response.decode("utf-8")


client = TcpClient("127.0.0.1", 5000)
print(client.request("status"))

La guía de programación orientada a objetos en Python ayuda a organizar estado y comportamiento en clases.

Reintentos con espera

No reintentes inmediatamente de forma infinita. Limita los intentos y aumenta la espera:

import socket
import time


def connect_with_retry(address, attempts=3):
    delay = 1

    for attempt in range(1, attempts + 1):
        try:
            return socket.create_connection(
                address,
                timeout=5,
            )
        except OSError:
            if attempt == attempts:
                raise

            time.sleep(delay)
            delay *= 2

Los reintentos son apropiados para fallos transitorios, no para credenciales inválidas o protocolos incompatibles.

Registrar información útil

import logging

logger = logging.getLogger(__name__)
logger.info(
    "Conectando con %s:%s",
    host,
    port,
)

Registra host, puerto, duración, cantidad de bytes y tipo de error. Evita guardar contraseñas, tokens o contenido privado. La guía de logging en Python presenta niveles, archivos y excepciones.

TCP frente a UDP

TCP ofrece conexión, orden y retransmisión. UDP envía datagramas independientes sin garantizar entrega u orden. TCP resulta adecuado para protocolos donde la integridad y secuencia importan. UDP puede ser útil cuando la latencia es prioritaria y la aplicación tolera pérdidas.

La especificación RFC 9293 define el protocolo TCP moderno y sus conceptos fundamentales.

Seguridad y cifrado

TCP no cifra el tráfico. Para datos sensibles utiliza TLS mediante bibliotecas de alto nivel o el módulo ssl. No inventes tu propio cifrado ni envíes credenciales en texto plano.

También valida tamaños antes de reservar memoria. Un servidor malicioso podría anunciar un mensaje enorme:

MAX_MESSAGE_SIZE = 1_000_000

if size > MAX_MESSAGE_SIZE:
    raise ValueError("Mensaje demasiado grande")

Probar el cliente

Separa las funciones de codificación y framing para probarlas sin una red real. Para integración, inicia un servidor local controlado en un puerto temporal.

La guía de Pytest para principiantes explica fixtures, asserts y organización.

Errores frecuentes

  • Enviar strings sin codificarlas.
  • Suponer que una llamada a recv() devuelve el mensaje completo.
  • No definir un protocolo de delimitación o longitud.
  • Trabajar sin timeouts.
  • Ignorar que recv() devuelve b"" cuando el otro extremo cierra.
  • Reintentar infinitamente.
  • Enviar información sensible sin TLS.
  • No limitar el tamaño de los mensajes.

Conclusión

Un cliente TCP fiable necesita más que connect(), sendall() y recv(). Define un protocolo, procesa datos parciales, utiliza timeouts, captura errores específicos y limita los tamaños. Después añade registros, reintentos controlados, pruebas y TLS cuando la información sea sensible.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Dicas para otimizar scripts Python lentos
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Por qué tu script Python es lento y cómo mejorarlo

    Descubre por qué un script Python es lento y cómo mejorarlo con cProfile, timeit, estructuras correctas, generadores y mejores algoritmos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Leitura de variáveis de ambiente em projetos Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Variables de entorno en Python: guía segura

    Aprende a usar variables de entorno en Python, archivos .env, validación, conversión de tipos, secretos, pruebas y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Introdução ao uso de type hints em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Type hints en Python: escribe código más claro

    Aprende type hints en Python con funciones, colecciones, Optional, TypedDict, Callable, genéricos, Protocol y herramientas de análisis.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Medição de tempo de execução de código Python com timeit
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Timeit en Python: medir el rendimiento correctamente

    Aprende timeit en Python para comparar funciones, repetir benchmarks, separar setup, medir por operación y evitar errores de microoptimización.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Entendendo como o GIL afeta performance em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    GIL de Python: qué es y cómo afecta al rendimiento

    Entiende el GIL de Python, su efecto en threads y CPU, y cuándo usar asyncio, multiprocessing, procesos o bibliotecas que

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Configuração de logs em aplicações Python usando logging
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Logging en Python: guía completa para principiantes

    Aprende logging en Python: niveles, archivos, formatos, excepciones, handlers, rotación, loggers por módulo y buenas prácticas.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026