Sockets - Linux - TCP - Cliente-Servidor

 

Server TCP en Linux

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>


#define PORT 8080

#define BUFFER_SIZE 1024


int main() {


fd "file descriptor" es un número entero que identifica un socket del sistema

server_fd almacena el del servidor, y new_socket el de un socket creado para aceptar una conexión entrante

    int server_fd, new_socket;


define una variable llamada address de tipo struct sockaddr_in (está definida en <arpa/inet.h> y se usa para manejar IPv4)

struct sockaddr_in address;


opción de configuración para el socket, para usar  SO_REUSEADDR y/o SO_REUSEPORT

    int opt = 1;


addrlen se declara para inicializarse con el tamaño de address y permitir que funciones como accept() sepan cuánta memoria leer o ajustar. Es esencial para manejar correctamente direcciones de red de clientes.

    int addrlen = sizeof(address);


crea un array. Se usa char porque puede manejar tanto caracteres como binarios

las funciones de red como read o recv esperan un puntero void o char

y lo llena de ceros…

    char buffer[BUFFER_SIZE] = {0};


declarando const anuncia que el valor no cambiará
welcome_message es un puntero a una cadena de caracteres que no puede ser modificada. Se declara y asigna el valor a la vez.

    const char *welcome_message = "Hello from the server!";


la función socket() es una llamada al sistema que interactúa con el kernel y reserva los recursos necesarios para la comunicación de red. AF_INET especifica el uso del protocol IPv4. SOCK_STREAM define el tipo de socket como orientado a conexión (TCP). 0 selecciona el protocolo determinado para TCP.

if ((server_fd = socket(...)) == 0): comprueba si la llamada a socket falló. Normalmente socket() retorna un descriptor de archivo mayor o igual a 0 si tiene éxito.

perror muestra un mensaje de error con información detallada sobre lo que falló, utilizando el mensaje pasado más el error del sistema. 

exit(EXIT_FAILURE); Termina el programa indicando que ocurrió un fallo, ya que el socket no pudo ser creado.

    // Creating socket file descriptor

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {

        perror("Socket creation failed");

        exit(EXIT_FAILURE);

    }

Esta sección configura opciones del socket para mejorar su funcionalidad antes de enlazarlo a un puerto.

* if(x) significa if x!=0

En C 0 es falso.. 1 verdadero. Además… 

Si setsockopt() se ejecuta correctamente devolverá 0 (Convención común: 0 para éxito y valores distintos de 0 para error.)


setsockopt
Es una función que configura opciones para el socket. Su prototipo es:

int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);

socket: El descriptor del socket (server_fd en este caso).

level: SOL_SOCKET indica que la opción a configurar pertenece al nivel del socket.

option_name: SO_REUSEADDR | SO_REUSEPORT:

SO_REUSEADDR: Permite reutilizar la dirección del socket rápidamente tras cerrar el programa, sin esperar el tiempo de liberación del puerto.

SO_REUSEPORT: (dependiente del sistema) Permite que múltiples sockets puedan enlazarse al mismo puerto (generalmente para aplicaciones de alta concurrencia).

option_value: Un puntero al valor a establecer, en este caso &opt (Es un entero (opt = 1) que se pasa por referencia para habilitar las opciones SO_REUSEADDR y SO_REUSEPORT.).

option_len: Tamaño del valor, aquí sizeof(opt).

    // Attaching socket to the port

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {

        perror("setsockopt failed");

        close(server_fd);

        exit(EXIT_FAILURE);

    }

Esta sección configura la estructura de dirección address para que el socket pueda enlazarse a un puerto y aceptar conexiones en la red.
address.sin_family especifica la familia de direcciones del socket, en este caso AFINET para IPv4

address.sin_addr.s_addr configura la dirección IP del socket. INADDR_ANY: Significa que el servidor aceptará conexiones en todas las interfaces de red disponibles. Esto incluye localhost (127.0.0.1) y cualquier dirección IP asignada a las interfaces de la máquina.

address.sin_port = htons(PORT); establece el puerto en el que el socket escuchará. PORT fue previamente definido en la constante (8080. htons(PORT): Convierte el número de puerto de orden de bytes del host (little-endian en la mayoría de las arquitecturas) a orden de bytes de red (big-endian), que es el formato estándar para comunicaciones en red.)

    // Setting up the address structure

    address.sin_family = AF_INET;

    address.sin_addr.s_addr = INADDR_ANY;

    address.sin_port = htons(PORT);


Esta sección enlaza el socket creado a una dirección IP y puerto específicos para que pueda escuchar conexiones.


bind(server_fd, ...): Llama a la función bind() para enlazar el socket identificado por server_fd a la dirección IP y puerto de address.

(struct sockaddr *)&address: El bind() espera un puntero de tipo genérico struct sockaddr. Por eso, se hace un cast al tipo (struct sockaddr *) desde la estructura específica struct sockaddr_in. 

sizeof(address): Especifica el tamaño de la estructura address, necesaria para que bind() sepa cuánto leer.

< 0: Verifica si la función falla. Si bind() retorna un valor negativo, indica un error (por ejemplo, si el puerto ya está en uso).

    // Binding the socket

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {

        perror("Bind failed");

        close(server_fd);

        exit(EXIT_FAILURE);

    }

Esta sección pone el socket en modo de escucha, permitiendo que acepte conexiones entrantes.


listen(server_fd, 3):

Llama a la función listen() para que el socket server_fd comience a escuchar conexiones.

3: Es el tamaño de la cola de conexiones pendientes. En este caso, el socket puede tener hasta 3 conexiones en espera antes de rechazarlas.

< 0: Verifica si listen() falló (retorna un valor negativo).


    // Listening for incoming connections

    if (listen(server_fd, 3) < 0) {

        perror("Listen failed");

        close(server_fd);

        exit(EXIT_FAILURE);

    }


    printf("Server is listening on port %d...\n", PORT);


Esta sección acepta una conexión entrante desde un cliente.


accept():Espera una conexión entrante en el socket server_fd. Si un cliente intenta conectarse, crea un nuevo socket para esa conexión y devuelve su descriptor (new_socket).

Parámetros:

server_fd: Descriptor del socket que está escuchando.

(struct sockaddr *)&address: Apunta a una estructura que almacenará la dirección del cliente.

(socklen_t *)&addrlen: Apunta a una variable que almacena el tamaño de la estructura address. Esto permite que accept() ajuste según sea necesario.

< 0: Si accept() falla, devuelve un valor negativo.

    // Accepting a connection

    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {

        perror("Accept failed");

        close(server_fd);

        exit(EXIT_FAILURE);

    }


    printf("Connection established with a client.\n");


send():Envía el mensaje welcome_message al cliente conectado usando el socket new_socket.

new_socket: El socket de la conexión.

welcome_message: El mensaje a enviar.

strlen(welcome_message): Tamaño del mensaje.

0: Sin opciones adicionales.


    // Sending a welcome message to the client

    send(new_socket, welcome_message, strlen(welcome_message), 0);

    printf("Welcome message sent to the client.\n");

read(): Lee datos enviados por el cliente a través del socket new_socket.

new_socket: Socket de la conexión con el cliente.

buffer: Donde se almacenan los datos recibidos.

BUFFER_SIZE: Cantidad máxima de datos que puede leer.

valread: Almacena la cantidad de bytes leídos del cliente. 

    // Receiving a message from the client

    int valread = read(new_socket, buffer, BUFFER_SIZE);

    printf("Message received from client: %s\n", buffer);


    close(new_socket);

    close(server_fd);

    return 0;

}

Cliente TCP en Linux

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>


#define PORT 8080

#define BUFFER_SIZE 1024


int main() {


Declara un entero llamado sock que se usará como descriptor del socket (Todas las operaciones (como enviar, recibir, conectar) se realizan usando este número.). Inicializado en 0, aunque será asignado por la función socket() más adelante.

    int sock = 0;

    struct sockaddr_in serv_addr;

    char buffer[BUFFER_SIZE] = {0};

    const char *message = "Hello from the client!";


sock = socket(AF_INET, SOCK_STREAM, 0): Llama a la función socket() para crear un socket:

AF_INET: Usa el protocolo IPv4.

SOCK_STREAM: Define que es un socket orientado a conexión (TCP).

0: Selecciona el protocolo predeterminado para TCP (no es una opción cambiarlo, es un por defecto sin sentido especial).


Si tiene éxito, devuelve un número entero (descriptor del socket) y lo asigna a sock.

Si falla, devuelve un valor negativo (-1).

< 0: Verifica si sock es menor que 0, lo que indica que hubo un error al crear el socket.

    // Creating socket file descriptor

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

        perror("Socket creation failed");

        exit(EXIT_FAILURE);

    }


serv_addr.sin_family = AF_INET;:

Especifica que el socket usará direcciones IPv4.

AF_INET es la familia de direcciones para IPv4 (Address Family Internet).

    // Setting up the server address

    serv_addr.sin_family = AF_INET;


serv_addr.sin_port = htons(PORT);: Configura el puerto al que el cliente se conectará.

PORT es la constante definida (8080).

htons() convierte el número de puerto al formato de bytes de red (big-endian), que es el estándar para transmisiones en red.

    serv_addr.sin_port = htons(PORT);


inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr):

Convierte la dirección IP "127.0.0.1" (en formato de texto) a su equivalente en formato binario (big-endian) y la almacena en serv_addr.sin_addr. (es el campo donde se guarda la dirección IP del servidor en formato binario. Es parte de la estructura sockaddr_in).. no es un campo que se pueda estar modificando u eligiendo opciones.. debe ser ese.

AF_INET indica que la dirección es IPv4.

    // Converting IPv4 address from text to binary form

    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {

        perror("Invalid address or address not supported");

        close(sock);

        exit(EXIT_FAILURE);

    }


Sock es el identificador del socket del cliente, previamente creado con socket()

(struct sockaddr *)&serv_addr Contiene la dirección del servidor (IP y puerto).

El cast (struct sockaddr *) hace que connect() acepte serv_addr, ya que espera un tipo genérico struct sockaddr *. El cast no modifica los datos, solo adapta el tipo.


sizeof(serv_addr): Proporciona el tamaño de la estructura serv_addr, para que connect() lea correctamente la dirección.

    // Connecting to the server

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {

        perror("Connection to the server failed");

        close(sock);

        exit(EXIT_FAILURE);

    }


    printf("Connected to the server.\n");


    // Sending a message to the server


send(): Envía datos al servidor a través del socket sock.

sock: Descriptor del socket, identifica la conexión con el servidor.

message: Puntero al mensaje que se quiere enviar (en este caso, "Hello from the client!").

strlen(message): Cantidad de bytes a enviar (la longitud del mensaje en este caso).

0: Opciones adicionales (usualmente se usa 0, que indica envío estándar).

    send(sock, message, strlen(message), 0);

    printf("Message sent to the server: %s\n", message);

read(): Lee datos enviados por el servidor a través del socket sock.

sock: Descriptor del socket, identifica la conexión activa con el servidor.

buffer: Espacio en memoria donde se almacenará el mensaje recibido.

BUFFER_SIZE: Cantidad máxima de datos que puede leer (tamaño del buffer).

int valread:

Almacena el número de bytes leídos por read().

Si read() devuelve:

Un número positivo: Representa la cantidad de bytes leídos.

0: Indica que el servidor cerró la conexión.

-1: Hubo un error en la lectura.

Impresión del mensaje: El contenido recibido se almacena en buffer y se muestra en la consola usando printf().

    // Receiving a message from the server

    int valread = read(sock, buffer, BUFFER_SIZE);

    printf("Message received from server: %s\n", buffer);


    close(sock);

    return 0;

}


Paso

TCP

UDP

Crear el socket

socket(AF_INET, SOCK_STREAM, 0)

socket(AF_INET, SOCK_DGRAM, 0)

Configurar la dirección

Configurar sockaddr_in (IP, puerto)

Configurar sockaddr_in (IP, puerto)

Enlazar el socket (bind)

bind() (servidor)

bind() (servidor)

Establecer conexión (cliente)

connect() (cliente)

No necesario

Escuchar conexiones (servidor)

listen() (servidor)

No necesario

Aceptar conexiones (servidor)

accept() (servidor)

No necesario

Enviar datos

send()

sendto()

Recibir datos

recv()

recvfrom()

Cerrar conexión

close()

close()