Introducción a redes — sockets y el modelo cliente-servidor

By Carlos Montiel | Especialista en IA Empresarial
Publicado: 2026-07-28 | Por: Carlos Montiel | Lectura: ~4 minutos

Antes de tener un juego multijugador necesitas dos programas que puedan hablar entre sí de forma confiable. Hoy aprendemos el lenguaje común de toda red: los sockets.

Qué es un socket, en términos simples

En la lección anterior nuestros enemigos ya "pensaban" localmente, dentro de un único proceso. Un juego multijugador rompe esa suposición: ahora hay dos (o más) programas corriendo, posiblemente en computadoras distintas, que necesitan compartir el mismo estado del mundo. La pieza fundamental que permite esto es el socket: un punto de conexión que permite enviar y recibir datos por red, identificado por una dirección IP y un puerto.

Vamos a trabajar con sockets Berkeley (BSD sockets), el estándar POSIX que usan Linux y macOS de forma nativa. En Windows, la API se llama WinSock y es casi idéntica en su uso — la diferencia principal es la inicialización con `WSAStartup` y que hay que enlazar la librería `ws2_32`.

// Encabezados para Linux/macOS (POSIX) #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> // Encabezados equivalentes para Windows (WinSock2) // #include <winsock2.h> // #include <ws2tcpip.h> // #pragma comment(lib, "ws2_32.lib")

TCP vs UDP: la decisión más importante

TCP garantiza entrega ordenada y confiable de datos, pero tiene más latencia. UDP es más rápido pero no garantiza que los paquetes lleguen, ni en qué orden. Para esta lección y la siguiente vamos a usar TCP porque es más simple de razonar mientras aprendes los fundamentos; en un juego de acción en tiempo real con muchos jugadores, la industria suele preferir UDP con su propia capa de confiabilidad, pero eso queda fuera del alcance de un curso introductorio.

Creando un servidor: paso a paso

Un servidor TCP sigue siempre la misma secuencia: crear el socket, asociarlo a una dirección y puerto (`bind`), ponerlo a escuchar conexiones (`listen`), y aceptar clientes (`accept`).

#include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> int main() { int socket_servidor = socket(AF_INET, SOCK_STREAM, 0); if (socket_servidor < 0) { perror("Error al crear el socket"); return 1; } // Permite reutilizar el puerto inmediatamente si el servidor se reinicia int opcion = 1; setsockopt(socket_servidor, SOL_SOCKET, SO_REUSEADDR, &opcion, sizeof(opcion)); sockaddr_in direccion{}; direccion.sin_family = AF_INET; direccion.sin_addr.s_addr = INADDR_ANY; // acepta conexiones en cualquier interfaz direccion.sin_port = htons(7777); // puerto 7777 if (bind(socket_servidor, (sockaddr*)&direccion, sizeof(direccion)) < 0) { perror("Error en bind"); close(socket_servidor); return 1; } if (listen(socket_servidor, 5) < 0) { // 5 = tamaño de la cola de espera perror("Error en listen"); close(socket_servidor); return 1; } printf("Servidor escuchando en el puerto 7777...\n"); sockaddr_in direccion_cliente{}; socklen_t tam_direccion = sizeof(direccion_cliente); int socket_cliente = accept(socket_servidor, (sockaddr*)&direccion_cliente, &tam_direccion); if (socket_cliente < 0) { perror("Error en accept"); close(socket_servidor); return 1; } printf("Cliente conectado!\n"); char buffer[1024] = {0}; ssize_t bytes_leidos = recv(socket_cliente, buffer, sizeof(buffer) - 1, 0); if (bytes_leidos > 0) { printf("Mensaje del cliente: %s\n", buffer); } const char* respuesta = "Hola desde el servidor"; send(socket_cliente, respuesta, strlen(respuesta), 0); close(socket_cliente); close(socket_servidor); return 0; }

`htons` convierte el número de puerto al orden de bytes de red (big-endian), independientemente de si tu computadora usa little-endian o big-endian internamente. Este detalle — que existe precisamente porque distintas arquitecturas de CPU representan números de forma diferente — es un ejemplo perfecto de algo que vale la pena pedirle a tu asistente de IA que te explique con un diagrama de bytes si no te queda claro de inmediato.

Creando un cliente

El cliente es más simple: crea el socket y se conecta directamente a una dirección y puerto conocidos con `connect`.

#include <cstdio> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main() { int socket_cliente = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in direccion_servidor{}; direccion_servidor.sin_family = AF_INET; direccion_servidor.sin_port = htons(7777); inet_pton(AF_INET, "127.0.0.1", &direccion_servidor.sin_addr); if (connect(socket_cliente, (sockaddr*)&direccion_servidor, sizeof(direccion_servidor)) < 0) { perror("Error al conectar"); return 1; } const char* mensaje = "Hola desde el cliente"; send(socket_cliente, mensaje, strlen(mensaje), 0); char buffer[1024] = {0}; ssize_t bytes_leidos = recv(socket_cliente, buffer, sizeof(buffer) - 1, 0); if (bytes_leidos > 0) { printf("Respuesta del servidor: %s\n", buffer); } close(socket_cliente); return 0; }

Compilando y probando ambos programas

g++ servidor.cpp -o servidor g++ cliente.cpp -o cliente # En una terminal: ./servidor # En otra terminal: ./cliente

Deberías ver "Cliente conectado!" y el mensaje intercambiado en ambas terminales. Esto, aunque parezca poco, es la base completa de todo juego multijugador: dos programas independientes, comunicándose por red con un protocolo que tú defines.

Un detalle crítico: TCP no respeta los límites de tus mensajes

Un error muy común de principiante es asumir que un `recv()` recibe exactamente lo que un `send()` envió del otro lado. TCP es un flujo de bytes continuo (un "stream"), no un sistema de mensajes discretos — puede fragmentar un mensaje grande en varias llamadas a `recv()`, o juntar varios mensajes pequeños en una sola. Para un protocolo de juego real necesitas definir tu propio "framing" (por ejemplo, enviar primero el tamaño del mensaje en 4 bytes, y luego el contenido). Vamos a resolver esto formalmente en la próxima lección, donde ya no solo intercambiamos un mensaje de saludo, sino el estado continuo de un juego multijugador real.

En la próxima lección tomamos exactamente estos sockets y construimos un juego multijugador funcional, sincronizando la posición de los jugadores entre cliente y servidor en tiempo real.

Carlos Montiel
Arquitecto de Soluciones IA Empresarial
Especialista en LLMs, Agentes y Orquestación
guatemalia.com/#contacto · info@guatemalia.com

¿Necesitas implementar IA en tu empresa?

Carlos Montiel es arquitecto de soluciones IA empresarial. Implementa LLMs, Agentes, RAG y orquestadores en empresas de Guatemala y Latinoamérica. Contáctalo para una consultoría.

Contactar a Carlos Montiel

info@guatemalia.com