IFTS 14 2025 TS SEIC Protocolo de IoT
-
Clase 12 - 28/10/2025
-
Los estudiantes de mi clase me preguntan cuál es la diferencia entre usar MQTT, sockets TCP o sockets UDP para comunicaciones de datos. En que caso utilizaríamos uno u otro. Para que volúmenes o velocidad de datos serviría uno u otro.
Excelente pregunta, y muy pertinente para una clase de Protocolos de IoT. Vamos a comparar MQTT, sockets TCP y sockets UDP en distintos aspectos clave, y luego daremos una guía clara sobre cuándo conviene usar cada uno en un sistema real de IoT.
🧩 Comparación general
Característica MQTT TCP (socket) UDP (socket) Tipo de protocolo Aplicación sobre TCP Transporte orientado a conexión Transporte sin conexión Fiabilidad Alta (QoS 0, 1, 2) Alta (entrega garantizada) Baja (no garantiza entrega) Orden de entrega Garantizado (por TCP) Garantizado No garantizado Velocidad/latencia Media Media-alta Alta (menor latencia) Overhead (encabezado) Medio-alto (depende de QoS) Medio Bajo Modo de operación Publicador / Suscriptor (Broker) Punto a punto Punto a punto / multidifusión posible Adecuado para transmisión Datos pequeños periódicos o eventos Datos de control o streaming confiable Mensajes pequeños de control rápido Escalabilidad Alta (gracias al broker) Baja (cada socket es 1:1) Alta (sin conexión directa) Seguridad TLS opcional (MQTTs) TLS/SSL puede implementarse Difícil de asegurar sin mecanismos extra
📌 ¿Cuándo usar cada uno?
✅ MQTT
-
Uso típico: sensores IoT, alarmas, control remoto, sistemas distribuidos con múltiples nodos.
-
Volumen de datos: bajo a medio (decenas a cientos de bytes por mensaje).
-
Frecuencia: baja a media (mensajes cada 1s, 10s, 1 min).
-
Ventajas:
-
Ideal para redes inestables o móviles.
-
Tiene soporte de QoS (calidad de servicio).
-
El broker permite comunicación 1:N y desacoplamiento temporal.
-
📍 Ejemplo real: estación meteorológica IoT que publica temperatura y humedad cada 10 segundos a un broker.
✅ Sockets TCP
-
Uso típico: transmisión fiable punto-a-punto (cliente-servidor), comandos remotos, configuración de dispositivos.
-
Volumen de datos: medio a alto.
-
Frecuencia: variable, útil para transmisión continua o sesiones interactivas.
-
Ventajas:
-
Garantiza entrega y orden.
-
Bueno para transferencias grandes o transmisión crítica (firmware, configuraciones, audio digital, etc.).
-
📍 Ejemplo real: un sistema SCADA leyendo datos de 10 dispositivos en tiempo real vía conexión TCP directa.
✅ Sockets UDP
-
Uso típico: mensajes rápidos donde la pérdida es tolerable (telemetría, control en tiempo real, audio/video en vivo).
-
Volumen de datos: pequeño a medio.
-
Frecuencia: alta (mensajes cada pocos ms o subsegundos).
-
Ventajas:
-
Latencia mínima.
-
Ideal para situaciones donde la velocidad es más importante que la fiabilidad.
-
📍 Ejemplo real: control de un robot móvil que envía su posición cada 50 ms a un servidor.
🚦Resumen gráfico (cuándo elegir)
Requisito → Protocolo ↓ Alta fiabilidad Baja latencia Escalabilidad Comunicación 1:N Datos grandes MQTT ✅ ⚠️ ✅ ✅ ⚠️ TCP socket ✅ ⚠️ ❌ ❌ ✅ UDP socket ❌ ✅ ✅ ✅ ⚠️
🎓 Recomendación didáctica
En clase podés presentar tres mini proyectos simulados:
-
MQTT: estación meteorológica IoT.
-
TCP socket: monitor remoto de alarmas con comando ACK.
-
UDP socket: robot autónomo que reporta posición en tiempo real.
-
-
Modelo Cliente-Servidor en TCP/IP :
#use "dcrtcp.lib"
#define HOST_PORT 1000
#define PORT_ACTUACIONES 2000
tcp_Socket Socket_ACIC, Socket_ACIC_sec, Socket_Actuac;
int handle_acicomp, handle_acicomp_sec;
longword resolved_ip_acicomp, resolved_ip_acicomp_sec;
typedef struct {
int state;
tcp_Socket* socket;
unsigned long open_timeout;
char buffer[512];
} VsState;
typedef struct {
int port;
long dest_ip;
int dest_port;
unsigned long open_timeout;
} VsInfo;
VsInfo vs_info = {
.port = 0,
.dest_ip = 0,
.dest_port = HOST_PORT,
.open_timeout = 5000L
};
VsInfo vs4_info = {
.port = PORT_ACTUACIONES,
.dest_ip = 0,
.dest_port = 2000,
.open_timeout = 5000L
};
VsState vs_state = {
.state = 0,
.socket = &Socket_ACIC,
.open_timeout = 5000L
};
VsState vs_state_sec = {
.state = 0,
.socket = &Socket_ACIC_sec,
.open_timeout = 5000L
};
VsState vs4_state = {
.state = 0,
.socket = &Socket_Actuac,
.open_timeout = 5000L
};
void vs_init(VsState* state) {
state->state = 0;
}
void vs_handler(VsState* state) {
switch (state->state) {
case 0: // VS_INIT
if (tcp_open(state->socket, 0, resolved_ip_acicomp, HOST_PORT, NULL)) {
state->state = 2; // VS_OPENING
}
break;
case 2: // VS_OPENING
if (sock_established(state->socket)) {
state->state = 3; // VS_OPEN
}
break;
case 3: // VS_OPEN
if (sock_dataready(state->socket)) {
sock_gets(state->socket, state->buffer, sizeof(state->buffer));
// Aquí se procesa el mensaje recibido
}
break;
default:
state->state = 0;
break;
}
}
void main() {
sock_init();
vs_init(&vs_state);
vs_init(&vs_state_sec);
vs_init(&vs4_state);
while (1) {
tcp_tick();
vs_handler(&vs_state);
vs_handler(&vs_state_sec);
vs_handler(&vs4_state);
}
}
¿Qué hace este resumen?
-
Abstrae el manejo de sockets TCP, tanto para conexión saliente (cliente) como para servidor (modo pasivo).
-
Incluye:
-
Estructura de estado (
VsState
) -
Estructura de configuración (
VsInfo
) -
Inicialización y manejo de sockets
-
Loop principal donde se manejan las conexiones
-
-
-
A continuación se detallan ambas funciones,
vs_init()
yvs_handler()
, siguiendo el modelo de máquina de estados secuenciales basado enswitch-case
.
🧩 1. Función
vs_init()
— Inicialización de la conexión TCPvoid vs_init(VsState* state) { state->state = VS_INIT; // Estado inicial: sin conexión state->laststate = -1; // No hay estado anterior aún state->offset = 0; // Reinicia el índice del buffer state->last_character = 0; // Tiempo del último byte recibido state->open_timeout = vs_info.open_timeout; // Timeout de apertura }
✅ ¿Qué hace?
-
Establece el estado inicial (
VS_INIT
) de la conexión TCP. -
Limpia la memoria relacionada con el manejo de datos y estados anteriores.
-
Se asegura de que el tiempo de espera esté configurado.
⚙️ 2. Función
vs_handler()
— Máquina de estados para el manejo del socket TCPEsta función es el núcleo que controla el comportamiento del socket según su estado actual. Aquí te muestro una versión canónica simplificada y bien comentada basada en lo encontrado en tu archivo:
void vs_handler(VsState* estado) { switch (estado->state) { case VS_INIT: // Intenta abrir el socket si es cliente if (tcp_open(estado->socket, 0, vs_info.dest_ip, vs_info.dest_port, NULL)) { estado->state = VS_OPENING; } break; case VS_OPENING: // Espera a que se establezca la conexión if (sock_established(estado->socket)) { estado->state = VS_OPEN; } else if (sock_timedout(estado->socket)) { sock_close(estado->socket); // Reinicia intento estado->state = VS_INIT; } break; case VS_OPEN: // Comunicación activa, revisar si hay datos if (sock_dataready(estado->socket)) { int len = sock_fastread(estado->socket, estado->buffer, sizeof(estado->buffer)); if (len > 0) { // Procesar buffer recibido // Por ejemplo: analizar comandos, responder, etc. } } // Verifica si la conexión fue cerrada remotamente if (!sock_alive(estado->socket)) { sock_close(estado->socket); estado->state = VS_INIT; } break; case VS_FORCEDCLOSE: // Cierre forzado de la conexión sock_abort(estado->socket); estado->state = VS_INIT; break; default: // En caso de estado inesperado, reiniciar estado->state = VS_INIT; break; } }
📋 Estados implementados
Estado Descripción VS_INIT
Socket inactivo. Intenta abrir uno nuevo (modo cliente). VS_OPENING
Esperando establecimiento de conexión TCP. VS_OPEN
Conexión activa: recepción/envío de datos. VS_FORCEDCLOSE
Cierre de emergencia. default
Estado no definido, se reinicia.
🧠 ¿Qué podés extender?
Podés agregar otros estados como:
-
VS_WAIT_ACK
: espera de una respuesta tipo ACK. -
VS_LISTEN
: si fuese servidor TCP. -
VS_OPEN_RECV
: para separar recepción de transmisión.
-
-
A continuación te presento una versión completa y modular de la función
vs_handler()
que incluye tanto:-
Conexiones activas (modo cliente TCP),
-
Conexiones pasivas (modo servidor TCP),
-
Manejo de timeout por inactividad (inter-character delay),
-
Estados bien definidos con
switch-case
.
✅ Definiciones necesarias
Primero definimos las estructuras y constantes asociadas (esto ya está en tu código original, pero lo resumo aquí por claridad):
#define VS_INIT 0#define VS_LISTEN 1 #define VS_OPENING 2 #define VS_OPEN 3 #define VS_WAITCLOSE 9 #define VS_FORCEDCLOSE 10 #define VS_WELLCLOSED 11 #define VS_MODEOFF 0 #define VS_MODEACTIVE 1 #define VS_MODEPASSIVE 2 typedef struct { int state; int laststate; tcp_Socket* socket; int offset; unsigned long last_character; char buffer[512]; unsigned long open_timeout; } VsState; typedef struct { int port; int timeout; long baud; int mode; longword dest_ip; int dest_port; int binary; unsigned long open_timeout; } VsInfo;
🧠
vs_handler()
con cliente, servidor y timeout por inactividadvoid vs_handler(VsState* estado, VsInfo* info) {int len; switch (estado->state) { case VS_INIT: if (info->mode == VS_MODEACTIVE) { // Conexión saliente (cliente TCP) if (tcp_open(estado->socket, 0, info->dest_ip, info->dest_port, NULL)) { estado->state = VS_OPENING; estado->last_character = MS_TIMER; } } else if (info->mode == VS_MODEPASSIVE) { // Conexión entrante (servidor TCP) tcp_listen(estado->socket, info->port, 0, NULL, 0); estado->state = VS_LISTEN; } break; case VS_LISTEN: if (sock_established(estado->socket)) { estado->state = VS_OPEN; estado->offset = 0; estado->last_character = MS_TIMER; } break; case VS_OPENING: if (sock_established(estado->socket)) { estado->state = VS_OPEN; estado->offset = 0; estado->last_character = MS_TIMER; } else if (MS_TIMER - estado->last_character > info->open_timeout) { sock_close(estado->socket); estado->state = VS_INIT; } break; case VS_OPEN: if (sock_dataready(estado->socket)) { len = sock_fastread(estado->socket, estado->buffer + estado->offset, sizeof(estado->buffer) - estado->offset); estado->offset += len; estado->last_character = MS_TIMER; // Procesar datos si es necesario // ... } // Timeout por inactividad if (info->timeout > 0 && estado->offset > 0 && (MS_TIMER - estado->last_character > info->timeout)) { estado->buffer[estado->offset] = '\0'; // null-terminate para debug // Procesar mensaje completo estado->offset = 0; } if (!sock_alive(estado->socket)) { estado->state = VS_INIT; sock_close(estado->socket); } break; case VS_FORCEDCLOSE: sock_abort(estado->socket); estado->state = VS_INIT; break; case VS_WAITCLOSE: if (!sock_alive(estado->socket)) { estado->state = VS_WELLCLOSED; } break; case VS_WELLCLOSED: estado->state = VS_INIT; break; default: estado->state = VS_INIT; break; } }
📌 ¿Qué hace esta versión?
-
Soporta modo cliente (
VS_MODEACTIVE
) intentando conectar a un servidor remoto. -
Soporta modo servidor (
VS_MODEPASSIVE
) escuchando en un puerto definido. -
Implementa un timeout por inactividad, útil para manejar tramas por lotes o delimitadas por pausas.
-
Es fácilmente reutilizable para múltiples sockets con distintas funciones (PTs, ACICOMP, etc).
🧪 ¿Cómo se usa?
VsState socket_pt_state;tcp_Socket socket_pt; VsInfo info_pt = { .port = 2500, .timeout = 500, // en milisegundos .baud = 9600, .mode = VS_MODEPASSIVE, // servidor TCP .dest_ip = 0, .dest_port = 0, .binary = 1, .open_timeout = 5000L }; void main() { sock_init(); socket_pt_state.socket = &socket_pt; socket_pt_state.state = VS_INIT; while (1) { tcp_tick(); vs_handler(&socket_pt_state, &info_pt); // otros handlers... } }
-
-
A continuación te presento una versión completa y estructurada de
vs_init()
yvs_handler()
basada en máquina de estados secuencial, que incluye:-
Modo activo (cliente TCP).
-
Modo pasivo (servidor TCP).
-
Detección de timeout por inactividad.
-
Cierre limpio o forzado de la conexión.
-
Completamente adaptable a Rabbit/RCM (Dynamic C).
✅ Estructuras de configuración
Primero, aseguramos las estructuras necesarias (ya están en tu código original):
#define VS_INIT 0#define VS_LISTEN 1 #define VS_OPENING 2 #define VS_OPEN 3 #define VS_TIMEOUT 4 #define VS_FORCEDCLOSE 5 #define VS_MODEOFF 0 #define VS_MODEACTIVE 1 #define VS_MODEPASSIVE 2 #define INACTIVITY_TIMEOUT_MS 30000UL // Timeout de inactividad (ej. 30 seg) typedef struct { int state; int laststate; tcp_Socket* socket; int offset; unsigned long last_character; // timestamp del último dato recibido char buffer[512]; unsigned long open_timeout; // tiempo de espera para abrir socket } VsState; typedef struct { int port; long dest_ip; int dest_port; int mode; unsigned long open_timeout; } VsInfo;
🧩 Función
vs_init()
void vs_init(VsState* state, VsInfo* info) {state->state = VS_INIT; state->laststate = -1; state->offset = 0; state->last_character = 0; state->open_timeout = info->open_timeout; memset(state->buffer, 0, sizeof(state->buffer)); }
⚙️ Función
vs_handler()
void vs_handler(VsState* state, VsInfo* info) {switch (state->state) { case VS_INIT: if (info->mode == VS_MODEACTIVE) { // Cliente: intenta conectar al servidor remoto if (tcp_open(state->socket, 0, info->dest_ip, info->dest_port, NULL)) { state->state = VS_OPENING; } } else if (info->mode == VS_MODEPASSIVE) { // Servidor: espera conexiones entrantes if (tcp_listen(state->socket, info->port, 0, NULL)) { state->state = VS_LISTEN; } } break; case VS_LISTEN: // Esperando conexión entrante if (sock_established(state->socket)) { state->last_character = MS_TIMER; state->state = VS_OPEN; } else if (sock_timedout(state->socket)) { sock_abort(state->socket); state->state = VS_INIT; } break; case VS_OPENING: if (sock_established(state->socket)) { state->last_character = MS_TIMER; state->state = VS_OPEN; } else if (sock_timedout(state->socket)) { sock_abort(state->socket); state->state = VS_INIT; } break; case VS_OPEN: if (sock_dataready(state->socket)) { int len = sock_fastread(state->socket, state->buffer, sizeof(state->buffer)); if (len > 0) { state->last_character = MS_TIMER; // 👉 Aquí se puede procesar `state->buffer` } } // Verifica inactividad if ((long)(MS_TIMER - state->last_character) > INACTIVITY_TIMEOUT_MS) { sock_close(state->socket); state->state = VS_TIMEOUT; } // Verifica cierre remoto if (!sock_alive(state->socket)) { sock_close(state->socket); state->state = VS_INIT; } break; case VS_TIMEOUT: // Timeout por inactividad sock_abort(state->socket); state->state = VS_INIT; break; case VS_FORCEDCLOSE: // Cierre forzado sock_abort(state->socket); state->state = VS_INIT; break; default: state->state = VS_INIT; break; } }
🌀 Ejemplo de uso en
main()
tcp_Socket socketTCP;VsState vs_state; VsInfo vs_info = { .port = 2000, .dest_ip = resolve("192.168.1.100"), // o usar IP fija .dest_port = 2000, .mode = VS_MODEPASSIVE, // o VS_MODEACTIVE .open_timeout = 5000L }; void main() { sock_init(); vs_init(&vs_state, &vs_info); while (1) { tcp_tick(); vs_handler(&vs_state, &vs_info); } }
🧠 ¿Qué podés adaptar?
-
VS_MODEACTIVE
: para conexiones salientes (como cliente). -
VS_MODEPASSIVE
: para escuchar conexiones entrantes (como servidor). -
Podés agregar:
-
VS_WAIT_ACK
si usás protocolos de ACK. -
Buffer circular para múltiples clientes.
-
Lógica para reintento automático.
-
-
-
Perfecto. Aquí tenemos dos ejemplos realistas y bien diferenciados de uso de sockets UDP en sistemas IoT, similares a los de TCP y MQTT que discutimos antes.
🧪 Ejemplo 1: Control de robot en tiempo real (UDP)
📝 Descripción:
Un robot móvil autónomo envía cada 50 ms su posición (x, y, θ) y velocidad al servidor de monitoreo mediante UDP. El servidor puede también enviar comandos de movimiento sin esperar respuesta.
🎯 Por qué UDP:
-
Baja latencia.
-
Pérdidas tolerables (el siguiente paquete traerá datos actualizados).
-
No se necesita conexión establecida.
🧩 Trama UDP:
-
12 bytes → 3 flotantes (
x
,y
,θ
) -
4 bytes → comando opcional (por ejemplo:
STOP
,FWD
,LEFT
,RIGHT
)
🧠 Estructura (flujo):
┌────────────┐ ┌────────────────┐ │ Robot IoT │ -- UDP Datagram → │ Servidor Base │ │ (cliente) │ ← UDP Datagram -- │ (monitor/control) │ └────────────┘ └────────────────┘
📄 Código (pseudocódigo del robot, cliente UDP):
udp_Socket sock; sock_init(); udp_open(&sock, 0, "192.168.1.100", 6000, NULL); while (1) { float x = get_x_position(); float y = get_y_position(); float theta = get_orientation(); char buffer[12]; memcpy(&buffer[0], &x, 4); memcpy(&buffer[4], &y, 4); memcpy(&buffer[8], &theta, 4); udp_send(&sock, buffer, 12); waitms(50); }
🧪 Ejemplo 2: Red de nodos de sensores de vibración (UDP broadcast)
📝 Descripción:
En una planta industrial, 20 nodos de sensores vibracionales miden cada 5 s y transmiten sus datos por broadcast UDP en la red local. Un servidor escucha todos los datagramas y almacena el historial.
🎯 Por qué UDP:
-
No es necesario establecer conexión con cada nodo.
-
UDP broadcast permite que todos los nodos transmitan a un solo servidor pasivo.
-
Se tolera pérdida ocasional (el siguiente dato vendrá pronto).
🧩 Trama UDP:
-
2 bytes → ID del nodo
-
4 bytes → valor RMS de vibración (float)
-
4 bytes → timestamp
🧠 Estructura:
[ Nodo 1 ] [ Nodo 2 ] [ Nodo 3 ] ┌──────────────┐ [ Nodo 4 ] --UDP→ │ Servidor de │ [ Nodo 5 ] │ recepción │ [ ... ] └──────────────┘
📄 Código del nodo (emisor UDP en broadcast):
udp_Socket sock; sock_init(); udp_open(&sock, 0, 0xFFFFFFFF, 5000, NULL); // 0xFFFFFFFF → broadcast IP while (1) { unsigned int node_id = 0x01; // Nodo 1 float rms = measure_vibration(); unsigned long timestamp = unix_time(); char buffer[10]; memcpy(&buffer[0], &node_id, 2); memcpy(&buffer[2], &rms, 4); memcpy(&buffer[6], ×tamp, 4); udp_send(&sock, buffer, 10); waitms(5000); }
🎓 ¿Qué podés enseñar con estos ejemplos?
-
Que UDP es ideal para aplicaciones de tiempo real, donde la pérdida de paquetes es aceptable.
-
Cómo usar broadcast/multicast para recibir múltiples nodos sin conexiones múltiples.
-
La importancia de definir tramas pequeñas y claras.
-
Cómo construir un sistema descentralizado y eficiente.
-
-
A continuación presentamos un ejemplo completo y educativo de un servidor que recibe datos por UDP. Lo vamos a hacer en dos versiones:
🧪 Ejemplo: Servidor UDP que recibe datos de sensores
📝 Descripción:
Una red de sensores transmite paquetes por UDP con la siguiente información:
-
2 bytes: ID del sensor
-
4 bytes: valor (ej. temperatura, vibración, presión)
-
4 bytes: timestamp
Este servidor UDP escucha en el puerto 5000, decodifica los datos, y los muestra en pantalla.
📄 Versión en C (compatible con Rabbit u otros embebidos)
#use "dcrtcp.lib" udp_Socket sock; char buffer[10]; // 2 + 4 + 4 bytes void main() { word remote_port; longword remote_ip; sock_init(); udp_open(&sock, 5000, 0, 0, NULL); // Puerto local 5000, acepta cualquier IP remota while (1) { tcp_tick(); // importante para el stack if (udp_recv(&sock, buffer, sizeof(buffer))) { unsigned int sensor_id = (buffer[0] << 8) | buffer[1]; float valor; unsigned long timestamp; memcpy(&valor, &buffer[2], 4); memcpy(×tamp, &buffer[6], 4); remote_ip = udp_recvip(&sock); remote_port = udp_recvport(&sock); printf("Sensor ID: %u | Valor: %.2f | Timestamp: %lu | Desde %lu.%lu.%lu.%lu:%u\n", sensor_id, valor, timestamp, (remote_ip>>24)&0xFF, (remote_ip>>16)&0xFF, (remote_ip>>8)&0xFF, remote_ip&0xFF, remote_port); } waitfor(50); // evitar CPU 100% } }
🐍 Versión equivalente en Python (servidor UDP)
import socket import struct UDP_IP = "0.0.0.0" UDP_PORT = 5000 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((UDP_IP, UDP_PORT)) print(f"Esperando datos UDP en {UDP_IP}:{UDP_PORT}...") while True: data, addr = sock.recvfrom(1024) if len(data) == 10: sensor_id = int.from_bytes(data[0:2], 'big') valor = struct.unpack('f', data[2:6])[0] timestamp = int.from_bytes(data[6:10], 'big') print(f"Desde {addr[0]}:{addr[1]} → Sensor ID={sensor_id}, Valor={valor:.2f}, Timestamp={timestamp}") else: print(f"Trama inesperada de {addr}")
🧠 Puntos didácticos para destacar
-
UDP no garantiza orden ni entrega: por eso los mensajes deben ser autocontenidos y compactos.
-
El servidor no necesita crear múltiples sockets: un único socket UDP escucha a todos los sensores.
-
Permite simular sensores reales y enseñar parsing binario (como con
struct.unpack
omemcpy
en C).
-
-
¡Excelente enfoque pedagógico! Para responder a tus estudiantes con claridad, es ideal organizar la explicación en función de:
-
¿Qué es? – Descripción breve.
-
¿Por qué usarlo? – Ventajas clave.
-
¿Cuándo usarlo? – Casos de uso concretos.
Te lo resumo en un cuadro comparativo, y luego te doy una guía para explicar en clase con ejemplos concretos.
📊 Tabla comparativa: MQTT, CoAP, UDP, TCP
Protocolo ¿Qué es? ¿Por qué usarlo? ¿Cuándo usarlo? MQTT Protocolo de mensajería basado en publish/subscribe sobre TCP - Comunicación asincrónica- Bajo consumo- QoS configurable- Escalable - Sensores IoT que reportan datos periódicos- Estaciones meteorológicas- Alarmas, notificaciones- Comunicación 1:N CoAP Protocolo liviano RESTful sobre UDP, tipo HTTP para IoT - Más liviano que HTTP- Compatible con UDP- Soporta confirmaciones (ACK)- Bajo consumo - Dispositivos embebidos- Redes con baja tasa de datos- Sensores controlables remotamente (GET/PUT/POST) UDP Protocolo de transporte no orientado a conexión - Latencia mínima- Sin handshake- Bajo overhead - Streaming en tiempo real- Telemetría rápida- Control de robots móviles TCP Protocolo de transporte confiable orientado a conexión - Transmisión confiable- Orden garantizado- Maneja paquetes grandes automáticamente - Comunicación cliente-servidor- Transferencia de archivos- Configuración remota crítica
📚 Cómo explicarlo en clase
✅ MQTT – ¿Por qué usarlo?
-
Para enviar mensajes cortos, con eficiencia, sin mantener conexión permanente activa.
-
Permite escalar de forma sencilla (número ilimitado de dispositivos).
-
Útil cuando no sabés quién va a consumir los datos, pero querés que estén disponibles.
📌 Ejemplo:
Un sensor de humedad publica cada 10 s sus datos al broker MQTT. Un panel en el celular se suscribe para recibirlo.
✅ CoAP – ¿Por qué usarlo?
-
Ideal para dispositivos con muy poca memoria y conectividad limitada.
-
Es como un “mini HTTP” con comandos tipo
GET /sensor/1
. -
Puede usar confirmaciones para asegurar entrega, aunque se base en UDP.
📌 Ejemplo:
Un dispositivo con batería responde a
GET /temp
con la temperatura. Otro sistema hacePUT /led
para encender un LED.
✅ UDP – ¿Por qué usarlo?
-
Para casos donde la velocidad es más importante que la confiabilidad.
-
No hay sobrecarga de control de conexión.
-
Ideal si perdés 1 de cada 100 paquetes, pero te llegan los demás rápido.
📌 Ejemplo:
Un robot autónomo transmite su posición cada 50 ms. No importa si un paquete se pierde; el siguiente lo actualiza.
✅ TCP – ¿Por qué usarlo?
-
Cuando no podés permitir la pérdida de datos.
-
Para transmitir grandes volúmenes sin tener que fragmentarlos manualmente.
-
Ideal para sesiones interactivas o configuraciones.
📌 Ejemplo:
Un sistema SCADA se conecta a un dispositivo remoto y le pide 100 valores de sensores con confiabilidad total.
🎯 Reglas rápidas para responder
❓ ¿Qué tan críticos son los datos?
✅ Usa TCP o MQTT con QoS❓ ¿Necesitás bajo consumo y tamaño mínimo?
✅ Usa CoAP o MQTT❓ ¿La latencia es más importante que la confiabilidad?
✅ Usa UDP❓ ¿Querés flexibilidad con múltiples clientes y dispositivos?
✅ Usa MQTT (broker pub/sub)
-
-
El tamaño máximo del buffer de datos transmitido por MQTT depende de:
-
La versión del protocolo MQTT.
-
La implementación específica del cliente/broker (p. ej., Eclipse Paho, Mosquitto, EMQX).
-
Las configuraciones internas de límite de mensaje.
📦 Límite según protocolo MQTT
Versión MQTT Tamaño máximo teórico de un mensaje MQTT MQTT 3.1 256 MB (teóricamente) MQTT 3.1.1 256 MB (longitud codificada en 4 bytes) MQTT 5.0 268,435,455 bytes (≈ 256 MB) Esto es porque el campo "Remaining Length" del encabezado MQTT está codificado con un esquema de 4 bytes que permite un valor máximo de
268,435,455
bytes.
⚙️ Límite en implementaciones comunes
Broker / Cliente Límite por defecto Configurable Mosquitto 256 KB ( max_packet_size
)✅ EMQX 1 MB ( max_packet_size
)✅ HiveMQ 256 MB ✅ Eclipse Paho C/Python 256 KB a 1 MB ✅ En la mayoría de los casos, los límite por defecto son mucho menores que el máximo teórico para proteger el rendimiento y evitar abusos.
🚨 Recomendación práctica para IoT
Aunque MQTT soporte grandes cargas:
-
No es buena práctica enviar grandes volúmenes de datos por MQTT.
-
Para IoT se recomienda que los mensajes sean pequeños: típicamente entre 20 y 512 bytes.
-
Si necesitás enviar archivos grandes (firmware, imágenes, logs):
-
Usá fragmentación en varias publicaciones MQTT.
-
O bien, usá HTTP, FTP, o MQTT 5 con chunking manual.
-
📌 Conclusión
El tamaño máximo de mensaje en MQTT es ~256 MB, pero en la práctica está limitado a 256 KB - 1 MB según el broker o cliente.
Perfecto. Vamos paso a paso:
🛠️ 1. Configurar Mosquitto para permitir mensajes MQTT más grandes
🔍 Por defecto, Mosquitto (v2.x o v1.x) limita el tamaño de un mensaje MQTT a 256 KB (262144 bytes).
✅ Para aumentar este límite:
🔧 Paso A: Editar archivo de configuración
Ubicación típica:
-
En Linux:
/etc/mosquitto/mosquitto.conf
-
En Windows:
mosquitto.conf
en la carpeta donde instalaste Mosquitto
🔧 Paso B: Agregar o modificar estas líneas:
# Permitir mensajes de hasta 1 MB max_packet_size 1048576
También podés usar hasta 256 MB (≈ 268435455 bytes), pero no se recomienda para IoT.
🔄 Paso C: Reiniciar Mosquitto
sudo systemctl restart mosquitto
o
mosquitto -c mosquitto.conf
📦 2. Enviar archivos grandes por MQTT fragmentados (chunked)
🎯 Objetivo:
Enviar archivos o datos largos en fragmentos de 512 bytes por MQTT, y reconstruirlos en el receptor.
🐍 Ejemplo en Python (usando
paho-mqtt
)🧑💻 Emisor (publicador que parte un archivo)
import paho.mqtt.client as mqtt broker = "localhost" topic = "archivo/chunk" chunk_size = 512 # bytes client = mqtt.Client() client.connect(broker) # Abrir archivo binario with open("archivo.txt", "rb") as f: data = f.read() # Enviar en fragmentos total_chunks = (len(data) + chunk_size - 1) // chunk_size for i in range(total_chunks): start = i * chunk_size end = start + chunk_size chunk = data[start:end] # Estructura del mensaje: índice + total + datos payload = f"{i}/{total_chunks}|".encode() + chunk client.publish(topic, payload) print("Archivo enviado en fragmentos.") client.disconnect()
🧑💻 Receptor (suscriptor que reconstruye el archivo)
import paho.mqtt.client as mqtt broker = "localhost" topic = "archivo/chunk" received_chunks = {} expected_chunks = None def on_message(client, userdata, msg): global expected_chunks # Separar encabezado del fragmento header, chunk = msg.payload.split(b'|', 1) idx, total = map(int, header.decode().split("/")) received_chunks[idx] = chunk expected_chunks = total print(f"Recibido fragmento {idx+1}/{total}") if expected_chunks is not None and len(received_chunks) == expected_chunks: with open("archivo_reconstruido.txt", "wb") as f: for i in range(expected_chunks): f.write(received_chunks[i]) print("Archivo reconstruido con éxito.") client = mqtt.Client() client.on_message = on_message client.connect(broker) client.subscribe(topic) client.loop_forever()
🔐 Consejo extra:
Para mayor confiabilidad podés:
-
Usar
QoS=1
para garantizar entrega de cada fragmento. -
Incluir CRC o checksum por fragmento si necesitás validación.
-
-
A diferencia de MQTT (que usa TCP), CoAP trabaja principalmente sobre UDP, lo que influye directamente en su tamaño máximo de mensaje.
📦 ¿Cuál es el tamaño máximo de datos transmitidos en CoAP?
📌 Depende de:
-
La capa de transporte: típicamente UDP.
-
MTU de red: el tamaño máximo de una trama IP.
-
Fragmentación y bloques (Block-wise transfer).
-
Implementación y configuración del stack CoAP.
🧮 Tamaño máximo típico de mensaje CoAP (UDP)
Entorno Tamaño máximo práctico del mensaje CoAP LAN (Ethernet) ≈ 1152 bytes IPv6 (con 6LoWPAN) ≈ 102 bytes o menos LoRa/LPWAN 20–51 bytes General sobre UDP 576–1500 bytes según MTU ⚠️ Como CoAP se basa en UDP, el tamaño de datagrama seguro (sin fragmentación IP) es 512–1024 bytes típicamente.
📦 ¿Y si necesito enviar más datos?
✔️ CoAP admite fragmentación propia mediante el mecanismo de:
✅ Block-wise transfer (RFC 7959)
-
Divide los datos en bloques (usualmente de 64, 128, 256, … hasta 1024 bytes).
-
Cada bloque es una petición/respuesta CoAP independiente con opción
Block1
oBlock2
. -
El receptor reensambla los bloques.
🔧 Tamaño configurable
En muchas implementaciones (como
libcoap
,Erbium
oContiki CoAP
):#define COAP_MAX_CHUNK_SIZE 1024 // o menor
🧠 Comparación CoAP vs MQTT en cuanto a tamaño
Protocolo Transporte Tamaño típico sin fragmentar Soporte para fragmentación MQTT TCP 256 KB (configurable) No necesita (stream TCP) CoAP UDP 512–1152 bytes ✅ con Block-wise transfer
📌 Conclusión
El tamaño máximo práctico de datos transmitidos en CoAP sin fragmentación es entre 512 y 1024 bytes. Para más, se usa Block-wise transfer (fragmentación por bloques).
Perfecto. Vamos a responder cuál es el tamaño máximo de los datos transmitidos (buffer útil) por TCP y UDP, con explicaciones claras y prácticas para entornos como IoT, redes locales o internet.
📦 UDP – Tamaño máximo del buffer de datos
UDP trabaja con datagramas independientes, y su límite depende del tamaño del datagrama IP.
🧮 Límite teórico:
-
UDP payload máximo teórico:
65507 bytes
-
65535 (máximo total IP) − 20 (IP header) − 8 (UDP header)
-
🧰 En la práctica:
Entorno Límite seguro típico (sin fragmentar) Red Ethernet (MTU 1500) 508–1472 bytes IPv6 sobre 6LoWPAN 60–100 bytes WiFi / redes móviles ≈ 1200 bytes ⚠️ Los datagramas más grandes pueden fragmentarse a nivel IP, pero esto no es confiable: si se pierde una parte, se pierde todo el datagrama.
✅ Recomendación práctica:
Para IoT y confiabilidad, no superar los 512 bytes por datagrama UDP.
🔁 TCP – Tamaño máximo del buffer de datos
TCP es orientado a flujo (no a paquetes), así que no tiene un "tamaño de mensaje" per se, pero tiene límites de envío por segmento.
🧮 Límite teórico:
-
Tamaño máximo del segmento TCP (MSS) ≈ 1460 bytes (por MTU de 1500 en Ethernet)
-
No hay límite total: podés enviar GB, pero en fragmentos (segmentos)
🔧 En la práctica:
-
TCP divide automáticamente los datos grandes en segmentos de ≈ 1460 bytes
-
El tamaño del buffer de envío/recepción depende del sistema operativo:
-
Linux: típicamente 64 KB a varios MB (
SO_RCVBUF
,SO_SNDBUF
)
-
✅ Recomendación práctica:
-
Para datos grandes (ej. imágenes, firmware, streaming), TCP es ideal.
-
El programador puede escribir buffers de miles de bytes, y TCP se encarga de dividir y reensamblar.
📊 Comparación rápida
Característica UDP TCP Tamaño máx. teórico 65507 bytes Ilimitado (segmentos de ~1460 bytes) Tamaño seguro sin pérdida 512–1472 bytes Automático (MSS ~1460, reensamblado) Fragmentación IP Posible, pero no confiable Automática y manejada por el stack Buffer por mensaje Fijo por datagrama Flujo continuo Adecuado para IoT pequeño ✅ (si < 512 bytes) ⚠️ (requiere más recursos)
🧠 Conclusión
🔹 UDP: 512–1472 bytes es el tamaño útil práctico por mensaje sin riesgo.
🔹 TCP: No hay límite real de tamaño; el sistema fragmenta automáticamente en segmentos ~1460 B.
Perfecto. A continuación te presento ejemplos en C y Python para medir y observar el tamaño máximo de datos que se pueden recibir por UDP y TCP, tanto en un entorno de prueba local como en IoT embebido.
🧪 PARTE 1: UDP – receptor que mide tamaño de trama
📄 🐍 Python – Receptor UDP que mide tamaño de buffer recibido
import socket UDP_IP = "0.0.0.0" UDP_PORT = 5005 BUFFER_SIZE = 65535 # Tamaño máximo UDP posible sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((UDP_IP, UDP_PORT)) print(f"Escuchando en UDP {UDP_IP}:{UDP_PORT}") while True: data, addr = sock.recvfrom(BUFFER_SIZE) print(f"Recibidos {len(data)} bytes desde {addr}")
📄 🐍 Python – Emisor UDP que envía tamaños crecientes
import socket import time UDP_IP = "127.0.0.1" UDP_PORT = 5005 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for size in range(100, 66000, 1000): # Desde 100 a 65k msg = bytes([0xAA]) * size try: sent = sock.sendto(msg, (UDP_IP, UDP_PORT)) print(f"Enviado {sent} bytes") except Exception as e: print(f"Fallo al enviar {size} bytes: {e}") time.sleep(0.1)
💡 Vas a observar que pasar ~1472 bytes falla en algunos entornos debido al MTU. UDP no reintenta.
🧪 PARTE 2: TCP – receptor que mide tamaño de datos
📄 🐍 Python – Receptor TCP que mide tamaño de datos
import socket TCP_IP = "0.0.0.0" TCP_PORT = 5006 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((TCP_IP, TCP_PORT)) sock.listen(1) print(f"Esperando conexión en TCP {TCP_IP}:{TCP_PORT}...") conn, addr = sock.accept() print(f"Conectado desde {addr}") while True: data = conn.recv(65536) # Recibe hasta 64 KB if not data: break print(f"Recibidos {len(data)} bytes")
📄 🐍 Python – Emisor TCP que envía bloques grandes
import socket import time TCP_IP = "127.0.0.1" TCP_PORT = 5006 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((TCP_IP, TCP_PORT)) for size in [512, 1024, 4096, 8192, 16384, 65536]: payload = bytes([0xBB]) * size sock.sendall(payload) print(f"Enviados {size} bytes") time.sleep(0.5) sock.close()
💡 TCP permite enviar grandes volúmenes de datos (MB) sin perder información, porque fragmenta automáticamente.
🧪 PARTE 3: Versión embebida en C (Rabbit / embebido UDP)
#use "dcrtcp.lib" udp_Socket sock; char buffer[1500]; // usar 512, 1024, 1472 para probar void main() { sock_init(); udp_open(&sock, 5005, 0, 0, NULL); // puerto UDP 5005 while (1) { int len = udp_recv(&sock, buffer, sizeof(buffer)); if (len > 0) { printf("Recibidos %d bytes por UDP\n", len); } waitfor(10); // evitar uso completo de CPU } }
📌 Conclusión didáctica
-
UDP: vas a ver que paquetes mayores a 1472 bytes pueden perderse o truncarse.
-
TCP: va a dividir automáticamente y podrás recibir bloques muy grandes sin pérdidas.
-
Ideal para demostrar por qué en IoT se recomienda mantener los mensajes UDP debajo de ~512 bytes.
-
-