Enchufes de 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). Comúnmente se implementa como una biblioteca de módulos enlazables. Se originó con el sistema operativo 4.2BSD Unix, que se lanzó en 1983.
Un socket es una representación abstracta (identificador) del extremo local de una ruta de comunicación de red. La API de sockets de Berkeley lo representa como un descriptor de archivo (manejador de archivo) en la filosofía de Unix que proporciona una interfaz común para la entrada y salida de flujos de datos.
Los sockets de Berkeley evolucionaron con pocas modificaciones de un estándar de facto a un componente de la especificación POSIX. El término conectores POSIX es esencialmente sinónimo de conectores Berkeley, pero también se conocen como conectores BSD, reconociendo la primera implementación en Berkeley Software Distribution..
Historia e implementaciones
Los sockets de Berkeley se originaron con el sistema operativo 4.2BSD Unix, lanzado en 1983, como una interfaz de programación. Sin embargo, no fue sino hasta 1989 que la Universidad de California, 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 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, normalmente escritas como una biblioteca contenedora basada en la API de C.
Conectores BSD y POSIX
A medida que la API de socket de Berkeley evolucionó y finalmente produjo la API de socket POSIX, 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.
Medida | BSD | POSIX |
---|---|---|
Conversión de la dirección de texto a la dirección envasada | inet_aton | inet_pton |
Conversión de dirección embalada a dirección de texto | inet_ntoa | inet_ntop |
Búsqueda avanzada para el nombre de host / servicio | gethostbyname, gethostbyaddr, getservbyname, getservbyport | getaddrinfo |
Búsqueda inversa de nombre de host/servicio | gethostbyaddr, getservbyport | getnameinfo |
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 de TLI también proporcionan la API de socket de Berkeley.
Los sistemas que no son Unix a menudo exponen la API de socket de Berkeley con una capa de traducción a una API de red nativa. Plan 9 y Genode usan API de sistema 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:
Archivo | Descripción |
---|---|
sys/socket.h | Funciones de toma de corriente y estructuras de datos. |
netinet/in.h | AF_INET y AF_INET6 dirigen a las familias y sus correspondientes familias de protocolo, PF_INET y PF_INET6. Estos incluyen direcciones IP estándar y números de puerto TCP y UDP. |
sys/un.h | PF_UNIX y PF_ Familia de dirección LOCAL. Se utiliza para la comunicación local entre programas que se ejecutan en el mismo ordenador. |
arpa/inet.h | Funciones para manipular direcciones IP numéricas. |
netdb.h | Funciones para traducir nombres de protocolo y nombres de host en direcciones numéricas. Busca datos locales y servicios de nombre. |
Funciones API de socket
La API de socket de Berkeley normalmente proporciona las siguientes funciones:
- socket() crea un nuevo socket de cierto tipo, identificado por un número entero, y asigna recursos del sistema a él.
- bind() se utiliza típicamente en el lado del servidor, y asocia un socket con una estructura de dirección de socket, es decir, una dirección IP local especificada y un número de puerto.
- listen() se utiliza en el lado servidor, y hace que una toma TCP atada entre el estado de escucha.
- connect() se utiliza en el lado cliente, y asigna un número de puerto local gratuito a un socket. En caso de una toma TCP, provoca un intento de establecer una nueva conexión TCP.
- acepta() se utiliza en el lado servidor. Acepta un intento recibido de entrada para crear una nueva conexión TCP del cliente remoto, y crea un nuevo socket asociado con el par de direcciones de toma de esta conexión.
- send(), recv(), sendto(), y recvfrom() se utilizan para enviar y recibir datos. Funciones estándar escrito() y read() también se puede utilizar.
- close() hace que el sistema libere los recursos asignados a un socket. En caso de TCP, la conexión se termina.
- gethostbyname() y gethostbyaddr() se utilizan para resolver los nombres y direcciones de los anfitriones. IPv4 sólo.
- getaddrinfo() y freeaddrinfo() se utilizan para resolver los nombres y direcciones de los anfitriones. IPv4, IPv6.
- select() se utiliza para suspender, esperando que una o más de una lista proporcionada de sockets estén listos para leer, listos para escribir, o que tengan errores.
- votación() se utiliza para comprobar el estado de una toma en un conjunto de tomas. El conjunto puede ser probado para ver si cualquier toma puede ser escrita, leído o si se produjo un error.
- getockopt() se utiliza para recuperar el valor actual de una opción de toma en particular para el socket especificado.
- setsockopt() se utiliza para establecer una opción de socket particular para el socket especificado.
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:
- dominio, que especifica la familia protocolo de la toma creada. Por ejemplo:
- AF_INET protocolo de red IPv4 (sólo IPv4)
- AF_INET6 IPv6 (y en algunos casos, compatible con IPv4)
- AF_UNIX para toma local (utilizando un nodo especial del sistema de archivos)
- Tipo, uno de:
- SOCK_STREAM (servicio fiable orientado a la corriente o Stream Sockets)
- SOCK_DGRAM (servicio de datagramas o hojas de datos)
- SOCK_SEQPACKET (servicio de paquete secuenciable)
- SOCK_RAW (protolos de rocío encima de la capa de red)
- protocolo especificando el protocolo de transporte real a utilizar. Los más comunes son IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP. Estos protocolos se especifican en el archivo netinet/in.h. El valor 0 puede ser utilizado para seleccionar un protocolo predeterminado del dominio y tipo seleccionados.
La función devuelve -1 si ocurre un error. De lo contrario, devuelve un número entero que representa el descriptor recién asignado.
Enlazar
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:
- Sockfd, un descriptor que representa el socket
- my_addr, un puntero a un sockaddr estructura que representa la dirección para atar.
- addrlen, un campo de tipo socklen_t especificando el tamaño del sockaddr estructura.
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, 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:
- Sockfd, un descriptor de socket válido.
- pendientes, un entero representando el número de conexiones pendientes que se pueden apagar en cualquier momento. El sistema operativo generalmente coloca una tapa en este valor.
Una vez que se acepta una conexión, se quita de la cola. En caso de éxito, se devuelve 0. Si ocurre un error, se devuelve -1..
Aceptar
Cuando una aplicación está escuchando conexiones orientadas a la transmisión de otros hosts, se le notifican dichos eventos (cf. función select()) y debe inicializar la conexión usando 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:
- Sockfd, el descriptor de la toma de escucha que tiene la conexión apagada.
- cliaddr, un puntero a una estructura sockaddr para recibir la información de la dirección del cliente.
- addrlen, un puntero a un socklen_t ubicación que especifica el tamaño de la estructura de dirección cliente pasó a aceptar(). Cuando acepta() devuelve, esta ubicación contiene el tamaño (en bytes) de la estructura.
accept() devuelve el nuevo descriptor de socket para la conexión aceptada, o el valor -1 si ocurre un error. Toda la comunicación posterior con el host remoto ahora ocurre 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 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 la conexión, esto establece una conexión. Ciertos tipos de protocolos no tienen conexión, sobre todo 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 send y recv. 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 é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 la llamada a conectar 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 obtenga un nuevo descriptor con socket(), en caso de que falle la llamada a connect().
Gethostbyname y gethostbyaddr
Las funciones gethostbyname() y gethostbyaddr() se utilizan para resolver nombres de host y direcciones 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:
- Nombre especifica el nombre DNS del host.
- addr especifica un puntero a un struct in_addr que contiene la dirección del anfitrión.
- Len especifica la longitud, en bytes, de addr.
- Tipo especifica el tipo de dirección de la familia (por ejemplo, AF_INET) de la dirección anfitriona.
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. falla o un host inválido o desconocido. De lo contrario, se devuelve un struct hostent * válido.
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. Además, 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.
Familias de protocolo y dirección
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:
Identifier | Función o uso |
---|---|
PF_LOCAL, PF_UNIX, PF_FILE | Local a host (pipes y archivo-dominio) |
PF_INET | Versión del Protocolo de Internet 4 |
PF_AX25 | Amateur Radio AX.25 |
PF_IPX | Novell Internetwork Packet Exchange |
PF_APPLETALK | AppleTalk |
PF_NETROM | Radio aficionado NetROM (relacionado con AX.25) |
PF_BRIDGE | Puente multiprotocolo |
PF_ATMPVC | Modo de Transferencia Asincrónica Circuitos Virtuales Permanentes |
PF_ATMSVC | Modo de transferencia asincrónica Interruptor de circuitos virtuales |
PF_INET6 | Internet Protocol version 6 |
PF_DECnet | Reservado para el proyecto DECnet |
PF_NETBEUI | Reservado para 802.2LLC proyecto |
PF_SECURITY | Seguridad llamada pseudo AF |
PF_KEY | API de gestión clave PF_KEY |
PF_NETLINK, PF_ROUTE | routing API |
PF_PACKET | Tomas de captura de paquete |
PF_ECONET | Acorn Econet |
PF_SNA | Linux Systems Network Architecture (SNA) Project |
PF_IRDA | Tomas de IrDA |
PF_PPPOX | PPP sobre tomas X |
PF_WANPIPE | Tomas de API de Sangoma Wanpipe |
PF_BLUETOOTH | Tomas Bluetooth |
Se crea un socket para comunicaciones con el socket()
, especificando la familia de protocolo deseada (PF_-identifier) como argumento.
El concepto de diseño original de la interfaz de socket distinguía entre tipos de protocolo (familias) y los tipos de dirección específicos que cada uno podía usar. Se imaginó 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 se ocupan 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 fueron definidas por el identificador de protocolo correspondiente, dejando la distinción entre 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.
La especificación POSIX.1—2008 no especifica ninguna constante PF, sino solo constantes AF
Enchufes sin procesar
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. Algunos servicios, como ICMP, que operan en la capa de Internet del modelo TCP/IP utilizan sockets sin procesar.
Modo de bloqueo y no bloqueo
Los enchufes 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 devuelto para determinar cuántos bytes se han enviado o recibido y debe volver a enviar cualquier dato que aún no haya sido procesado. Al usar sockets de bloqueo, se debe prestar especial atención a accept(), ya que aún puede bloquearse después de indicar la legibilidad si un cliente se desconecta durante la fase de conexión.
Un socket sin bloqueo devuelve lo que está en el búfer de recepción y continúa inmediatamente. Si no se escriben correctamente, los programas que usan sockets sin bloqueo son particularmente susceptibles a las condiciones de carrera debido a las variaciones en la velocidad del enlace de la red.
Un socket normalmente se establece en modo de bloqueo o no bloqueo mediante las funciones fcntl e ioctl.
Zócalos de terminación
El sistema operativo no libera los recursos asignados a un socket hasta que se cierra el socket. Esto es especialmente importante si la llamada conectar falla y se volverá a intentar.
Cuando una aplicación cierra un socket, solo se destruye la interfaz del socket. Es responsabilidad del kernel destruir el socket internamente. A veces, un socket puede entrar en un estado TIME_WAIT, en el lado del servidor, por hasta 4 minutos.
En los sistemas SVR4, uso de close()
puede descartar datos. Es posible que se requiera el uso de shutdown()
o SO_LINGER en estos sistemas para garantizar la entrega de todos los datos.
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 funciones de rendimiento y corrección de errores para la transmisión de secuencias de bytes. Un proceso crea un socket TCP llamando al socket()
función con los parámetros para la familia de protocolos (PF INET, PF_INET6), el modo de socket para Stream Sockets ( SOCK_STREAM), y el identificador de protocolo IP para TCP (IPPROTO_TCP).
Servidor
Establecer un servidor TCP implica los siguientes pasos básicos:
- Crear una toma TCP con una llamada socket().
- Colocar la toma en el puerto de escucha (bind()Después de establecer el número de puerto.
- Preparación de la toma para escuchar conexiones (haciendo una toma de escucha), con una llamada a listen().
- Aceptar conexiones entrantes ()acepta()). Esto bloquea el proceso hasta que se reciba una conexión entrante, y devuelve un descriptor de socket para la conexión aceptada. El descriptor inicial sigue siendo un descriptor de escucha, y acepta() se puede llamar de nuevo en cualquier momento con esta toma, hasta que esté cerrada.
- Comunicación con el host remoto con las funciones de API send() y recv(), así como con las funciones generales escrito() y read().
- Cierre cada toma que se abrió después de usar con función close()
El siguiente programa crea un servidor TCP escuchando en el puerto número 1100:
#include > > > > #include - No. #include ■netinet/in.h #include ■arpa/inet.h #include Identificado.h #include ■stdlib.h #include Identificando.h #include No identificado.h int principal()vacío) {} struct sockaddr_in sa; int SocketFD = socket()PF_INET, SOCK_STREAM, IPPROTO_TCP); si ()SocketFD == -1) {} perror()"no puede crear socket"); Salida()EXIT_FAILURE); } memset()"sa, 0, tamaño sa); sa.sin familia = AF_INET; sa.sin_port = htons()1100); sa.sin_addr.S_addr = htonl()INADDR_ANY); si ()Bind()SocketFD,(struct sockaddr *)"sa, tamaño sa) == -1) {} perror()"Bond falló"); cerca()SocketFD); Salida()EXIT_FAILURE); } si ()escucha()SocketFD, 10) == -1) {} perror()"listen falló"); cerca()SocketFD); Salida()EXIT_FAILURE); } para (;) {} int ConnectFD = aceptar()SocketFD, NULL, NULL); si ()ConnectFD == -1) {} perror()"Acepto fallado"); cerca()SocketFD); Salida()EXIT_FAILURE); } /* realizar operaciones de escritura... read(ConnectFD, buff, size) */ si ()apagado()ConnectFD, SHUT_RDWR) == -1) {} perror()"La caída falló"); cerca()ConnectFD); cerca()SocketFD); Salida()EXIT_FAILURE); } cerca()ConnectFD); } cerca()SocketFD); retorno EXIT_SUCCESS; }
Cliente
La programación de una aplicación de cliente TCP implica los siguientes pasos:
- Creando una toma TCP.
- Conexión al servidor (connect()), pasando un
sockaddr_in
estructura con lasin_family
establecido AF_INET,sin_port
fijado al puerto el punto final está escuchando (en orden de byte de red), ysin_addr
establecido en la dirección IP del servidor de escucha (también en orden de byte de red). - Comunicación con el host remoto con las funciones de API send() y recv(), así como con las funciones generales escrito() y read().
- Cierre cada toma que se abrió después de usar con función close().
#include > > > > #include - No. #include ■netinet/in.h #include ■arpa/inet.h #include Identificado.h #include ■stdlib.h #include Identificando.h #include No identificado.h int principal()vacío) {} struct sockaddr_in sa; int res; int SocketFD; SocketFD = socket()PF_INET, SOCK_STREAM, IPPROTO_TCP); si ()SocketFD == -1) {} perror()"no puede crear socket"); Salida()EXIT_FAILURE); } memset()"sa, 0, tamaño sa); sa.sin familia = AF_INET; sa.sin_port = htons()1100); res = inet_pton()AF_INET, "192.168.1.3", "sa.sin_addr); si ()conectar()SocketFD, ()struct sockaddr *)"sa, tamaño sa) == -1) {} perror()"la conexión falló"); cerca()SocketFD); Salida()EXIT_FAILURE); } /* realizar operaciones de escritura... */ cerca()SocketFD); retorno 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. Ser sin conexión significa que no existe el concepto de flujo o conexión permanente entre dos hosts. Dichos 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), es completamente distinto del de los puertos TCP.
Servidor
Una aplicación puede configurar un servidor UDP en el número de puerto 7654 de la siguiente manera. Los programas contienen un bucle infinito que recibe datagramas UDP con la función recvfrom().
#include Identificado.h#include No.#include Identificando.h#include - No.#include > > > >#include ■netinet/in.h#include No identificado.h /* para close() para socket */ #include ■stdlib.hint principal()vacío){} int calcetines; struct sockaddr_in sa; char buffer[1024]; ssize_t recsize; socklen_t fromlen; memset()"sa, 0, tamaño sa); sa.sin familia = AF_INET; sa.sin_addr.S_addr = htonl()INADDR_ANY); sa.sin_port = htons()7654); fromlen = tamaño sa; calcetines = socket()PF_INET, SOCK_DGRAM, IPPROTO_UDP); si ()Bind()calcetines, ()struct sockaddr *)"sa, tamaño sa) == -1) {} perror()"El atentado al terrorismo falló"); cerca()calcetines); Salida()EXIT_FAILURE); } para (;) {} recsize = recv from()calcetines, ()vacío*)buffer, tamaño buffer, 0, ()struct sockaddr*)"sa, "fromlen); si ()recsize . 0) {} fprintf()stderr, "n", strerror()errno)); Salida()EXIT_FAILURE); } printf()"recsize: %dn ", ()int)recsize); dormir()1); printf()"datagrama: %.*sn", ()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 número de puerto 7654.
#include ■stdlib.h#include Identificado.h#include No.#include Identificando.h#include - No.#include > > > >#include ■netinet/in.h#include No identificado.h#include ■arpa/inet.hint principal()vacío){} int calcetines; struct sockaddr_in sa; int bytes_sent; char buffer[200]; strcpy()buffer, "hola mundo!"); /* crear un Internet, datagram, socket usando UDP */ calcetines = socket()PF_INET, SOCK_DGRAM, IPPROTO_UDP); si ()calcetines == -1) {} /* si el socket no pudo inicializar, salida */ printf()"Error Creando Socket"); Salida()EXIT_FAILURE); } /* Dirección de toma de corriente cero */ memset()"sa, 0, tamaño sa); /* La dirección es IPv4 */ sa.sin familia = AF_INET; /* IPv4 direcciones es un uint32_t, convertir una representación de cadena de los octets al valor apropiado */ sa.sin_addr.S_addr = inet_addr()"127.0.0.1"); /* las tomas son cortas sin señal, htons(x) asegura x está en orden de byte de red, establece el puerto a 7654 */ sa.sin_port = htons()7654); bytes_sent = Enviar a()calcetines, buffer, strlen()buffer), 0,(struct sockaddr*)"sa, tamaño sa); si ()bytes_sent . 0) {} printf()"Error envío paquete: %sn", strerror()errno)); Salida()EXIT_FAILURE); } cerca()calcetines); /* cerrar la toma */ retorno 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.
Contenido relacionado
Videotex
Lockheed Martin
Velero