stringtranslate.com

Enchufes Berkeley

Berkeley sockets es una interfaz de programación de aplicaciones (API) para sockets de Internet y sockets de dominio Unix , que se utiliza para la comunicación entre procesos (IPC). Se implementa comúnmente como una biblioteca de módulos enlazables. Se originó con el sistema operativo Unix 4.2BSD , que fue lanzado 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 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 les conoce como sockets BSD , en reconocimiento a la primera implementación en Berkeley Software Distribution .

Historia e implementaciones

Los sockets Berkeley se originaron con el sistema operativo Unix 4.2BSD , lanzado en 1983, como 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 socket Berkeley. Se convirtió en la interfaz estándar para 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 de C. [1]

Conectores BSD y POSIX

A medida que la API de socket Berkeley evolucionó y finalmente produjo la API de socket POSIX, [2] ciertas funciones quedaron obsoletas o eliminadas y reemplazadas por otras. La API POSIX también está diseñada para ser reentrante y es compatible con 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 Berkeley.

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

Archivos de encabezado

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

Funciones 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 ocurrió un error. De lo contrario, devuelve un número 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 se le asigna 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 se produce un error.

escuchar

Después de que un socket se haya asociado con una dirección, listening() lo prepara para las conexiones entrantes. Sin embargo, esto sólo 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 ). listening() requiere dos argumentos:

Una vez que se acepta una conexión, se retira de la cola. En caso de éxito, se devuelve 0. Si se produce un error, se devuelve -1.

aceptar

Cuando una aplicación está escuchando conexiones orientadas a transmisiones desde otros hosts, se le notifican dichos eventos (consulte la función select() ) y debe inicializar la conexión usando la función aceptar() . 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:

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

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

conectar

connect() establece un enlace de comunicación directo a 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 , se establece una conexión. Ciertos tipos de protocolos no tienen conexión, en particular el Protocolo de datagramas de usuario . Cuando se usa con protocolos sin conexión, connect define la dirección remota para enviar y recibir datos, lo que permite el uso de funciones como enviar y recibir . En estos casos, la función de conexión impide la recepción de datagramas de otras fuentes.

connect() devuelve un número entero que representa el código de error: 0 representa el éxito, mientras que –1 representa un error. Históricamente, en los sistemas derivados de BSD, el estado de un descriptor de socket no está definido si falla la llamada para conectarse (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 caso de que falle la llamada a connect(). [5]

gethostbyname y gethostbyaddr

Las funciones gethostbyname() y gethostbyaddr() se utilizan para resolver nombres y direcciones de host en el sistema de nombres de dominio o en 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 verificar el entero externo h_errno para ver si se trata de una falla 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 del socket BSD, pero a menudo se usan 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ó DNS por primera vez. Las primeras versiones no consultaban DNS y solo realizaban búsquedas en /etc/hosts. La versión 4.3BSD (1984) añadió DNS de forma tosca. La implementación actual que utiliza Name Service Switch deriva de Solaris y posterior NetBSD 1.4 (1999). [8] Inicialmente definido para NIS+ , NSS hace que DNS sea solo una de las muchas opciones de búsqueda mediante 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 enumera 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 pudiera 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 del tipo de dirección y no de la familia de protocolos. Sin embargo, este concepto de separación de protocolo y tipo de dirección no ha encontrado soporte 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 adecuado de ambas formas. [11]

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

Enchufes crudos

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

Modo de bloqueo y no bloqueo

Los enchufes Berkeley pueden funcionar en uno de dos modos: con bloqueo o sin 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 aún no haya sido procesado. [14] Cuando se utilizan sockets de bloqueo, se debe prestar especial atención a aceptar() ya que aún puede bloquearse después de indicar legibilidad si un cliente se desconecta durante la fase de conexión.

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

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

Tomas terminales

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

Cuando una aplicación cierra un socket, solo se destruye la interfaz del 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. Es posible que se requiera el uso de shutdown()SO_LINGER en estos sistemas para garantizar la entrega de todos los datos. [dieciséis]

Ejemplo cliente-servidor usando TCP

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

Servidor

Establecer un servidor TCP implica los siguientes pasos básicos:

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

 #incluir <sys/types.h> #incluir <sys/socket.h> #incluir <netinet/in.h> #incluir <arpa/inet.h> #incluir <stdio.h> #incluir <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" ); salir ( EXIT_FAILURE ); } memset ( & sa , 0 , tamaño de sa ); sa . sin_familia = AF_INET ; sa . sin_port = htons ( 1100 ); sa . sin_addr . s_addr = htonl ( INADDR_ANY ); if ( bind ( SocketFD , ( struct sockaddr * ) & sa , sizeof sa ) ​​== -1 ) { perror ( "bind falló" ); cerrar ( SocketFD ); salir ( EXIT_FAILURE ); } if ( escucha ( SocketFD , 10 ) == -1 ) { perror ( "falló la escucha" ); cerrar ( SocketFD ); salir ( EXIT_FAILURE ); } para (;;) { int ConnectFD = aceptar ( SocketFD , NULL , NULL ); if ( ConnectFD == -1 ) { perror ( "aceptación fallida" ); cerrar ( SocketFD );                                                                                               salir ( EXIT_FAILURE ); } /* realizar operaciones de lectura y escritura...  read(ConnectFD, buff, size)  */ if ( apagado ( ConnectFD , SHUT_RDWR ) == -1 ) { perror ( "fallo de apagado" ); cerrar ( ConectarFD ); cerrar ( SocketFD ); salir ( EXIT_FAILURE ); } cerrar ( ConectarFD ); }                  cerrar ( SocketFD ); devolver EXIT_SUCCESS ; }   

Cliente

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

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

Ejemplo cliente-servidor usando UDP

El Protocolo de datagramas de usuario (UDP) es un protocolo sin conexión sin garantía de entrega. Los paquetes UDP pueden llegar desordenados, varias veces o no llegar en absoluto. Debido a este diseño mínimo, UDP tiene una sobrecarga considerablemente menor que TCP. Estar sin conexión significa que no existe el concepto de transmisión o conexión permanente entre dos hosts. Estos datos se denominan datagramas ( datagram sockets ).

El espacio de direcciones UDP, el espacio de los números de puerto UDP (en terminología ISO, los TSAP ), está completamente separado 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() .

#incluir <stdio.h> #incluir <errno.h> #incluir <cadena.h> #incluir <sys/socket.h> #incluir < sys/types.h> #incluir <netinet/in.h> #incluir <unistd.h> /* para close() para socket */ #include <stdlib.h>        int principal ( vacío ) { int calcetín ; estructura sockaddr_in sa ; búfer de caracteres [ 1024 ]; ssize_t recsize ; calcetín_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 ;                 calcetín = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP );     if ( bind ( sock , ( struct sockaddr * ) & sa , sizeof sa ) == -1 ) { perror ( "error de enlace fallido" ); cerrar ( calcetín ); salir ( EXIT_FAILURE ); }               for (;;) { recsize = recvfrom ( sock , ( void * ) buffer , sizeof buffer , 0 , ( struct sockaddr * ) & sa , & fromlen ); if ( 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.

#incluir <stdlib.h> #incluir <stdio.h > #incluir <errno.h> #incluir <cadena.h> #incluir <sys/socket.h> #incluir <sys/types.h> #incluir <netinet /in.h> #include <unistd.h> #incluye <arpa/inet.h>         int principal ( vacío ) { int calcetín ; estructura sockaddr_in sa ; int bytes_enviados ; búfer de caracteres [ 200 ]; strcpy ( búfer , "¡hola mundo!" ); /* crear un datagrama, un socket de Internet usando UDP */ sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( sock == -1 ) { /* si el socket no se pudo inicializar, salga */ printf ( "Error al crear el socket" ); salir ( EXIT_FAILURE ); } /* Dirección del socket de puesta a cero */ memset ( & sa , 0 , sizeof sa ); /* La dirección es IPv4 */ sa . sin_familia = 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 firmar, htons(x) garantiza que x esté en el orden de bytes de la red, configure 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 )); salir ( EXIT_FAILURE ); } cerrar ( calcetín ); /* cerrar 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 ej. 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 FreeBSD
  8. ^ Conill, Ariadna (27 de marzo de 2022). "la tragedia de gethostbyname". ariadna.espacio .
  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: API de redes de sockets, W. Richard Stevens, Bill Fenner, Andrew M. Rudoff, Addison Wesley, 2003.
  12. ^ "Las especificaciones básicas de Open Group, número 7". Pubs.opengroup.org . Consultado el 26 de julio de 2012 .
  13. ^ "Sockets sin formato TCP/IP: aplicaciones Win32". 19 de enero de 2022.
  14. ^ "Guía de Beej para la programación de redes". Beej.us. 2007-05-05 . Consultado el 26 de julio de 2012 .
  15. ^ "enchufes terminales". 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 jure de la interfaz Sockets está contenida en el estándar POSIX, conocido como:

La información sobre este estándar y el trabajo en curso al respecto está disponible en el sitio web de Austin.

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

enlaces externos