Te armo dos ejemplos completos en lenguaje C (uno con UDP y otro con TCP) usando máquinas de estado con switch-case
. Cada uno hace lo siguiente:
🔹 1. UDP con máquina de estados
/*
* UDP cliente/servidor no bloqueante con máquina de estados
* Envía 10 KB en puerto 5005 y recibe 10 KB en puerto 5010
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#define SERVER_IP "127.0.0.1"
#define SEND_PORT 5005
#define RECV_PORT 5010
#define BUFFER_SIZE 10240 // 10 KB
typedef enum {
STATE_INIT,
STATE_SEND,
STATE_RECV,
STATE_DONE
} state_t;
int main() {
int sock_send, sock_recv;
struct sockaddr_in addr_send, addr_recv;
char send_buffer[BUFFER_SIZE];
char recv_buffer[BUFFER_SIZE];
ssize_t n;
// Crear socket envío
sock_send = socket(AF_INET, SOCK_DGRAM, 0);
fcntl(sock_send, F_SETFL, O_NONBLOCK);
memset(&addr_send, 0, sizeof(addr_send));
addr_send.sin_family = AF_INET;
addr_send.sin_port = htons(SEND_PORT);
inet_pton(AF_INET, SERVER_IP, &addr_send.sin_addr);
// Crear socket recepción
sock_recv = socket(AF_INET, SOCK_DGRAM, 0);
fcntl(sock_recv, F_SETFL, O_NONBLOCK);
memset(&addr_recv, 0, sizeof(addr_recv));
addr_recv.sin_family = AF_INET;
addr_recv.sin_port = htons(RECV_PORT);
addr_recv.sin_addr.s_addr = INADDR_ANY;
bind(sock_recv, (struct sockaddr *)&addr_recv, sizeof(addr_recv));
// Preparar buffer de envío
memset(send_buffer, 'A', sizeof(send_buffer));
state_t state = STATE_INIT;
while (state != STATE_DONE) {
switch (state) {
case STATE_INIT:
printf("Iniciando comunicación UDP...\n");
state = STATE_SEND;
break;
case STATE_SEND:
n = sendto(sock_send, send_buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&addr_send, sizeof(addr_send));
if (n < 0 && errno != EWOULDBLOCK) {
perror("Error en sendto");
state = STATE_DONE;
} else if (n > 0) {
printf("Se enviaron %ld bytes por UDP.\n", n);
state = STATE_RECV;
}
break;
case STATE_RECV:
n = recvfrom(sock_recv, recv_buffer, BUFFER_SIZE, 0, NULL, NULL);
if (n < 0 && errno != EWOULDBLOCK) {
perror("Error en recvfrom");
state = STATE_DONE;
} else if (n > 0) {
printf("Se recibieron %ld bytes por UDP.\n", n);
state = STATE_DONE;
}
break;
default:
state = STATE_DONE;
break;
}
usleep(100000); // 100 ms
}
close(sock_send);
close(sock_recv);
return 0;
}
🔹 2. TCP con máquina de estados
/*
* TCP cliente no bloqueante con máquina de estados
* Envía y recibe 10 KB de datos en puerto 5005
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 5005
#define BUFFER_SIZE 10240 // 10 KB
typedef enum {
STATE_INIT,
STATE_CONNECT,
STATE_SEND,
STATE_RECV,
STATE_DONE
} state_t;
int main() {
int sock;
struct sockaddr_in server_addr;
char send_buffer[BUFFER_SIZE];
char recv_buffer[BUFFER_SIZE];
ssize_t n;
sock = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sock, F_SETFL, O_NONBLOCK);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
// Preparar buffer de envío
memset(send_buffer, 'B', sizeof(send_buffer));
state_t state = STATE_INIT;
while (state != STATE_DONE) {
switch (state) {
case STATE_INIT:
printf("Iniciando comunicación TCP...\n");
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
if (errno == EINPROGRESS) {
printf("Conexión en progreso...\n");
state = STATE_CONNECT;
} else {
perror("Error en connect");
state = STATE_DONE;
}
}
break;
case STATE_CONNECT:
// En no bloqueante asumimos que ya conectó
state = STATE_SEND;
break;
case STATE_SEND:
n = send(sock, send_buffer, BUFFER_SIZE, 0);
if (n < 0 && errno != EWOULDBLOCK) {
perror("Error en send");
state = STATE_DONE;
} else if (n > 0) {
printf("Se enviaron %ld bytes por TCP.\n", n);
state = STATE_RECV;
}
break;
case STATE_RECV:
n = recv(sock, recv_buffer, BUFFER_SIZE, 0);
if (n < 0 && errno != EWOULDBLOCK) {
perror("Error en recv");
state = STATE_DONE;
} else if (n > 0) {
printf("Se recibieron %ld bytes por TCP.\n", n);
state = STATE_DONE;
}
break;
default:
state = STATE_DONE;
break;
}
usleep(100000); // 100 ms
}
close(sock);
return 0;
}
📌 Notas para alumnos:
-
En ambos casos se usa fcntl(..., O_NONBLOCK)
para que las operaciones no bloqueen.
-
La máquina de estados con switch-case
permite controlar la secuencia: INIT → SEND → RECV → DONE
.
-
En TCP, la conexión puede tardar, por eso se maneja el estado STATE_CONNECT
.
-
Se usan buffers de 10 KB (10240
bytes) llenados con un carácter (A
o B
).
Te armo el código en C con máquina de estados para cubrir tres casos:
-
Cliente UDP no bloqueante (envía 10 KB al puerto 5005 y recibe 10 KB en 5010).
-
Cliente TCP no bloqueante (envía/recibe 10 KB al puerto 5015).
-
Servidor TCP (puerto 5020) que escucha conexiones, recibe bloques grandes de datos, responde, y gestiona el cierre limpio de la conexión.
Todo con máquina de estados basada en switch-case
.
📌 Código C con UDP y TCP (cliente y servidor)
/*
* Ejemplo de comunicación UDP y TCP con máquina de estados
* - Cliente UDP: puerto remoto 5005 (envío), puerto local 5010 (recepción)
* - Cliente TCP: puerto remoto 5015
* - Servidor TCP: puerto local 5020
*
* Compilar en Linux:
* gcc main.c -o main
* Ejecutar:
* ./main
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define UDP_TX_PORT 5005
#define UDP_RX_PORT 5010
#define TCP_PORT 5015
#define TCP_SERVER_PORT 5020
#define BUFFER_SIZE 10240 // 10 KB
// ---------- Funciones auxiliares ----------
int make_nonblocking(int sock) {
int flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
// ---------- Cliente UDP ----------
void udp_client_fsm() {
enum { UDP_INIT, UDP_SEND, UDP_RECV, UDP_DONE } state = UDP_INIT;
static int sock_tx, sock_rx;
static struct sockaddr_in addr_tx, addr_rx;
static char buffer_tx[BUFFER_SIZE];
static char buffer_rx[BUFFER_SIZE];
socklen_t addr_len = sizeof(addr_rx);
int ret;
while (state != UDP_DONE) {
switch (state) {
case UDP_INIT:
printf("[UDP] Inicializando...\n");
sock_tx = socket(AF_INET, SOCK_DGRAM, 0);
sock_rx = socket(AF_INET, SOCK_DGRAM, 0);
make_nonblocking(sock_tx);
make_nonblocking(sock_rx);
memset(&addr_tx, 0, sizeof(addr_tx));
addr_tx.sin_family = AF_INET;
addr_tx.sin_port = htons(UDP_TX_PORT);
inet_pton(AF_INET, "127.0.0.1", &addr_tx.sin_addr);
memset(&addr_rx, 0, sizeof(addr_rx));
addr_rx.sin_family = AF_INET;
addr_rx.sin_port = htons(UDP_RX_PORT);
addr_rx.sin_addr.s_addr = INADDR_ANY;
bind(sock_rx, (struct sockaddr *)&addr_rx, sizeof(addr_rx));
memset(buffer_tx, 'U', BUFFER_SIZE);
state = UDP_SEND;
break;
case UDP_SEND:
ret = sendto(sock_tx, buffer_tx, BUFFER_SIZE, 0,
(struct sockaddr *)&addr_tx, sizeof(addr_tx));
if (ret > 0) {
printf("[UDP] Enviados %d bytes\n", ret);
state = UDP_RECV;
}
break;
case UDP_RECV:
ret = recvfrom(sock_rx, buffer_rx, BUFFER_SIZE, 0,
(struct sockaddr *)&addr_rx, &addr_len);
if (ret > 0) {
printf("[UDP] Recibidos %d bytes\n", ret);
state = UDP_DONE;
}
break;
case UDP_DONE:
close(sock_tx);
close(sock_rx);
printf("[UDP] Comunicación finalizada\n");
break;
}
}
}
// ---------- Cliente TCP ----------
void tcp_client_fsm() {
enum { TCP_INIT, TCP_CONNECT, TCP_SEND, TCP_RECV, TCP_CLOSE, TCP_DONE } state = TCP_INIT;
static int sock;
static struct sockaddr_in server_addr;
static char buffer_tx[BUFFER_SIZE];
static char buffer_rx[BUFFER_SIZE];
int ret;
while (state != TCP_DONE) {
switch (state) {
case TCP_INIT:
printf("[TCP Client] Inicializando...\n");
sock = socket(AF_INET, SOCK_STREAM, 0);
make_nonblocking(sock);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TCP_PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
memset(buffer_tx, 'T', BUFFER_SIZE);
state = TCP_CONNECT;
break;
case TCP_CONNECT:
ret = connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret == 0 || errno == EINPROGRESS) {
printf("[TCP Client] Conectado (o en progreso)...\n");
state = TCP_SEND;
}
break;
case TCP_SEND:
ret = send(sock, buffer_tx, BUFFER_SIZE, 0);
if (ret > 0) {
printf("[TCP Client] Enviados %d bytes\n", ret);
state = TCP_RECV;
}
break;
case TCP_RECV:
ret = recv(sock, buffer_rx, BUFFER_SIZE, 0);
if (ret > 0) {
printf("[TCP Client] Recibidos %d bytes\n", ret);
state = TCP_CLOSE;
}
break;
case TCP_CLOSE:
printf("[TCP Client] Cerrando conexión...\n");
close(sock);
state = TCP_DONE;
break;
case TCP_DONE:
printf("[TCP Client] Finalizado\n");
break;
}
}
}
// ---------- Servidor TCP ----------
void tcp_server_fsm() {
enum { SERVER_INIT, SERVER_ACCEPT, SERVER_RECV, SERVER_SEND, SERVER_CLOSE, SERVER_DONE } state = SERVER_INIT;
static int server_sock, client_sock;
static struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
static char buffer[BUFFER_SIZE];
int ret;
while (state != SERVER_DONE) {
switch (state) {
case SERVER_INIT:
printf("[TCP Server] Inicializando en puerto %d...\n", TCP_SERVER_PORT);
server_sock = socket(AF_INET, SOCK_STREAM, 0);
make_nonblocking(server_sock);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TCP_SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_sock, 1);
state = SERVER_ACCEPT;
break;
case SERVER_ACCEPT:
client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_len);
if (client_sock >= 0) {
make_nonblocking(client_sock);
printf("[TCP Server] Cliente conectado\n");
state = SERVER_RECV;
}
break;
case SERVER_RECV:
ret = recv(client_sock, buffer, BUFFER_SIZE, 0);
if (ret > 0) {
printf("[TCP Server] Recibidos %d bytes\n", ret);
state = SERVER_SEND;
} else if (ret == 0) {
printf("[TCP Server] Cliente solicitó cierre\n");
state = SERVER_CLOSE;
}
break;
case SERVER_SEND:
ret = send(client_sock, buffer, BUFFER_SIZE, 0);
if (ret > 0) {
printf("[TCP Server] Reenviados %d bytes\n", ret);
state = SERVER_RECV;
}
break;
case SERVER_CLOSE:
close(client_sock);
close(server_sock);
printf("[TCP Server] Conexión cerrada\n");
state = SERVER_DONE;
break;
case SERVER_DONE:
printf("[TCP Server] Finalizado\n");
break;
}
}
}
// ---------- MAIN ----------
int main() {
printf("=== DEMO Cliente UDP ===\n");
udp_client_fsm();
printf("\n=== DEMO Cliente TCP ===\n");
tcp_client_fsm();
printf("\n=== DEMO Servidor TCP ===\n");
tcp_server_fsm();
return 0;
}
📌 Resumen
-
UDP Cliente: envía 10 KB → recibe 10 KB, todo no bloqueante.
-
TCP Cliente: conecta a 127.0.0.1:5015
, envía y recibe 10 KB.
-
TCP Servidor: escucha en 5020
, acepta cliente, recibe/envía bloques grandes y cierra limpio al detectar ret==0
(FIN del cliente).
Te paso un ejemplo simple en C de un servidor DNS por UDP.
⚠️ Importante: es un ejemplo didáctico muy simplificado que solo responde a cualquier consulta con una IP fija (por ejemplo, 192.168.1.100
). No implementa todo el protocolo DNS completo, pero sirve para que los alumnos vean cómo funciona la recepción y respuesta de un paquete DNS en UDP puerto 53.
📌 Código en C — Servidor DNS UDP (simplificado)
/*
* Servidor DNS simple por UDP
* Responde siempre con la misma dirección IP: 192.168.1.100
* Compilación: gcc dns_server.c -o dns_server
* Ejecución (como root si es necesario por puerto <1024): sudo ./dns_server
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define DNS_PORT 53
#define BUFFER_SIZE 512
// Dirección IP fija a devolver
const unsigned char fixed_ip[4] = {192, 168, 1, 100};
// Estructura básica del encabezado DNS
struct DNS_HEADER {
unsigned short id; // ID de la transacción
unsigned short flags; // Flags
unsigned short q_count; // Número de preguntas
unsigned short ans_count;// Número de respuestas
unsigned short auth_count;
unsigned short add_count;
};
// Estructura de pregunta DNS
struct QUESTION {
unsigned short qtype;
unsigned short qclass;
};
// Estructura de recurso DNS
struct R_DATA {
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
};
void build_dns_response(unsigned char *buffer, int query_len, int *response_len) {
struct DNS_HEADER *dns = NULL;
unsigned char *qname = NULL, *reader = NULL;
struct QUESTION *qinfo = NULL;
dns = (struct DNS_HEADER*) buffer;
qname = (unsigned char*)(buffer + sizeof(struct DNS_HEADER));
reader = qname + strlen((const char*)qname) + 1;
qinfo = (struct QUESTION*)(reader);
// Modificar encabezado para respuesta
dns->flags = htons(0x8180); // Respuesta estándar, sin error
dns->ans_count = htons(1); // Una respuesta
// Mover puntero al final de la consulta
reader += sizeof(struct QUESTION);
// Escribir respuesta (apuntador al nombre: 0xC00C)
unsigned char *rname = reader;
rname[0] = 0xC0;
rname[1] = 0x0C;
reader += 2;
// Datos de recurso
struct R_DATA *rdata = (struct R_DATA*) reader;
rdata->type = htons(1); // Tipo A
rdata->_class = htons(1); // Clase IN
rdata->ttl = htonl(60); // TTL = 60s
rdata->data_len = htons(4);
reader += sizeof(struct R_DATA);
// IP
memcpy(reader, fixed_ip, 4);
reader += 4;
*response_len = reader - buffer;
}
int main() {
int sockfd;
struct sockaddr_in server, client;
unsigned char buffer[BUFFER_SIZE];
socklen_t client_len;
int n, response_len;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("Error al crear socket");
exit(1);
}
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(DNS_PORT);
if (bind(sockfd, (struct sockaddr*)&server, sizeof(server)) < 0) {
perror("Error en bind");
close(sockfd);
exit(1);
}
printf("Servidor DNS escuchando en puerto %d...\n", DNS_PORT);
while (1) {
client_len = sizeof(client);
n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
(struct sockaddr*)&client, &client_len);
if (n < 0) {
perror("Error en recvfrom");
continue;
}
printf("Consulta recibida (%d bytes)\n", n);
build_dns_response(buffer, n, &response_len);
sendto(sockfd, buffer, response_len, 0,
(struct sockaddr*)&client, client_len);
printf("Respuesta enviada con IP %d.%d.%d.%d\n",
fixed_ip[0], fixed_ip[1], fixed_ip[2], fixed_ip[3]);
}
close(sockfd);
return 0;
}
🔹 Explicación breve para alumnos
-
El servidor escucha en UDP puerto 53.
-
Cuando llega un paquete DNS (consulta), lo analiza mínimamente.
-
Construye una respuesta con un registro tipo A que siempre devuelve la IP 192.168.1.100
.
-
Responde al cliente que hizo la consulta.
Te armo un cliente DNS en C que envíe una consulta UDP al servidor que hicimos antes (por ejemplo, preguntando por www.ejemplo.com
) y reciba la respuesta.
Este cliente construye un paquete DNS mínimo, lo envía a 127.0.0.1:53
(puerto estándar DNS) y luego imprime la IP que recibe en la respuesta.
📌 Cliente DNS en C (simplificado)
/*
* Cliente DNS simple por UDP
* Consulta por "www.ejemplo.com" y recibe la IP desde el servidor DNS
* Compilación: gcc dns_client.c -o dns_client
* Ejecución: ./dns_client
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define DNS_PORT 53
#define BUFFER_SIZE 512
// Encabezado DNS (simplificado)
struct DNS_HEADER {
unsigned short id;
unsigned short flags;
unsigned short q_count;
unsigned short ans_count;
unsigned short auth_count;
unsigned short add_count;
};
// Pregunta DNS
struct QUESTION {
unsigned short qtype;
unsigned short qclass;
};
// Función para convertir dominio a formato DNS (3www7ejemplo3com0)
void ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) {
int lock = 0, i;
strcat((char*)host, ".");
for(i = 0; i < strlen((char*)host); i++) {
if(host[i]=='.') {
*dns++ = i-lock;
for(;lock<i;lock++) {
*dns++=host[lock];
}
lock++;
}
}
*dns++='\0';
}
int main() {
int sockfd, n;
struct sockaddr_in dest;
unsigned char buffer[BUFFER_SIZE];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0) {
perror("Error creando socket");
exit(1);
}
dest.sin_family = AF_INET;
dest.sin_port = htons(DNS_PORT);
dest.sin_addr.s_addr = inet_addr("127.0.0.1"); // Servidor local
// Construcción de consulta
struct DNS_HEADER *dns = (struct DNS_HEADER *)&buffer;
dns->id = (unsigned short) htons(1234);
dns->flags = htons(0x0100); // Recursiva
dns->q_count = htons(1);
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
unsigned char *qname = (unsigned char*)&buffer[sizeof(struct DNS_HEADER)];
unsigned char hostname[] = "www.ejemplo.com";
ChangetoDnsNameFormat(qname , hostname);
struct QUESTION *qinfo = (struct QUESTION*)&buffer[sizeof(struct DNS_HEADER) + (strlen((const char*)qname) + 1)];
qinfo->qtype = htons(1); // Tipo A
qinfo->qclass = htons(1); // Clase IN
int query_size = sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION);
// Enviar consulta
sendto(sockfd, buffer, query_size, 0, (struct sockaddr*)&dest, sizeof(dest));
printf("Consulta DNS enviada: %s\n", hostname);
// Recibir respuesta
socklen_t len = sizeof(dest);
n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&dest, &len);
if(n < 0) {
perror("Error en recvfrom");
close(sockfd);
exit(1);
}
printf("Respuesta recibida (%d bytes)\n", n);
// Leer dirección IP de respuesta (últimos 4 bytes en este ejemplo)
unsigned char *ip = buffer + n - 4;
printf("IP devuelta: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
close(sockfd);
return 0;
}
🔹 Flujo de prueba
-
Ejecutás servidor:
sudo ./dns_server
-
En otra terminal, ejecutás cliente:
./dns_client
-
El cliente envía la consulta www.ejemplo.com
→ el servidor responde siempre con 192.168.1.100
.
-
El cliente imprime:
Consulta DNS enviada: www.ejemplo.com
Respuesta recibida (56 bytes)
IP devuelta: 192.168.1.100