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