logo

Programación de sockets en C

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 el otro socket se acerca al otro para formar una conexión. El servidor forma el socket de escucha mientras el cliente se comunica con el servidor.
La programación de sockets se usa ampliamente en aplicaciones de mensajería instantánea, transmisión binaria y colaboraciones de documentos, plataformas de transmisión en línea, etc.

Ejemplo

En este programa en C estamos intercambiando un mensaje de saludo entre el servidor y el cliente para demostrar el modelo cliente/servidor.

servidor.c

C
#include  #include  #include  #include  #include  #include  #define PORT 8080 int main(int argc char const* argv[]) {  int server_fd new_socket;  ssize_t valread;  struct sockaddr_in address;  int opt = 1;  socklen_t addrlen = sizeof(address);  char buffer[1024] = { 0 };  char* hello = 'Hello from server';  // Creating socket file descriptor  if ((server_fd = socket(AF_INET SOCK_STREAM 0)) < 0) {  perror('socket failed');  exit(EXIT_FAILURE);  }  // Forcefully attaching socket to the port 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);  // Forcefully attaching socket to the port 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  &addrlen))  < 0) {  perror('accept');  exit(EXIT_FAILURE);  }    // subtract 1 for the null  // terminator at the end  valread = read(new_socket buffer  1024 - 1);   printf('%sn' buffer);  send(new_socket hello strlen(hello) 0);  printf('Hello message sentn');  // closing the connected socket  close(new_socket);    // closing the listening socket  close(server_fd);  return 0; } 

cliente.c

C
#include    #include  #include  #include  #include  #define PORT 8080 int main(int argc char const* argv[]) {  int status valread client_fd;  struct sockaddr_in serv_addr;  char* hello = 'Hello from client';  char buffer[1024] = { 0 };  if ((client_fd = socket(AF_INET SOCK_STREAM 0)) < 0) {  printf('n Socket creation error n');  return -1;  }  serv_addr.sin_family = AF_INET;  serv_addr.sin_port = htons(PORT);  // Convert IPv4 and IPv6 addresses from text to binary  // form  if (inet_pton(AF_INET '127.0.0.1' &serv_addr.sin_addr)  <= 0) {  printf(  'nInvalid address/ Address not supported n');  return -1;  }  if ((status  = connect(client_fd (struct sockaddr*)&serv_addr  sizeof(serv_addr)))  < 0) {  printf('nConnection Failed n');  return -1;  }    // subtract 1 for the null  // terminator at the end  send(client_fd hello strlen(hello) 0);  printf('Hello message sentn');  valread = read(client_fd buffer  1024 - 1);   printf('%sn' buffer);  // closing the connected socket  close(client_fd);  return 0; } 


Compilando



gcc client.c -o clientgcc server.c -o server


Producción

Client:Hello message sentHello from serverServer:Hello from clientHello message sent

Componentes de la programación de sockets

1. Enchufes

Enchufes son uno de los componentes principales utilizados por el programa para acceder a la red y comunicarse con otros procesos/nodos a través de la red. Es simplemente una combinación de una dirección IP y un número de puerto que actúa como punto final para la comunicación.
Ejemplo: 192.168.1.1:8080 donde las dos partes separadas por dos puntos representan la Dirección IP (192.168.1.1) y el número de puerto (8080).

Tipos de enchufes:

  • Zócalo TCP (zócalo de flujo): Proporciona comunicación confiable basada en conexión (es decir, protocolo TCP ).
  • Zócalo UDP (zócalo de datagrama): Proporciona comunicación sin conexión más rápida pero poco confiable (es decir, protocolo UDP ).

2. Modelo Cliente-Servidor

El modelo cliente-servidor Se refiere a la arquitectura utilizada en la programación de sockets donde un cliente y un servidor interactúan entre sí para intercambiar información o servicios. Esta arquitectura permite al cliente enviar solicitudes de servicio y al servidor procesar y enviar respuestas a esas solicitudes de servicio.

Diagrama de estado para el modelo de servidor y cliente

Programación de sockets en CDiagrama de estado para el modelo de servidor y cliente de Socket

La programación de sockets en C es una forma poderosa de manejar la comunicación de red.

Crear un proceso del lado del servidor

El servidor se crea siguiendo los siguientes pasos:

algoritmo de agrupamiento k

1. Creación de zócalos

Este paso implica la creación del socket usando la función socket().

Parámetros:

  • calcetín: descriptor de socket un número entero (como un identificador de archivo)
  • dominio: El número entero especifica el dominio de comunicación. Usamos AF_LOCAL como se define en el estándar POSIX para la comunicación entre procesos en el mismo host. Para comunicarnos entre procesos en diferentes hosts conectados por IPV4 utilizamos AF_INET y AF_I NET 6 para procesos conectados por IPV6.
  • tipo: tipo de comunicación
    SOCK_STREAM: TCP (orientado a conexión confiable)
    SOCK_DGRAM: UDP (sin conexión no confiable)
  • 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).
C
sockfd = socket(domain type protocol) 

2. Establecer opción de enchufe

Esto ayuda a manipular las opciones para el socket al que hace referencia el descriptor de archivo sockfd. Esto es completamente opcional pero ayuda a reutilizar la dirección y el puerto. Previene errores como: dirección ya en uso.

C
setsockopt(sockfd level optname optval socklen_t optlen); 

3. Atar

Después de la creación del socket, la función bind() 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 host local, por lo tanto usamos INADDR_ANY para especificar la dirección IP.

C++
bind(sockfd sockaddr *addr socklen_t addrlen); 

Parámetros:

  • calcetín : descriptor de archivo de socket creado usando la función socket().
  • dirección : puntero a una estructura sockaddr que contiene la dirección IP y el número de puerto para vincular el socket.
  • addrlen : longitud de la estructura de dirección.

4. Escuche

En este paso, el servidor utiliza la función listening() que pone el socket del servidor en modo pasivo donde espera a que el cliente se acerque al servidor para establecer una conexión. El trabajo pendiente define la longitud máxima hasta la cual 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.

C
listen(sockfd backlog); 

Parámetros :

  • calcetín : descriptor de archivo de socket creado usando la función socket().
  • reserva : número que representa el tamaño de la cola que contiene las conexiones pendientes mientras el servidor espera aceptar una conexión.

5. Aceptar

En este paso, el servidor extrae la primera solicitud de conexión de la cola de conexiones pendientes para el socket de escucha. sockfd crea un nuevo socket conectado usando el aceptar() función 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.

C
new_socket= accept(sockfd sockaddr *addr socklen_t *addrlen); 

Parámetros:

  • calcetín : descriptor de archivo de socket devuelto por socket() y bind().
  • dirección : puntero a una estructura sockaddr que contendrá la dirección IP y el número de puerto del cliente.
  • addrlen : puntero a una variable que especifica la longitud de la estructura de direcciones.

6. Enviar/Recibir

En este paso el servidor puede enviar o recibir datos del cliente.

Enviar(): para enviar datos al cliente

C
send(sockfd *buf len flags); 

Parámetros:

  • calcetín : descriptor de archivo de socket devuelto por la función socket().
  • buf : puntero al búfer que contiene los datos a enviar.
  • solo : número de bytes de datos a enviar.
  • banderas : número entero que especifica varias opciones sobre cómo se envían los datos; normalmente se utiliza 0 para el comportamiento predeterminado.

Recibir() : para recibir los datos del cliente.

C
recv( sockfd *buf len flags); 

Parámetros:

  • calcetín : descriptor de archivo de socket devuelto por la función socket().
  • buf : puntero al búfer que contiene los datos que se almacenarán.
  • solo : número de bytes de datos a enviar.
  • banderas : número entero que especifica varias opciones sobre cómo se envían los datos; normalmente se utiliza 0 para el comportamiento predeterminado.

6. Cerrar

Una vez completado el intercambio de información, el servidor cierra el socket utilizando la función close() y libera los recursos del sistema.

C
close(fd); 

Parámetros:

  • fd: descriptor de archivo del socket.

Crear un proceso del lado del cliente

Siga los pasos a continuación para crear un proceso del lado del cliente:

1. Conexión de enchufe

Este paso implica la creación del socket, que se realiza de la misma manera que la creación del socket del servidor.

2. Conectar

La llamada al sistema connect() conecta el socket 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.

C++
connect(sockfd sockaddr *addr socklen_t addrlen); 

Parámetros

  • calcetín : descriptor de archivo de socket devuelto por la función socket().
  • dirección : puntero a la estructura sockaddr que contiene la dirección IP y el número de puerto del servidor.
  • addrlen : tamaño de la dirección.

3. Enviar/Recibir

En este paso, el cliente puede enviar o recibir datos del servidor, lo cual se realiza utilizando las funciones enviar() y recibir() de manera similar a cómo el servidor envía/recibe datos del cliente.

4. Cerrar

Una vez que se completa el intercambio de información, el cliente también necesita cerrar el socket creado y liberar los recursos del sistema usando la función close() de la misma manera que lo hace el servidor.

Problemas comunes y sus soluciones en la programación de sockets

  • Fallos de conexión: Para evitar fallos de conexión debemos asegurarnos de que el cliente esté intentando conectarse al sitio correcto. dirección IP y puerto .
  • Errores de vinculación de puertos: Estos errores ocurren cuando otra aplicación ya está utilizando un puerto; en este escenario, el enlace a ese puerto fallará. Intente usar un puerto diferente o cierre la aplicación anterior usando el puerto.
  • Zócalos de bloqueo: Por defecto, los sockets están bloqueando. Esto significa que llamadas como aceptar() o recv() esperarán indefinidamente si no hay datos o conexión del cliente. Puede configurar el socket en modo sin bloqueo si es necesario.
Crear cuestionario