Programación de sockets en C / C ++

¿Qué es la programación de sockets?

La programación de sockets es una forma de conectar dos nodos en una red para comunicarse entre sí. Un socket (nodo) escucha en un puerto particular en una IP, mientras que otro socket se acerca al otro para formar una conexión. El servidor forma el socket del oyente mientras que el cliente se comunica con el servidor.

Diagrama de estado para el modelo de servidor y cliente

Etapas para servidor

Creación de sockets:

int sockfd = socket (dominio, tipo, protocolo)

sockfd: descriptor de socket, un número entero (como un identificador de archivo)
dominio: entero, dominio de comunicación, p. ej., AF_INET (protocolo IPv4), AF_INET6 (protocolo IPv6)
tipo: tipo de comunicación
SOCK_STREAM: TCP (confiable, orientado a la conexión)
SOCK_DGRAM: UDP (no confiable, sin conexión)
protocolo: valor de protocolo para el Protocolo de Internet (IP), que es 0. Este es el mismo número que aparece en el campo de protocolo en el encabezado IP de un paquete. (protocolos man para más detalles)

Setsockopt:

int setsockopt (int sockfd, int nivel, int optname,
const void * optval, socklen_t optlen);

Esto ayuda a manipular las opciones para el socket referido por el descriptor de archivo sockfd. Esto es completamente opcional, pero ayuda a reutilizar la dirección y el puerto. Evita errores como: «dirección ya en uso».

Unir:

int bind (int sockfd, const struct sockaddr * addr,
socklen_t addrlen);

Después de la creación del socket, la función de vinculación vincula el socket a la dirección y el número de puerto especificados en addr (estructura de datos personalizada). En el código de ejemplo, vinculamos el servidor al localhost, por lo tanto, usamos INADDR_ANY para especificar la dirección IP.

Escucha:

int listen (int sockfd, int backlog);

Pone el socket del servidor en modo pasivo, donde espera que el cliente se acerque al servidor para hacer una conexión. El backlog define la longitud máxima a la que puede crecer la cola de conexiones pendientes para sockfd. Si llega una solicitud de conexión cuando la cola está llena, el cliente puede recibir un error con una indicación de ECONNREFUSED.

Aceptar:

int new_socket = accept (int sockfd, struct sockaddr * addr, socklen_t * addrlen);

Extrae la primera solicitud de conexión en la cola de conexiones pendientes para el socket de escucha, sockfd, crea un nuevo socket conectado y devuelve un nuevo descriptor de archivo que hace referencia a ese socket. En este punto, se establece la conexión entre el cliente y el servidor, y están listos para transferir datos.

Etapas para el cliente

Conexión de socket: exactamente igual que la de la creación de socket del servidor
Conectar:

int connect (int sockfd, const struct sockaddr * addr,
socklen_t addrlen);

La llamada al sistema connect () conecta el conector al que hace referencia el descriptor de archivo sockfd a la dirección especificada por addr. La dirección y el puerto del servidor se especifican en addr.

Implementación

Aquí estamos intercambiando un mensaje de saludo entre el servidor y el cliente para demostrar el modelo cliente / servidor.

Servidor C



// Programa C / C ++ del lado del servidor para demostrar la programación de Socket
#include <unistd.h> 
#include <stdio.h> 
#include <sys/socket.h> 
#include <stdlib.h> 
#include <netinet/in.h> 
#include <string.h> 
#define PORT 8080 
int main(int argc, char const *argv[]) 
{ 
    int server_fd, new_socket, valread; 
    struct sockaddr_in address; 
    int opt = 1; 
    int addrlen = sizeof(address); 
    char buffer[1024] = {0}; 
    char *hello = "Hola del servidor"; 
       
    // Creando descriptor de archivo de socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) 
    { 
        perror("socket failed"); 
        exit(EXIT_FAILURE); 
    } 
       
    // Conectar con fuerza el zócalo al puerto 8080
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, 
                                                  &opt, sizeof(opt))) 
    { 
        perror("setsockopt"); 
        exit(EXIT_FAILURE); 
    } 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = INADDR_ANY; 
    address.sin_port = htons( PORT ); 
       
    // Conectar con fuerza el zócalo al puerto 8080
    if (bind(server_fd, (struct sockaddr *)&address,  
                                 sizeof(address))<0) 
    { 
        perror("bind failed"); 
        exit(EXIT_FAILURE); 
    } 
    if (listen(server_fd, 3) < 0) 
    { 
        perror("listen"); 
        exit(EXIT_FAILURE); 
    } 
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address,  
                       (socklen_t*)&addrlen))<0) 
    { 
        perror("accept"); 
        exit(EXIT_FAILURE); 
    } 
    valread = read( new_socket , buffer, 1024); 
    printf("%s\n",buffer ); 
    send(new_socket , hello , strlen(hello) , 0 ); 
    printf("Hola mensaje enviado\n"); 
    return 0; 
} 

Cliente C

// Programa C / C ++ del lado del cliente para demostrar la programación de Socket
#include <stdio.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <string.h> 
#define PORT 8080 
   
int main(int argc, char const *argv[]) 
{ 
    int sock = 0, valread; 
    struct sockaddr_in serv_addr; 
    char *hello = "Hola del cliente"; 
    char buffer[1024] = {0}; 
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    { 
        printf("\n Error de creación de socket \n"); 
        return -1; 
    } 
   
    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_port = htons(PORT); 
       
    // Convierta direcciones IPv4 e IPv6 de texto a formato binario
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0)  
    { 
        printf("\nDirección inválida/ Dirección no admitida \n"); 
        return -1; 
    } 
   
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) 
    { 
        printf("\nLa conexión falló \n"); 
        return -1; 
    } 
    send(sock , hello, strlen(hello) , 0 ); 
    printf("Hola mensaje enviado \n"); 
    valread = read( sock , buffer, 1024); 
    printf("%s\n",buffer ); 
    return 0; 
} 

Compilando:

cliente gcc.c -o cliente
servidor gcc.c -o servidor

Producción:

Cliente: mensaje de saludo enviado
Hola del servidor
Servidor: Hola del cliente
Hola mensaje enviado