stringtranslate.com

Zócalos Berkeley

Un socket de Berkeley ( BSD ) es una interfaz de programación de aplicaciones (API) para sockets de dominio de Internet y sockets de dominio Unix , que se utiliza para la comunicación entre procesos (IPC). Se suele implementar como una biblioteca de módulos enlazables. Se originó con el sistema operativo Unix 4.2BSD , que se lanzó en 1983.

Un socket es una representación abstracta ( identificador ) del punto final local de una ruta de comunicación de red. La API de sockets de Berkeley lo representa como un descriptor de archivo ( identificador de archivo ) en la filosofía Unix que proporciona una interfaz común para la entrada y salida de flujos de datos.

Los sockets Berkeley evolucionaron con pocas modificaciones desde un estándar de facto hasta convertirse en un componente de la especificación POSIX . El término sockets POSIX es esencialmente sinónimo de sockets Berkeley , pero también se los conoce como sockets BSD , en reconocimiento a la primera implementación en la distribución de software Berkeley .

Historia e implementaciones

Los sockets Berkeley se originaron con el sistema operativo Unix 4.2BSD , lanzado en 1983, como una interfaz de programación. Sin embargo, no fue hasta 1989 que la Universidad de California en Berkeley pudo lanzar versiones del sistema operativo y la biblioteca de redes libres de las restricciones de licencia del Unix propietario de AT&T Corporation .

Todos los sistemas operativos modernos implementan una versión de la interfaz de sockets de Berkeley. Se convirtió en la interfaz estándar para las aplicaciones que se ejecutan en Internet . Incluso la implementación de Winsock para MS Windows, creada por desarrolladores no afiliados, sigue de cerca el estándar.

La API de sockets BSD está escrita en el lenguaje de programación C. La mayoría de los demás lenguajes de programación proporcionan interfaces similares, generalmente escritas como una biblioteca contenedora basada en la API C. [1]

Zócalos BSD y POSIX

A medida que la API de sockets de Berkeley evolucionó y finalmente dio lugar a la API de sockets POSIX, [2] ciertas funciones quedaron obsoletas o se eliminaron y se reemplazaron por otras. La API POSIX también está diseñada para ser reentrante y admite IPv6.

Alternativas

La API de interfaz de capa de transporte (TLI) basada en STREAMS ofrece una alternativa a la API de socket. Muchos sistemas que proporcionan la API TLI también proporcionan la API de socket de Berkeley.

Los sistemas que no son Unix suelen exponer la API de socket de Berkeley con una capa de traducción a una API de red nativa. Plan 9 [3] y Genode [4] utilizan API de sistemas de archivos con archivos de control en lugar de descriptores de archivos.

Archivos de encabezado

La interfaz de socket de Berkeley se define en varios archivos de encabezado. Los nombres y el contenido de estos archivos difieren ligeramente entre implementaciones. En general, incluyen:

Funciones de la API de socket

Diagrama de flujo de transacción cliente-servidor utilizando sockets con el Protocolo de Control de Transmisión (TCP).

La API de socket de Berkeley normalmente proporciona las siguientes funciones:

enchufe

La función socket() crea un punto final para la comunicación y devuelve un descriptor de archivo para el socket. Utiliza tres argumentos:

La función devuelve -1 si se produjo un error. De lo contrario, devuelve un entero que representa el descriptor recién asignado.

unir

bind() asocia un socket con una dirección. Cuando se crea un socket con socket() , solo se le asigna una familia de protocolos, pero no una dirección. Esta asociación debe realizarse antes de que el socket pueda aceptar conexiones de otros hosts. La función tiene tres argumentos:

bind() devuelve 0 en caso de éxito y -1 si ocurre un error.

escuchar

Una vez que se ha asociado un socket con una dirección, listen() lo prepara para las conexiones entrantes. Sin embargo, esto solo es necesario para los modos de datos orientados a la transmisión (orientados a la conexión), es decir, para los tipos de socket ( SOCK_STREAM , SOCK_SEQPACKET ). listen() requiere dos argumentos:

Una vez que se acepta una conexión, se la saca de la cola. Si se realiza correctamente, se devuelve 0. Si se produce un error, se devuelve -1.

aceptar

Cuando una aplicación está escuchando conexiones orientadas a flujos de otros hosts, se le notifican dichos eventos (cf. función select() ) y debe inicializar la conexión utilizando la función accept() . Crea un nuevo socket para cada conexión y elimina la conexión de la cola de escucha. La función tiene los siguientes argumentos:

accept() devuelve el nuevo descriptor de socket para la conexión aceptada o el valor -1 si se produce un error. Toda comunicación posterior con el host remoto se realiza ahora a través de este nuevo socket.

Los sockets de datagramas no requieren procesamiento por accept() ya que el receptor puede responder inmediatamente a la solicitud utilizando el socket de escucha.

conectar

connect() establece un enlace de comunicación directa con un host remoto específico identificado por su dirección a través de un socket, identificado por su descriptor de archivo.

Cuando se utiliza un protocolo orientado a conexión , esto establece una conexión. Ciertos tipos de protocolos no requieren conexión, en particular el Protocolo de datagramas de usuario . Cuando se utiliza con protocolos sin conexión, connect define la dirección remota para enviar y recibir datos, lo que permite el uso de funciones como send y recv . En estos casos, la función connect impide la recepción de datagramas de otras fuentes.

connect() devuelve un entero que representa el código de error: 0 representa éxito, mientras que –1 representa un error. Históricamente, en los sistemas derivados de BSD, el estado de un descriptor de socket es indefinido si la llamada a connect falla (como se especifica en la Especificación Única de Unix), por lo tanto, las aplicaciones portátiles deben cerrar el descriptor de socket inmediatamente y obtener un nuevo descriptor con socket(), en el caso de que la llamada a connect() falle. [5]

obtener hostbyname y obtener hostbyaddr

Las funciones gethostbyname() y gethostbyaddr() se utilizan para resolver nombres de host y direcciones en el sistema de nombres de dominio u otros mecanismos de resolución del host local (por ejemplo, búsqueda en /etc/hosts). Devuelven un puntero a un objeto de tipo struct hostent , que describe un host de protocolo de Internet . Las funciones utilizan los siguientes argumentos:

Las funciones devuelven un puntero NULL en caso de error, en cuyo caso se puede comprobar el entero externo h_errno para ver si se trata de un error temporal o de un host no válido o desconocido. De lo contrario, se devuelve una estructura hostent * válida.

Estas funciones no son estrictamente un componente de la API de socket BSD, pero se utilizan a menudo junto con las funciones de la API para buscar un host. Estas funciones ahora se consideran interfaces heredadas para consultar el sistema de nombres de dominio. Se han definido nuevas funciones que son completamente independientes del protocolo (compatibles con IPv6). Estas nuevas funciones son getaddrinfo() y getnameinfo() , y se basan en una nueva estructura de datos addrinfo . [6]

Este par de funciones apareció al mismo tiempo que la API de socket BSD propiamente dicha en 4.2BSD (1983), [7] el mismo año en que se creó por primera vez el DNS. Las versiones anteriores no consultaban al DNS y solo realizaban búsquedas en /etc/hosts. La versión 4.3BSD (1984) agregó el DNS de una manera rudimentaria. La implementación actual que utiliza Name Service Switch deriva de Solaris y más tarde de NetBSD 1.4 (1999). [8] Inicialmente definido para NIS+ , NSS hace que el DNS sea solo una de las muchas opciones de búsqueda por parte de estas funciones y su uso puede deshabilitarse incluso hoy en día. [9]

Protocolo y dirección de familias

La API de socket de Berkeley es una interfaz general para redes y comunicación entre procesos, y admite el uso de varios protocolos de red y arquitecturas de direcciones.

A continuación se muestra una muestra de familias de protocolos (precedidas por el identificador simbólico estándar) definidas en una implementación moderna de Linux o BSD :

Se crea un socket para comunicaciones con la socket()función, especificando la familia de protocolo deseada ( identificador PF_ ) como argumento.

El concepto de diseño original de la interfaz de socket distinguía entre tipos de protocolos (familias) y los tipos de direcciones específicos que cada uno puede utilizar. Se previó que una familia de protocolos puede tener varios tipos de direcciones. Los tipos de direcciones se definieron mediante constantes simbólicas adicionales, utilizando el prefijo AF en lugar de PF . Los identificadores AF están destinados a todas las estructuras de datos que tratan específicamente con el tipo de dirección y no con la familia de protocolos. Sin embargo, este concepto de separación de protocolo y tipo de dirección no ha encontrado apoyo de implementación y las constantes AF se definieron mediante el identificador de protocolo correspondiente, dejando la distinción entre constantes AF y PF como un argumento técnico sin consecuencias prácticas. De hecho, existe mucha confusión en el uso correcto de ambas formas. [11]

La especificación POSIX.1—2008 no especifica ninguna constante PF , sino solo constantes AF [12]

Zócalos sin procesar

Los sockets sin formato proporcionan una interfaz sencilla que evita el procesamiento por parte de la pila TCP/IP del host. Permiten la implementación de protocolos de red en el espacio de usuario y ayudan a depurar la pila de protocolos. [13] Algunos servicios, como ICMP , utilizan sockets sin formato que operan en la capa de Internet del modelo TCP/IP.

Modo de bloqueo y no bloqueo

Los sockets Berkeley pueden funcionar en uno de dos modos: bloqueo o no bloqueo.

Un socket de bloqueo no devuelve el control hasta que haya enviado (o recibido) algunos o todos los datos especificados para la operación. Es normal que un socket de bloqueo no envíe todos los datos. La aplicación debe verificar el valor de retorno para determinar cuántos bytes se han enviado o recibido y debe reenviar cualquier dato que no se haya procesado aún. [14] Al utilizar sockets de bloqueo, se debe prestar especial atención a accept(), ya que aún puede bloquearse después de indicar legibilidad si un cliente se desconecta durante la fase de conexión.

Un socket no bloqueante devuelve lo que haya en el búfer de recepción y continúa inmediatamente. Si no se escriben correctamente, los programas que utilizan sockets no bloqueantes son particularmente susceptibles a condiciones de carrera debido a variaciones en la velocidad del enlace de la red. [ cita requerida ]

Un socket normalmente se configura en modo de bloqueo o no bloqueo utilizando las funciones fcntl y ioctl .

Zócalos de terminación

El sistema operativo no libera los recursos asignados a un socket hasta que este se cierra. Esto es especialmente importante si la llamada de conexión falla y se vuelve a intentar.

Cuando una aplicación cierra un socket, solo se destruye la interfaz con el socket. Es responsabilidad del núcleo destruir el socket internamente. A veces, un socket puede entrar en un estado TIME_WAIT , en el lado del servidor, durante hasta 4 minutos. [15]

En los sistemas SVR4, el uso de close()puede descartar datos. shutdown()En estos sistemas, puede ser necesario el uso de o SO_LINGER para garantizar la entrega de todos los datos. [16]

Ejemplo de cliente-servidor utilizando TCP

El Protocolo de Control de Transmisión (TCP) es un protocolo orientado a la conexiónsocket() que proporciona una variedad de funciones de rendimiento y corrección de errores para la transmisión de flujos de bytes. Un proceso crea un socket TCP llamando a la función con los parámetros para la familia de protocolos ( PF INET , PF_INET6 ), el modo de socket para los sockets de flujo ( SOCK_STREAM ) y el identificador de protocolo IP para TCP ( IPPROTO_TCP ).

Servidor

Para establecer un servidor TCP se siguen los siguientes pasos básicos:

El siguiente programa crea un servidor TCP que escucha en el puerto número 1100:

 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include < stdlib.h> #include <string.h> #include <unistd.h> int main ( void ) { struct sockaddr_in sa ; int SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( SocketFD == -1 ) { perror ( "no se puede crear el socket" ); exit ( EXIT_FAILURE ); } memset ( & sa , 0 , sizeof sa ); sa . sin_family = AF_INET ; sa . sin_port = htons ( 1100 ); sa . sin_addr . s_addr = htonl ( INADDR_ANY ); si ( bind ( SocketFD ,( struct sockaddr * ) & sa , sizeof sa ) == -1 ) { perror ( "error en el enlace" ); cerrar ( SocketFD ); salir ( EXIT_FAILURE ); } si ( listen ( SocketFD , 10 ) == -1 ) { perror ( "error en la escucha" ); cerrar ( SocketFD ); salir ( EXIT_FAILURE ); } para (;;) { int ConnectFD = accept ( SocketFD , NULL , NULL ); si ( ConnectFD == -1 ) { perror ( "error en la aceptación" ); cerrar ( SocketFD );                                                                                               salir ( EXIT_FAILURE ); } /* realizar operaciones de lectura y escritura ...  read(ConnectFD, buff, size)  */ if ( shutdown ( ConnectFD , SHUT_RDWR ) == -1 ) { perror ( "falló el apagado" ); cerrar ( ConnectFD ); cerrar ( SocketFD ); salir ( EXIT_FAILURE ); } cerrar ( ConnectFD ); }                  cerrar ( SocketFD ); devolver EXIT_SUCCESS ; }   

Cliente

La programación de una aplicación cliente TCP implica los siguientes pasos:

 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include < stdlib.h> #include <string.h> #include <unistd.h> int main ( void ) { struct sockaddr_in sa ; int res ; int SocketFD ;                           SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( SocketFD == -1 ) { perror ( "no se puede crear el socket" ); exit ( EXIT_FAILURE ); } memset ( & sa , 0 , sizeof sa ); sa . sin_family = AF_INET ; sa . sin_port = htons ( 1100 ); res = inet_pton ( AF_INET , "192.168.1.3" , & sa . sin_addr );                              if ( connect ( SocketFD , ( struct sockaddr * ) & sa , sizeof sa ) ​​== -1 ) { perror ( "la conexión falló" ); close ( SocketFD ); exit ( EXIT_FAILURE ); } /* realizar operaciones de lectura y escritura ... */ close ( SocketFD ); return EXIT_SUCCESS ; }                    

Ejemplo de cliente-servidor utilizando UDP

El Protocolo de datagramas de usuario (UDP) es un protocolo sin conexión que no garantiza la entrega. Los paquetes UDP pueden llegar desordenados, varias veces o no llegar en absoluto. Debido a este diseño minimalista, UDP tiene una sobrecarga considerablemente menor que TCP. Al no tener conexión, no existe el concepto de flujo o conexión permanente entre dos hosts. Estos datos se denominan datagramas ( sockets de datagramas ).

El espacio de direcciones UDP, el espacio de los números de puerto UDP (en la terminología ISO, los TSAP ), es completamente distinto del de los puertos TCP.

Servidor

Una aplicación puede configurar un servidor UDP en el puerto número 7654 de la siguiente manera. El programa contiene un bucle infinito que recibe datagramas UDP con la función recvfrom() .

#include <stdio.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> /* para cerrar() para socket */ #include <stdlib.h>        int main ( void ) { int sock ; struct sockaddr_in sa ; char buffer [ 1024 ]; ssize_t recsize ; socklen_t fromlen ;              memset ( & sa , 0 , tamaño de sa ); sa . sin_familia = AF_INET ; sa . sin_addr . s_addr = htonl ( INADDR_ANY ); sa . sin_port = htons ( 7654 ); fromlen = tamaño de sa ;                 sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP );     if ( bind ( sock , ( struct sockaddr * ) & sa , sizeof sa ) ​​== -1 ) { perror ( "error error en el enlace" ); close ( sock ); exit ( EXIT_FAILURE ); }               para (;;) { recsize = recvfrom ( sock , ( void * ) buffer , sizeof buffer , 0 , ( struct sockaddr * ) & sa , & fromlen ); si ( recsize < 0 ) { fprintf ( stderr , "%s \n " , strerror ( errno )); salir ( EXIT_FAILURE ); } printf ( " recsize: %d \n " , ( int ) recsize ); dormir ( 1 ); printf ( " datagrama: %.*s \n " , ( int ) recsize , buffer ); } }                             

Cliente

El siguiente es un programa cliente para enviar un paquete UDP que contiene la cadena "¡Hola mundo!" a la dirección 127.0.0.1 en el puerto número 7654.

#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h>         int main ( void ) { int sock ; struct sockaddr_in sa ; int bytes_sent ; char buffer [ 200 ]; strcpy ( buffer , "¡Hola mundo!" ); /* crear un socket de Internet, datagrama, usando UDP */ sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( sock == -1 ) { /* si el socket no se pudo inicializar, salir */ printf ( "Error al crear el socket" ); exit ( EXIT_FAILURE ); } /* Poner en cero la dirección del socket */ memset ( & sa , 0 , sizeof sa ); /* La dirección es IPv4 */ sa . sin_family = AF_INET ; /* Las direcciones IPv4 son uint32_t, convierte una representación de cadena de los octetos al valor apropiado */ sa . sin_addr . s_addr = inet_addr ( "127.0.0.1" ); /* los sockets son cortos sin signo, htons(x) asegura que x esté en el orden de bytes de la red, establece el puerto en 7654 */ sa.sin_port = htons(7654); bytes_sent = sendto ( sock , buffer , strlen ( buffer ) , 0 , ( struct sockaddr * ) & sa , sizeof sa ) ; if ( bytes_sent < 0 ) { printf ( " Error al enviar el paquete: %s \n " , strerror ( errno )); exit ( EXIT_FAILURE ); } close ( sock ); /* cierra el socket */ return 0 ; }                                                                          

En este código, buffer es un puntero a los datos que se enviarán y buffer_length especifica el tamaño de los datos.

Referencias

  1. ^ Por ejemplo, en el lenguaje de programación Ruby ruby-doc::Socket
  2. ^ "— Especificación POSIX.1-2008". Opengroup.org . Consultado el 26 de julio de 2012 .
  3. ^ "La Organización de Redes en el Plan 9".
  4. ^ "Pila TCP/IP de Linux como complemento VFS".
  5. ^ Stevens y Rago 2013, pág. 607.
  6. ^ POSIX.1-2004
  7. ^ gethostbyname(3)  –  Manual de funciones de la biblioteca de FreeBSD
  8. ^ Conill, Ariadne (27 de marzo de 2022). "la tragedia de gethostbyname". ariadne.space .
  9. ^ nsswitch.conf(5)  –  Manual de formatos de archivos de FreeBSD
  10. ^ https://manpages.debian.org/experimental/ax25-tools/netrom.4.en.html. {{cite web}}: Falta o está vacío |title=( ayuda )
  11. ^ Programación de redes UNIX Volumen 1, tercera edición: La API de redes de sockets, W. Richard Stevens, Bill Fenner, Andrew M. Rudoff, Addison Wesley, 2003.
  12. ^ "Especificaciones básicas de Open Group, número 7". Pubs.opengroup.org . Consultado el 26 de julio de 2012 .
  13. ^ "Sockets TCP/IP sin procesar: aplicaciones Win32". 19 de enero de 2022.
  14. ^ "Guía de Beej para la programación en red". Beej.us. 5 de mayo de 2007. Consultado el 26 de julio de 2012 .
  15. ^ "terminación de sockets". Softlab.ntua.gr . Consultado el 26 de julio de 2012 .
  16. ^ "ntua.gr - Programación de sockets UNIX en C - Preguntas frecuentes: Preguntas sobre clientes y servidores (TCP/SOCK_STREAM)". Softlab.ntua.gr . Consultado el 26 de julio de 2012 .

La definición estándar de iure de la interfaz Sockets está contenida en el estándar POSIX, conocido como:

La información sobre esta norma y el trabajo en curso sobre ella está disponible en el sitio web de Austin.

Las extensiones IPv6 a la API de socket base están documentadas en RFC 3493 y RFC 3542.

Enlaces externos