Transferir datos a través de un socket usando un nombre de host. ¿Qué es un enchufe? Principio general de funcionamiento del enchufe.

Trabajar con WinSockets en Visual C++

Enchufe (enchufe, conector) - abstracto concepto de software, utilizado para indicar en un programa de aplicación el punto final de un canal de comunicación con un medio de comunicación formado red informática. Cuando utilizamos protocolos TCP/IP, podemos decir que el socket es un medio de conexión. programa de aplicación al puerto (ver arriba) del nodo de la red local.

Una interfaz de socket es simplemente un conjunto de llamadas al sistema y/o funciones de biblioteca del lenguaje de programación SI, divididas en cuatro grupos:

A continuación consideramos un subconjunto de funciones de interfaz de socket suficientes para escribir aplicaciones de red, implementando el modelo cliente-servidor en modo orientado a conexión.

1. Funciones de control local

Las funciones de gestión local se utilizan principalmente para realizar las acciones preparatorias necesarias para organizar la interacción de dos programas asociados. Las funciones se llaman así porque su ejecución es local para el programa.

1.1 Creando un enchufe

Se crea un socket usando la siguiente llamada al sistema #include int socket (dominio, tipo, protocolo) int dominio; tipo entero; protocolo int;

El argumento de dominio especifica el conjunto de protocolos utilizados para la interacción (un tipo de dominio de comunicación para la pila de protocolos TCP/IP debe tener el valor simbólico AF_INET (definido en sys/socket.h).

El argumento de tipo especifica el modo de interacción:

  • SOCK_STREAM - con establecimiento de conexión;
  • SOCK_DGRAM: sin establecer una conexión.

El argumento del protocolo especifica un protocolo de capa de transporte específico (entre varios protocolos posibles en la pila de protocolos). Si este argumento se establece en 0, se utilizará el protocolo predeterminado (TCP para SOCK_STREAM y UDP para SOCK_DGRAM cuando se utiliza el conjunto de protocolos TCP/IP).

Al finalizar exitosamente su trabajo esta función devuelve un descriptor de socket: un número entero no negativo que lo identifica de forma única. Un descriptor de socket es similar a un descriptor de archivo del sistema operativo UNIX.

Si se detecta un error durante su funcionamiento, la función devuelve el número "-1".

1.2. Zócalo de encuadernación

Para conectar un socket al entorno de comunicación formado por una red informática, es necesario ejecutar la llamada al sistema bind, que define en el formato aceptado para la red. dirección local Canal de comunicación con el medio ambiente. En las redes TCP/IP, un socket está asociado con un puerto local. La llamada al sistema de vinculación tiene la siguiente sintaxis:

#incluir #incluir #incluir int enlazar (s, addr, addrlen) int s; estructura sockaddr *dirección; int aturdido;

El argumento s especifica el descriptor del socket que se está vinculando.

El argumento addr generalmente debe apuntar a una estructura de datos que contiene la dirección local asignada al socket. Para redes TCP/IP, esta estructura es sockaddr_in.

La estructura sockaddr_in es utilizada por varias llamadas al sistema y funciones de interfaz de socket y se define en el archivo de inclusión in.h de la siguiente manera:

Estructura sockaddr_in ( short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero; );

El campo sin_family determina el formato de dirección utilizado (conjunto de protocolos), en nuestro caso (para TCP/IP) debería tener el valor AF_INET.

El campo sin_addr contiene la dirección (número) del nodo de red.

El campo sin_port contiene el número de puerto en el nodo de red.

El campo sin_zero no se utiliza.

La definición de la estructura in_addr (del mismo archivo de inclusión) es:

Struct in_addr ( union ( u_long S_addr; /* otros miembros (no interesantes para nosotros) del sindicato */ ) S_un; #define s_addr S_un.S_addr );

La estructura sockaddr_in debe estar completamente llena antes de emitir la llamada al sistema de enlace. En este caso, si el campo sin_addr.s_addr tiene el valor INADDR_ANY, entonces la llamada al sistema vinculará el número (dirección) del nodo de la red local al socket.

Si tiene éxito, la vinculación devuelve 0; de lo contrario, "-1".

2. Funciones de conectividad

Para establecer una conexión cliente-servidor, se utilizan escuchar y aceptar llamadas al sistema (en el lado del servidor), así como conectar (en el lado del cliente). Para completar los campos de la estructura socaddr_in utilizada en llamar conectar, generalmente se usa la función de biblioteca gethostbyname, que traduce el nombre simbólico de un host de red en su número (dirección).

2.1. Esperando que se establezca la conexión

La llamada al sistema de escucha expresa el deseo del programa servidor que la emitió de esperar solicitudes de los programas cliente y tiene siguiente vista:

#incluir int escucha (s, n) int s; int n;

El argumento s especifica un identificador de socket a través del cual el programa escuchará las solicitudes de los clientes. El socket primero debe crearse con la llamada al sistema de socket y se le debe proporcionar una dirección mediante la llamada al sistema de enlace.

El argumento n especifica la longitud máxima de la cola de solicitudes de protocolo de enlace entrantes. Si algún cliente emite una solicitud de protocolo de enlace cuando la cola está llena, la solicitud será rechazada.

La finalización exitosa de la llamada al sistema de escucha se indica mediante código cero devolver.

2.2. Solicitud de conexión

Para contactar un programa cliente con el servidor con una solicitud para establecer una conexión lógica, use la llamada al sistema de conexión, que tiene el siguiente formulario #include #incluir #incluir int conectar (s, dirección, addrlen) int s; estructura sockaddr_in *addr; int aturdido;

El argumento s especifica un identificador del socket a través del cual el programa realiza una solicitud de conexión al servidor. El socket primero debe crearse con la llamada al sistema de socket y se le debe proporcionar una dirección mediante la llamada al sistema de enlace.

El argumento addr debe apuntar a una estructura de datos que contenga la dirección asignada al socket" del programa servidor al que se realiza la solicitud de conexión. Para redes TCP/IP, esta estructura es sockaddr_in. Para generar los valores de los campos de la estructura sockaddr_in, es conveniente utilizar la función gethostbyname.

El argumento addrlen especifica el tamaño, en bytes, de la estructura de datos especificada por el argumento addr.

Para que la solicitud de conexión sea exitosa, es necesario, al menos, que el programa del servidor haya ejecutado una llamada al sistema de escucha para el socket con la dirección especificada en este punto.

Si la solicitud tiene éxito, la llamada al sistema de conexión devuelve 0; de lo contrario, -1 (estableciendo el código de motivo del error en la variable errno global).

Nota. Si en el momento en que se ejecuta connect, el socket que está utilizando no ha sido vinculado a una dirección usando bind, entonces dicha vinculación se realizará automáticamente.

Nota. En el modo de comunicación sin conexión, no es necesario realizar la llamada al sistema de conexión. Sin embargo, su ejecución en este modo no es un error: el significado de las acciones realizadas en este caso simplemente cambia: la dirección "predeterminada" se establece para todos los envíos de datagramas posteriores.

2.3. Recibir una solicitud de conexión

Para aceptar solicitudes de programas cliente para establecer comunicaciones, los programas servidor utilizan la llamada al sistema de aceptación, que se ve así:

#incluir #incluir int aceptar (s, dirección, p_addrlen) int s; estructura sockaddr_in *addr; int *p_addrlen;

El argumento s especifica el identificador del socket a través del cual el programa del servidor recibió la solicitud de conexión (a través de la solicitud del sistema de escucha).

El argumento addr debe apuntar a un área de memoria lo suficientemente grande como para acomodar una estructura de datos que contenga la dirección del socket del programa cliente que realizó la solicitud de conexión. No se requiere inicialización de esta área.

p_addrlen debe apuntar a un área de memoria, especificada como un número entero que especifica el tamaño, en bytes, del área de memoria especificada por addr.

La llamada al sistema de aceptación recupera la primera solicitud de conexión de la cola mantenida por la llamada al sistema de escucha y devuelve un identificador a un socket nuevo (creado automáticamente) con las mismas propiedades que el socket especificado por el argumento s. Este nuevo identificador debe usarse en. todos los datos de operaciones de comunicación posteriores.

Además, tras finalizar con éxito, acepte:

  • el área de memoria indicada por el argumento addr contendrá una estructura de datos (para redes TCP/IP esto es sockaddr_in) que describe la dirección del socket del programa cliente a través del cual realizó su solicitud de conexión;
  • el número entero señalado por p_addrlen será igual al tamaño de esta estructura de datos.

Si la cola de solicitudes está vacía en el momento en que se ejecuta la aceptación, entonces el programa entra en un estado de espera a que lleguen las solicitudes de los clientes durante un período de tiempo indefinido (aunque este comportamiento de aceptación se puede cambiar).

El signo de error de aceptación es un valor de retorno negativo (el descriptor del socket no puede ser negativo).

Nota. La llamada al sistema de aceptación se utiliza en programas de servidor que operan en modo de solo conexión.

2.4. Generando una dirección de host de red

Para obtener la dirección de un nodo de red TCP/IP por su nombre simbólico, utilice la función de biblioteca

#incluir #incluir struct hostent *gethostbyname (nombre) char *nombre;

El argumento de nombre especifica la dirección de la secuencia de caracteres que forman el nombre simbólico del host de la red.

Al completarse exitosamente, la función devuelve un puntero a la estructura hostent, definida en el archivo de inclusión netdb.h y que tiene el siguiente formato struct hostent ( char *h_name; char **h_aliases; int h_addrtype; int h_lenght; char *h_addr; ) ;

El campo h_name indica el nombre oficial (principal) del nodo.

El campo h_aliases apunta a una lista de nombres de nodos adicionales (sinónimos), si los hay.

El campo h_addrtype contiene el identificador del conjunto de protocolos utilizado para redes TCP/IP; este campo tendrá el valor AF_INET.

El campo h_lenght contiene la longitud de la dirección del nodo.

El campo h_addr apunta a un área de memoria que contiene la dirección del host utilizada por las llamadas al sistema y las funciones de interfaz de socket.

A continuación se analiza un ejemplo de cómo llamar a la función gethostbyname para obtener la dirección de un host remoto en un programa cliente que utiliza la llamada al sistema connect para generar una solicitud para establecer una conexión con un programa servidor en este host.

3. Funciones de comunicación

En el modo de conexión lógica, después de la ejecución exitosa de un par de llamadas al sistema interconectadas, conectar (en el cliente) y aceptar (en el servidor), es posible el intercambio de datos.

Este intercambio se puede implementar mediante las llamadas al sistema de lectura y escritura habituales que se utilizan para trabajar con archivos (en lugar de descriptores de archivos, en ellos se especifican descriptores de socket).

Además, se pueden utilizar adicionalmente las llamadas al sistema send y recv, orientadas específicamente al trabajo con sockets.

Nota. Para intercambiar datos en modo sin conexión, generalmente se utilizan las llamadas al sistema sendto y recvfrom. Sendto le permite especificar, junto con los datos transmitidos (que constituyen un datagrama), la dirección de su destinatario. Recvfrom, simultáneamente con la entrega de datos al destinatario, le informa sobre la dirección del remitente.

3.1. Enviando datos

Para enviar datos a un socio de red, utilice la llamada al sistema de envío, que tiene este aspecto:

#incluir #incluir int enviar (s, buf, len, banderas) int s; carbón *buf; longitud interna; banderas internas;

El argumento s especifica el descriptor de socket a través del cual se envían los datos.

El argumento buf apunta a la ubicación de la memoria que contiene los datos que se van a transferir.

El argumento len especifica la longitud (en bytes) de los datos que se transferirán.

El argumento flags modifica la ejecución de la llamada al sistema de envío. En valor cero Con este argumento, la llamada de envío es exactamente la misma que la llamada del sistema de escritura.

En caso de éxito, enviar devuelve el número de bytes de datos transferidos desde el área especificada por el argumento buf. Si el canal de datos identificado por el descriptor s se vuelve "lleno", el envío coloca el programa en estado de espera hasta que se libera.

3.2. Recibiendo datos

Para recibir datos de un socio de red, utilice la llamada al sistema recv, que tiene este aspecto:

#incluir #incluir int recv (s, buf, len, banderas) int s; carbón *buf; longitud interna; banderas internas;

El argumento s especifica el descriptor de socket a través del cual se reciben los datos.

El argumento buf apunta a un área de memoria dedicada a recibir datos.

El argumento len especifica la longitud, en bytes, de esta región.

El argumento flags modifica la ejecución de la llamada al sistema recv. Cuando este argumento se establece en cero, llamar a recv es exactamente lo mismo que llamar a la llamada al sistema de lectura.

En caso de éxito, recv devuelve el número de bytes de datos recibidos en el área especificada por el argumento buf. Si el canal de datos identificado por el descriptor s está "vacío", entonces recv pone el programa en estado de espera hasta que aparezcan datos en él.

4. Funciones de cierre

Para cerrar la comunicación con un socio de la red, se utilizan las llamadas de cierre y apagado del sistema.

4.1. cerrar llamada al sistema

Para cerrar un socket creado previamente, use la llamada al sistema de cierre habitual, que se usa en el sistema operativo UNIX para cerrar archivos abiertos previamente y tiene el siguiente formulario

Int cerrar(s) int s;

Sin embargo, en el modo de conexión lógica (que, por regla general, garantiza una entrega de datos confiable), los mecanismos de intercambio dentro del sistema intentarán transmitir/recibir los datos que quedan en el canal de transmisión en el momento en que se cierra el socket. Esto puede llevar un tiempo significativo. Intervalo, inaceptable para algunas aplicaciones. En tal situación, debe utilizar la llamada de apagado del sistema que se describe a continuación.

4.2. Restablecer datos almacenados en el buffer

Para cerrar "de emergencia" una conexión con un socio (al "restablecer" los datos que aún no se han transferido), se utiliza la llamada al sistema de apagado, ejecutada antes del cierre y que tiene la siguiente forma

Int apagado (s, cómo) int s; ent cómo;

El argumento s especifica un identificador para un socket creado previamente.

El argumento how especifica las acciones que se realizarán cuando se borran los buffers del sistema del socket:

2 - restablecer todos los datos transmitidos a través del enchufe en cualquier dirección.

5. Ejemplo de uso de la interfaz de socket

Esta sección analiza el uso de la interfaz de socket en modo de interacción con el establecimiento de una conexión lógica durante mucho tiempo. ejemplo sencillo interacción entre dos programas (servidor y cliente) que operan en diferentes nodos de la red TCP/IP.

el servidor, habiendo aceptado la solicitud de conexión, envía al cliente la pregunta "¿Quién eres?";

el cliente, al recibir la pregunta, la imprime en la salida estándar y envía la respuesta "Soy su cliente" al servidor y completa su trabajo;

el servidor imprime la respuesta del cliente en la salida estándar, cierra la comunicación con él y pasa al estado de espera de la siguiente solicitud.

Nota. Los textos de los programas que se ofrecen a continuación están destinados únicamente a ilustrar la lógica de la interacción entre programas a través de la red, por lo tanto, no contienen atributos de programas destinados a un uso práctico, como procesar códigos de retorno de llamadas y funciones del sistema, analizar códigos de error en el variable global errno, respuesta a eventos asincrónicos, etc.

5.1. programa de servidor

El texto del programa del servidor en el lenguaje de programación SI tiene este aspecto:

1 #incluir 2 #incluir 3 #incluir 4 #incluir 5 #incluir 6 #define SRV_PORT 1234 7 #define BUF_SIZE 64 8 #define TXT_QUEST "¿Quién eres?\n" 9 main () ( 10 int s, s_new; 11 int from_len; 12 char buf; 13 struct sockaddr_in sin, from_sin; 14 s = socket (AF_INET, SOCK_STREAM, 0); 15 memset ((char *)&sin, "\0", sizeof(sin)); 20 escuchar (s, 3) ( 22 from_len = sizeof(from_sin); 23 s_new = aceptar (s, &from_sin, &from_len); 24 escribir (s_new, TXT_QUEST, sizeof(TXT_QUEST)); 25 from_len = leer (s_new, buf , BUF_SIZE); 27 apagado (s_new, 0);

Las líneas 1...5 describen los archivos de inclusión, que contienen definiciones para todas las estructuras de datos y constantes simbólicas necesarias.

La línea 6 asigna el nombre simbólico SRV_PORT a la constante entera 1234. En el futuro, esta constante se utilizará como número de puerto del servidor. El valor de esta constante también debe ser conocido por el programa cliente.

La línea 7 asigna el nombre simbólico BUF_SIZE a la constante entera 64. Esta constante determinará el tamaño del búfer utilizado para acomodar los datos recibidos del cliente.

La línea 8 asigna el nombre simbólico TXT_QUEST a la secuencia de caracteres que componen el texto de la pregunta del cliente. El último carácter de la secuencia es el carácter de nueva línea "\n". Esto se hizo para simplificar la salida del texto de la pregunta en el lado del cliente.

En la línea 14, se crea (abre) un socket para organizar el modo de interacción con el establecimiento de una conexión lógica (SOCK_STREAM) en la red TCP/IP (AF_INET), al elegir un protocolo de capa de transporte, el protocolo “predeterminado” (0 ) se utiliza.

En las líneas 15...18, primero se restablece la estructura de datos sin y luego se completan sus campos individuales. El uso de la constante INADDR_ANY simplifica el código al eliminar la necesidad de usar la función gethostbyname para obtener la dirección del host local en el que se ejecuta el servidor.

La línea 19 utiliza la llamada al sistema de vinculación para vincular el socket especificado por los descriptores al número de puerto SRV_PORT en el nodo local. Bind se completará con éxito siempre que en el momento de su ejecución ya no haya ningún programa ejecutándose en el mismo nodo que utilice este número de puerto.

La línea 20 utiliza la llamada del sistema de escucha para poner en cola tres solicitudes de conexión entrantes al servidor.

La línea 21 sirve como encabezado para el bucle interminable de solicitudes de servicio de los clientes.

En la línea 23, que contiene la llamada al sistema de aceptación, la ejecución del programa se suspende indefinidamente si la cola de solicitudes al servidor para establecer una conexión está vacía. Cuando aparece una solicitud de este tipo, la aceptación se completa con éxito y devuelve un descriptor de socket en la variable s_new para intercambiar información con el cliente.

En la línea 24, el servidor envía una pregunta al cliente mediante la llamada al sistema de escritura.

La línea 25 utiliza la llamada al sistema de lectura para leer la respuesta del cliente.

En la línea 26, la respuesta se envía a la salida estándar, que tiene el descriptor de archivo número 1. Dado que la línea de respuesta contiene un carácter de nueva línea, el texto de la respuesta se colocará en una línea separada de la pantalla.

La línea 27 contiene la salida de apagado del sistema, que garantiza que los buffers del sistema del socket que contienen datos para leer se borren (los datos "extra" pueden terminar allí como resultado de operación incorrecta cliente).

La línea 28 cierra (borra) el socket utilizado para intercambiar datos con el siguiente cliente.

Nota. Este programa (como la mayoría de los programas de servidor reales) no completa su trabajo por sí solo, ya que se encuentra en un bucle interminable de procesamiento de solicitudes de clientes. Su ejecución sólo puede interrumpirse externamente enviándole señales de finalización (interrupciones). Un programa de servidor diseñado correctamente debería manejar tales señales apagándose correctamente (en particular, cerrando el socket con manijas).

5.2. programa cliente

El texto del programa cliente en el lenguaje de programación SI tiene este aspecto:

1 #incluir 2 #incluir 3 #incluir 4 #incluir 5 #incluir 6 #define SRV_HOST "delta" 7 #define SRV_PORT 1234 8 #define CLNT_PORT 1235 9 #define BUF_SIZE 64 10 #define TXT_ANSW "Soy tu cliente\n" 11 main () ( 12 int s; 13 int from_len; 14 char buf 15 struct hostent *hp; 16 struct sockaddr_in clnt_sin, 17 s = socket (AF_INET, SOCK_STREAM, 0); 18 memset ((char *)&clnt_sin, "\0", sizeof(clnt_sin)); sin_addr.s_addr = INADDR_ANY; 21 clnt_sin.sin_port = CLNT_PORT; 22 hp = gethostbyname(SRV_HOST); = AF_INET; 28 conectar (s, &srv_sin, sizeof(srv_sin)); 29 from_len = recv (s, buf, BUF_SIZE, 0); 33 salida(0);

Las líneas 6 y 7 describen las constantes SRV_HOST y SRV_PORT, que definen el nombre del host remoto en el que opera el programa del servidor y el número de puerto al que está vinculado el socket del servidor.

La línea 8 asigna el nombre simbólico CLNT_PORT a la constante entera 1235. En el futuro, esta constante se utilizará como número de puerto del cliente.

Las líneas 17...22 crean un socket vinculado a un puerto en el nodo local.

La línea 24 utiliza la función de biblioteca gethostbyname para traducir el nombre simbólico del host remoto (en este caso “delta”) en el que debe operar el servidor a la dirección de este nodo, ubicado en una estructura de tipo hostent.

La línea 26 copia la dirección del nodo remoto de la estructura hostent en el campo correspondiente de la estructura srv_sin, que luego (en la línea 28) se usa en la llamada al sistema de conexión para identificar el programa del servidor.

En las líneas 29...31, los datos se intercambian con el servidor y la pregunta recibida del servidor se envía a la salida estándar.

La línea 32 cierra (elimina) el socket usando la llamada al sistema close.

Los enchufes se utilizan para proporcionar comunicaciones de red. Un socket es el punto final de las comunicaciones de la red. Cada socket en uso tiene un tipo y un proceso asociado. Los sockets existen dentro de los dominios de comunicación. Los dominios son abstracciones que implican una estructura de direccionamiento específica y un conjunto de protocolos que definen los diferentes tipos de sockets dentro de un dominio. Ejemplos de dominios de comunicación podrían ser: Dominio UNIX, dominio de internet, etc.

En el dominio de Internet, un socket es una combinación de una dirección IP y un número de puerto que identifica de forma única un único proceso de red en toda la Internet global. Dos sockets, uno para el host receptor y otro para el host emisor, definen la conexión para protocolos orientados a conexión como TCP.

Creando un enchufe

Para crear un socket, utilice la llamada al sistema de socket.

s = socket(dominio, tipo, protocolo);

Esta llamada se basa en información sobre el dominio de comunicación y el tipo de socket. Para utilizar las funciones de Internet, los valores de los parámetros deben ser los siguientes: dominio de comunicación- AF_INET (protocolos de Internet). tipo de enchufe-SOCK_STREAM; Este tipo proporciona un flujo de bytes consistente, confiable y orientado a la comunicación bidireccional. El flujo de tipo socket se mencionó anteriormente. A continuación se proporciona una breve descripción de otros tipos de enchufes: Conector de datagrama- admite flujo de datos bidireccional. No hay garantía de que este flujo sea consistente, confiable o que los datos no se dupliquen. Una característica importante de este conector es que los límites de registro de datos están predefinidos. Zócalo crudo- proporciona la capacidad de acceso de los usuarios a protocolos de comunicación subyacentes que admiten abstracciones de sockets. Estos sockets suelen estar orientados a datagramas. La función de socket crea un punto final para las comunicaciones y devuelve un descriptor de archivo que hace referencia al socket, o -1 en caso de error. Este descriptor se utiliza más adelante para establecer comunicación.

Para crear un socket como arroyo con protocolo tcp Para proporcionar soporte de comunicación, la llamada a la función de socket debe ser la siguiente:

s = enchufe(AF_INET, SOCK_STREAM, 0);

Vinculación a nombres locales

El socket se crea sin nombre. Hasta que un socket tenga un nombre asociado, los procesos remotos no pueden hacer referencia a él y, por lo tanto, no se pueden recibir mensajes en ese socket. Los procesos de comunicación utilizan asociaciones para estos fines. En un dominio de Internet, la asociación consta de una dirección local y remota y una dirección local y remota. puerto remoto. En la mayoría de los dominios, la asociación debe ser única.

En el dominio de Internet, asociar un socket y un nombre puede ser bastante complejo, pero afortunadamente generalmente no es necesario asociar específicamente una dirección y un número de puerto con un socket, ya que las funciones de conexión y envío asociarán automáticamente un socket determinado con un socket adecuado. dirección si esto no se ha hecho antes llamar.

Para vincular un socket con una dirección y un número de puerto, use la llamada al sistema de vinculación:

enlazar(s, nombre, nombrelen);

El nombre de enlace es una cadena de bytes de longitud variable interpretada por el protocolo admitido. La interpretación puede variar según los diferentes dominios de comunicación.

Estableciendo una conexión

En el lado del cliente, la comunicación se establece mediante la función de conexión estándar:

error = conectar(s, serveraddr, serveraddrlen);

que inicia la comunicación en el socket utilizando descriptores de socket e información de la estructura dirección del servidor, teniendo tipo sockaddr_in, que contiene la dirección del servidor y el número de puerto al que se debe establecer la comunicación. Si el socket no ha sido vinculado a una dirección, connect llamará automáticamente a la función del sistema de vinculación.

Connect devuelve 0 si la llamada fue exitosa. Un valor de retorno de -1 indica que se produjo algún error durante el proceso de protocolo de enlace. Si la llamada a la función es exitosa, el proceso puede operar en el identificador del socket usando las funciones de lectura y escritura, y cerrar el canal usando la función de cierre.

Del lado del servidor, el proceso de establecimiento de la conexión es más complicado. Cuando un servidor desea ofrecer uno de sus servicios, vincula un socket a una dirección públicamente conocida asociada con ese servicio y escucha pasivamente en ese socket. Para estos fines, se utiliza la llamada al sistema de escucha:

error = escuchar(s, qlongitud);

Dónde s este es un descriptor de socket, y longitud q este es el número máximo de solicitudes de protocolo de enlace que se pueden poner en cola esperando ser procesadas por el servidor; este número puede estar limitado por las características del sistema.

Cuando el servidor recibe una solicitud de un cliente y decide establecer una conexión, crea un nuevo socket y lo asocia con una asociación equivalente a un "socket de escucha". Para un dominio de Internet esto significa el mismo número de puerto. La llamada al sistema de aceptación se utiliza para este propósito:

newsock = aceptar(s, clientaddr, clientaddrlen);

El socket asociado con el cliente y el socket devuelto por la función de aceptación se utilizan para establecer comunicación entre el servidor y el cliente.

Transferencia de datos

Una vez establecida la conexión, utilice varias funciones El proceso de transferencia de datos puede comenzar. Una vez conectado, el usuario puede enviar y recibir mensajes utilizando las funciones de lectura y escritura:

escribir(s, buf, tamaño de(buf)); leer(s, buf, tamaño de(buf));

Las llamadas de envío y recepción son casi idénticas a las de lectura y escritura, excepto que se agrega un argumento de banderas.

enviar(s, buf, tamaño de(buf), banderas); recv(s, buf, tamaño de(buf), banderas);

Se pueden especificar uno o más indicadores utilizando valores distintos de cero, como los siguientes:

  • MSG_OOB- Enviar/recibir datos típicos de sockets de flujo.
  • MSG_PEEK- Ver datos sin leer. cuando se especifica en recv, todos los datos presentes se devuelven al usuario, pero los datos en sí se dejan como "no leídos". La próxima lectura o recepción llamada en este socket devolverá los datos leídos la última vez.
  • MSG_DONTROUTE- enviar datos sin enrutar paquetes.

(Usado solo por procesos que administran tablas de enrutamiento).

Cierre de enchufes Cuando los módulos en comunicación deciden dejar de transmitir datos y cerrar la sesión de comunicación, intercambian un protocolo de enlace de tres vías con segmentos que contienen establecer bit

"No hay más datos del remitente" (este bit también se llama bit FIN).

Si un socket ya no está en uso, un proceso puede cerrarlo usando la función close, llamándolo con el identificador de socket apropiado:

cerrar(es);

Si los datos se han asociado con un socket que promete entrega (un socket de tipo flujo), el sistema intentará transmitir los datos. Sin embargo, transcurrido un periodo de tiempo bastante largo, si los datos aún no se entregan, serán descartados. Si un proceso de usuario desea detener toda la transferencia de datos, puede hacerlo llamando al apagado del socket para cerrarlo. Llamar al apagado hace que todos los datos en cola se descarten "instantáneamente". El formato de la convocatoria es el siguiente:

apagado(s, cómo);

  • donde how tiene uno de los siguientes valores:
  • 0 - si el usuario ya no desea leer los datos
  • 1 - si ya no se enviarán datos

2 - si no se enviarán ni recibirán datos

/* La función MakeConnection asigna un socket y establece una conexión con el host remoto. Número de puerto predeterminado 80. Entrada: nombre del servidor WWW (con número de puerto, si no es 80) Salida: descriptor de archivo en caso de éxito -1 en caso de error */ int MakeConnection(unsigned char* ServerName)( int s; struct sockaddr_in ssin; struct hostent* hp; unsigned char strHlp, *pch /* usa el número de puerto predeterminado - 80 o un número específico del nombre del servidor */ strcpy(strHlp,ServerName); = "\0"; pch++; PortNum = atoi(pch); if(PortNum==0)( PortNum = 80; ) ) /* obtener host por nombre - resolver el nombre de host en dirección IP */ if((hp=gethostbyname (strHlp)) == NULL) ( return -1; ) bzero(&ssin, sizeof(ssin)); bcopy(hp->h_addr, &ssin.sin_addr, hp->h_length = hp->h_addrtype); ssin.sin_port = htons(PortNum); /* asignar un socket */ if((s=socket(AF_INET, SOCK_STREAM, 0))==-1) ( return -1; ) /* establecer una conexión */ if( connect(s, &ssin, sizeof(ssin), 0)==-1)( return -1; ) return s /* descriptor de socket */ )

Capítulo 15

En este capítulo, se le presentará otra forma de interactuar entre procesos, significativamente diferente de las que discutimos en capítulos 13 Y 14. Hasta ahora, todas las herramientas que hemos comentado se han basado en recursos compartidos en un único ordenador. Los recursos podrían ser áreas del sistema de archivos, segmentos de memoria compartida o colas de mensajes, pero solo podrían ser utilizados por procesos que se ejecuten en la misma máquina.

La versión de UNIX de Berkeley introdujo un nuevo medio de comunicación, la interfaz socket, que es una extensión del concepto de tubería discutido en Capítulo 13. Los sistemas Linux también tienen interfaces de socket.

Puede utilizar enchufes de la misma forma que las tuberías, pero admiten la comunicación dentro de una red informática. Un proceso en una máquina puede usar sockets para comunicarse con un proceso en otra máquina, lo que hace posible tener sistemas cliente-servidor distribuidos en una red. Los procesos que se ejecutan en la misma máquina también pueden utilizar sockets.

Además, la interfaz de socket está disponible en Windows a través de la especificación Windows Sockets o WinSock, disponible públicamente. Los servicios de socket en Windows los proporciona el archivo de sistema Winsock.dll. Por lo tanto, los programas que ejecutan Windows pueden interactuar a través de la red con computadoras que ejecutan control de linux y UNIX y viceversa, implementando así sistemas cliente-servidor. Aunque la interfaz de programación de WinSock no es completamente idéntica a la interfaz de socket en UNIX, se basa en los mismos sockets.

No podemos cubrir todas las capacidades de red de Linux en un solo capítulo, por lo que sólo encontrará las interfaces básicas del software de red que le permitirán escribir sus propios programas de red.

Cubriremos los siguientes temas con más detalle:

□ cómo funcionan las conexiones de enchufe;

□ atributos de socket, direcciones e intercambio de información;

□ información de red y demonio de Internet (inetd/xinetd);

□ clientes y servidores.

¿Qué es un enchufe?

Un socket es un medio de comunicación que permite el desarrollo de sistemas cliente-servidor para uso local, en una sola máquina o en red. Funciones del sistema operativo Linux, como salida, conexiones de bases de datos y servicio de páginas web, así como utilidades de red, Por ejemplo

, destinado al registro remoto y utilizado para la transferencia de archivos, suele utilizar sockets para el intercambio de datos.

Los enchufes se crean y utilizan de manera diferente a las tuberías porque enfatizan clara diferencia entre cliente y servidor. El mecanismo de socket le permite crear múltiples clientes conectados a un solo servidor.

Conexiones basadas en sockets

Se puede considerar que las conexiones basadas en sockets son como realizar llamadas telefónicas a una institución. Una llamada telefónica llega a una organización y es respondida por una recepcionista que dirige la llamada al departamento apropiado (proceso del servidor) y de allí al empleado correcto (enchufe del servidor). Cada llamada telefónica entrante (cliente) se enruta al punto final apropiado y los operadores intermedios pueden encargarse del seguimiento. llamadas telefonicas. Antes de analizar el establecimiento de conexiones mediante sockets en sistemas Linux, debemos comprender cómo se comportan en aplicaciones de sockets compatibles con la conexión.

En primer lugar aplicación de servidor Crea un socket que, como un descriptor de archivo, representa un recurso asignado a un único proceso de servidor. El servidor lo crea mediante una llamada al sistema.

y este socket no se puede compartir con otros procesos.

A continuación, el servidor asigna un nombre al socket. Tomas locales con nombres dados archivos en el archivo sistema linux a menudo se encuentra en el directorio /tmp o /usr/tmp. Para los sockets de red, el nombre del archivo será el identificador del servicio (número de puerto/punto de acceso) relacionado con red específica, al que los clientes pueden conectarse. Este identificador, al especificar un número de puerto específico correspondiente al proceso del servidor correcto, permite a Linux enrutar las conexiones entrantes a lo largo de una ruta específica. Por ejemplo, un servidor web normalmente crea un socket en el puerto 80, un identificador reservado para este propósito. Los navegadores web saben que deben utilizar el puerto 80 para sus conexiones HTTP a sitios web que el usuario desea leer. Un socket recibe nombre mediante una llamada al sistema

. A continuación, el proceso del servidor espera a que el cliente se conecte al socket nombrado. La llamada al sistema crea una cola de conexiones entrantes. El servidor puede aceptarlos mediante una llamada al sistema.

Cuando el servidor llama

, se crea un nuevo socket que es diferente del socket nombrado. Este nuevo socket se utiliza únicamente para comunicarse con este cliente específico. El socket nombrado se guarda para futuras conexiones de otros clientes. Si el servidor está escrito correctamente, puede beneficiarse de múltiples conexiones. El servidor web logra esto entregando páginas a muchos clientes al mismo tiempo. En el caso de un servidor simple, todos los clientes posteriores esperan en cola hasta que el servidor esté listo nuevamente.

El lado del cliente del sistema que utiliza sockets es mucho más sencillo. El cliente crea un socket sin nombre usando la llamada

. Luego llama para conectarse al servidor utilizando el socket nombrado del servidor como dirección.

Una vez instalados, los sockets se pueden utilizar como descriptores de archivos de bajo nivel, lo que permite el intercambio de datos bidireccional.

Completa los ejercicios 15.1 y 15.2.

Ejercicio 15.1. Cliente local sencillo. Veremos la llamada al sistema en detalle un poco más adelante cuando analicemos algunos problemas de abordaje.

1. Incluya los archivos de encabezado necesarios y establezca las variables:


2. Cree un socket para el cliente:

sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

3. Asigne un nombre al socket según lo acordado con el servidor:

dirección.sun_family = AF_UNIX;
strcpy(dirección.sun_path, "server_socket");

4. Conecte su socket al socket del servidor:

:
printf("char del servidor = %c\n", ch);

Este programa fallará si intenta ejecutarlo porque el socket del servidor nombrado aún no se ha creado ( Mensaje exacto El mensaje de error puede diferir en diferentes sistemas.)

Vaya: cliente1: no existe tal archivo o directorio
Ejercicio 15.2. Servidor local sencillo

El siguiente es un programa de servidor simple, server1.c, que acepta una solicitud de conexión de un cliente. Crea un socket de servidor, le da un nombre, crea una cola de espera y acepta solicitudes de conexión.

1. Incluya los archivos de encabezado necesarios y establezca las variables:


estructura sockaddr_un dirección_servidor;
estructura sockaddr_un dirección_cliente;

2. Elimine todos los sockets antiguos y cree un socket sin nombre para el servidor:

server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

3. Asigne un nombre al socket:

dirección_servidor.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");

4. Cree una cola de solicitudes de conexión y espere la solicitud del cliente:

5. Acepte la solicitud de conexión:

(struct sockaddr *)&client_address, &client_len);

6. Leer y escribir datos del cliente usando

:

Cómo funciona esto

En este ejemplo, el programa servidor sólo puede atender a un cliente a la vez. Simplemente lee el carácter recibido del cliente, lo incrementa y lo vuelve a escribir. En sistemas más complejos, donde el servidor debe hacer más trabajo en nombre del cliente, este enfoque no será apropiado porque otros clientes no podrán conectarse hasta que el servidor se apague. Más adelante verás un par de métodos que permiten que varios clientes se conecten.

Cuando ejecuta un programa de servidor, crea un socket y espera solicitudes de conexión. Si lo ejecuta en segundo plano, es decir. se ejecutará de forma independiente, luego podrá ejecutar clientes como tareas de alta prioridad.

Mientras espera solicitudes de conexión, el servidor muestra un mensaje. En el ejemplo dado, el servidor está esperando una solicitud del socket del sistema de archivos y puede verlo usando el comando normal.

.

Es una buena idea eliminar un socket después de haber terminado de trabajar con él, incluso si el programa falla debido a la recepción de una señal. Esto evitará que el sistema de archivos se llene de archivos no utilizados.

$ ls -lF conector del servidor
srwxr-xr-x 1 usuarios de neil 0 2007-06-23 11:41 server_socket=

Aquí el tipo de dispositivo es socket, lo cual se indica con el símbolo

antes de los permisos y un símbolo al final del nombre. El socket fue creado como archivo normal con derechos de acceso modificados por la actual. Si usa el comando, puede ver el servidor ejecutándose en segundo plano. Se muestra durmiendo (parámetro igual a) y por lo tanto no consume recursos de CPU.
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIEMPO COMANDO
0 1000 23385 10689 17 0 1424 312 361800 Puntos/1 0:00 ./servidor1

Ahora, cuando ejecute el programa, se conectará correctamente al servidor. Dado que el socket del servidor existe, puede conectarse a él e intercambiar datos.

servidor esperando carácter del servidor = B

En la terminal, la salida del servidor y del cliente se mezcla, pero puede ver que el servidor recibió un carácter del cliente, lo incrementó y lo devolvió. Luego, el servidor continúa ejecutándose y espera al siguiente cliente. Si ejecuta varios clientes juntos, se atenderán por turnos, aunque el resultado resultante puede ser aún más confuso.

$ ./cliente1 & ./cliente1 & ./cliente1 &

Atributos del zócalo

Para comprender completamente las llamadas al sistema utilizadas en este ejemplo, necesita saber algo sobre redes en sistemas UNIX.

Los enchufes se caracterizan por tres atributos: dominio, tipo Y protocolo. También tienen una dirección que se utiliza como nombre del socket. Los formatos de dirección varían según el dominio, también llamado familia de protocolos. Cada familia de protocolos puede utilizar una o más familias de direcciones, que definen el formato de dirección.

Dominios de socket

Los dominios definen el entorno operativo de red que utilizará la conexión de socket. El dominio de socket más popular es

, refiriéndose a Internet y utilizado en muchos redes locales Linux y, por supuesto, la propia Internet. El Protocolo de Internet (IP) de bajo nivel, que tiene una sola familia de direcciones, impone una forma específica de especificar las computadoras en la red. se llama Dirección IP.
Nota

Para superar algunos de los problemas del protocolo IP estándar con un número significativamente limitado de direcciones disponibles, se desarrolló el protocolo de Internet IPv6 de próxima generación. Utiliza un dominio de socket diferente

y otros formatos de dirección. Se espera que IPv6 eventualmente reemplace al IP, pero esto llevará muchos años. Aunque ya existen implementaciones de IPv6 para Linux, discutirlas está más allá del alcance de este libro.

Aunque las máquinas en Internet casi siempre tienen nombres, estos se convierten en direcciones IP. Una dirección IP de ejemplo es 192.168.1.99. Todas las direcciones IP están representadas por cuatro números, cada uno de los cuales es menor que 256, y forman el llamado cuatros con puntos. Cuando un cliente se conecta a través de una red mediante sockets, necesita la dirección IP de la computadora servidor.

Es posible que haya varios servicios disponibles en la computadora servidor. El cliente puede acceder a un servicio específico en una computadora conectada a la red mediante un puerto IP. Dentro del sistema, un puerto se identifica mediante un entero único de 16 bits y fuera del sistema mediante una combinación de dirección IP y número de puerto. Los sockets son puntos finales de comunicación que deben asociarse con puertos antes de que sea posible la transferencia de datos.

Los servidores esperan solicitudes de conexión de clientes específicos. Los servicios conocidos tienen números de puerto dedicados que utilizan todas las máquinas Linux y UNIX. Normalmente, pero no siempre, estos números son inferiores a 1024. Los ejemplos incluyen el búfer de impresión de la impresora (515),

(513), (21) y (80). El último nombrado... puerto estándar para servidores web. Normalmente, los números de puerto inferiores a 1024 están reservados para servicios del sistema y pueden ser atendidos por procesos con derechos de superusuario. El estándar X/Open define una constante en el archivo de encabezado netdb.h para indicar el mayor número de puertos reservados.

Debido a que existe un conjunto estándar de números de puerto para servicios estándar, las computadoras pueden conectarse fácilmente entre sí sin tener que adivinar el número de puerto correcto. Los servicios locales pueden utilizar direcciones de puertos no estándar.

El dominio en el primer ejercicio,

, es un dominio de sistema de archivos UNIX que pueden utilizar sockets ubicados en una sola computadora, quizás ni siquiera en la red. Si es así, entonces el protocolo de bajo nivel es E/S de archivos y las direcciones son nombres de archivos. El socket del servidor utilizó la dirección que vio aparecer en el directorio actual cuando ejecutó la aplicación del servidor.

Además, se podrán utilizar otros dominios:

para redes basadas en protocolos ISO estándar y para Xerox Sistema de red (sistema de red Fotocopia). No los discutiremos en este libro. Tipos de enchufes

Un dominio de socket puede tener múltiples métodos de comunicación, cada uno de los cuales puede tener características diferentes. En el caso de sockets de dominio

No surgen problemas porque proporcionan un intercambio de datos bidireccional fiable. En los dominios de red, es necesario conocer las características de la red subyacente y su impacto en diversos mecanismos de transferencia de datos.

Los protocolos de Internet proporcionan dos mecanismos para transmitir datos con diferentes niveles de servicio: corrientes Y datagramas.

Tomas de corriente

Stream sockets (algo similar flujos estándar E/S) proporcionan una conexión que es un flujo de bytes bidireccional consistente y confiable. Por lo tanto, se garantiza que los datos no se perderán, duplicarán o reordenarán sin indicar el error ocurrido. Los mensajes grandes se fragmentan, se transmiten y se vuelven a unir. Esto es similar a una secuencia de archivos que toma grandes cantidades de datos y los divide en bloques más pequeños para escribirlos en un disco físico. Los sockets de flujo tienen un comportamiento predecible.

Conectores de flujo descritos por tipo

, se implementan en el dominio mediante conexiones basadas en protocolos TCP/IP. Además, este es un tipo de socket común en . En este capítulo, nos centraremos en los sockets porque son los sockets más utilizados en la programación de aplicaciones de red.
Nota

TCP/IP es la abreviatura de Protocolo de control de transmisión/Protocolo de Internet. El protocolo IP es un protocolo de transmisión de paquetes de bajo nivel que proporciona selección de ruta al enviar datos en una red de una computadora a otra. TCP proporciona pedidos, control de flujo y retransmisión para garantizar una transmisión completa y correcta. grandes volúmenes datos o un mensaje sobre una situación de error correspondiente.

Conectores de datagramas

A diferencia de los sockets de datagramas de transmisión, que se describen por el tipo

, no establezca ni mantenga una conexión. Además, existe un límite en el tamaño del datagrama que se puede enviar. Se transmite como un único mensaje de red, que puede perderse, duplicarse o llegar intempestivamente, es decir. antes de los datagramas enviados después de él.

Los sockets de datagramas se implementan en el dominio.

a través de conexiones UDP/IP y proporcionar un servicio desorganizado y poco confiable. (UDP es la abreviatura de Protocolo de datagramas de usuario). Sin embargo, consumen relativamente pocos recursos porque no requieren conexiones de red. Son rápidos porque... No se pierde tiempo configurando una conexión de red.

Los datagramas son útiles para solicitudes únicas a servicios de información, para proporcionar información de estado de rutina o para realizar registros de datos de baja prioridad. Su ventaja es que detener el servidor no causará inconvenientes indebidos al cliente y no requerirá reiniciar el cliente. Debido a que los servidores basados ​​en datagramas normalmente almacenan datos sin conexión, se pueden detener y reiniciar sin interferir con sus clientes.

Esto concluye nuestra discusión sobre datagramas. Para obtener más información, consulte; sección "Datagramas" al final de este capítulo.

Protocolos de socket

Si el mecanismo de transferencia de datos de bajo nivel permite múltiples protocolos que proporcionan el tipo de socket requerido, puede seleccionar un protocolo o socket específico. En este capítulo, nos centraremos en los sockets de sistemas de archivos y redes UNIX, que no requieren que seleccione un protocolo distinto al predeterminado.

Creando un enchufe

La llamada al sistema de socket crea un socket y devuelve un identificador que se puede usar para acceder al socket:

#incluir
#incluir
int socket(dominio int, tipo int, protocolo int);

El socket creado es un punto final de la línea de transmisión. Parámetro

especifica la familia de direcciones, el parámetro especifica el tipo de intercambio de datos utilizado con este socket y a es el protocolo utilizado.

en la mesa 15.1 muestra los nombres de dominio.


Tabla 15.1

Los dominios de socket más populares incluyen

, utilizado para sockets locales implementados por sistemas de archivos UNIX y Linux, y utilizado para sockets de red UNIX. Los sockets de dominio pueden ser utilizados por programas que se comunican a través de redes basadas en el protocolo TCP/IP, incluido Internet. La interfaz de Windows Winsock también proporciona acceso a este dominio de socket.

El parámetro de tipo de socket especifica las características de comunicación que se aplicarán al nuevo socket. Los valores posibles podrían ser

Y . es un flujo de bytes bidireccional, ordenado, confiable y basado en conexión. En el caso de un dominio de socket, este tipo de comunicación de forma predeterminada la proporciona una conexión TCP que se establece entre dos puntos finales de socket de flujo al conectarse. Los datos se pueden transferir en dos direcciones a través de un enlace de socket. Los protocolos TCP incluyen un medio para fragmentar y luego reensamblar mensajes grandes y retransmitir cualquier parte que pueda haberse perdido en la red. - servicio de datagramas. Puede utilizar dicho socket para enviar mensajes con un tamaño máximo fijo (normalmente pequeño), pero no hay garantía de que el mensaje se entregue o de que no se reordenen en la red. En el caso de los sockets de dominio, este tipo de transferencia de datos se proporciona mediante datagramas UDP (User Datagram Protocol).

El protocolo utilizado para la comunicación suele estar determinado por el tipo de socket y el dominio. Como regla general, no hay elección. Parámetro

se aplica en los casos en los que todavía se ofrece una opción. La configuración 0 le permite seleccionar protocolo estándar, utilizado en todos los ejemplos de este capítulo.

llamada al sistema

devuelve un identificador muy parecido a un descriptor de archivo de bajo nivel. Cuando un socket está conectado al punto final de otro socket, puede usar llamadas al sistema en el identificador del socket para enviar y recibir datos usando sockets. La llamada al sistema se utiliza para eliminar una conexión de socket.

Direcciones de socket

Cada dominio de socket requiere un formato de dirección diferente. en el dominio

la dirección se describe mediante la estructura declarada en el archivo de encabezado sys/un.h:
sa_family_t sun_family; /* AF_UNIX */
char sol_ruta; /* Ruta al archivo */

Para permitir que se pasen direcciones de diferentes tipos a llamadas al sistema para procesar sockets, todos los formatos de direcciones se describen mediante una estructura similar que comienza con un campo (en este caso

), especificando el tipo de dirección (dominio de socket). En un dominio, la dirección se especifica mediante el nombre del archivo en el campo de estructura.

EN sistemas modernos tipo de linux

, definido en el estándar X/Open como declarado en el archivo de encabezado sys/un.h, se interpreta como tipo. Además, el tamaño especificado en el campo es limitado (en Linux, se especifican 108 caracteres; en otros sistemas, se puede utilizar, por ejemplo, una constante con nombre). Debido a que el tamaño de la estructura de direcciones puede variar, muchas llamadas al sistema de socket requieren o proporcionan como salida la longitud que se utilizará para copiar una estructura de direcciones particular. la dirección se especifica mediante una estructura llamada , definida en el archivo netinet/in.h, que contiene al menos los siguientes elementos:
short int pecado_familia; /* AF_INET */
sin firmar short int sin_port; /* Número de puerto */
estructura in_addr sin_addr; /* dirección de Internet */

La estructura de dirección IP de tipo in_addr se define de la siguiente manera:

sin firmar largo int s_addr;

Los cuatro bytes de la dirección IP forman un valor de 32 bits. Conector de dominio

completamente descrito por dirección IP y número de puerto. Desde el punto de vista de la aplicación, todos los sockets actúan como descriptores de archivos y sus direcciones se especifican mediante valores enteros únicos.

Nomenclatura de sockets

Para crear un socket (creado llamando

) accesible a otros procesos, el programa servidor debe darle un nombre al socket. Los sockets de dominio están asociados con un nombre de archivo completo en el sistema de archivos, como vio en el programa de ejemplo del servidor1. Los sockets de dominio están asociados con un número de puerto IP.
#incluir
int bind(int socket, const struct sockaddr *dirección, size_t dirección len);

llamada al sistema

Asigna la dirección especificada en el parámetro al socket sin nombre asociado con el descriptor del socket. La longitud de la estructura de direcciones se pasa en el parámetro:

La longitud y el formato de la dirección dependen de la familia de direcciones. En una llamada al sistema

un puntero a una estructura de dirección particular debe convertirse a un tipo de dirección genérico.

Al finalizar exitosamente

devuelve 0. Si falla, se devuelve -1 y a la variable se le asigna uno de los valores enumerados en la tabla. 15.2.

Tabla 15.2

Crear una cola de sockets

Para aceptar solicitudes de conexiones entrantes basadas en sockets, el programa del servidor debe crear una cola para contener las solicitudes pendientes. Se forma mediante una llamada al sistema.

.
#incluir
int escucha (int socket, int backlog);

Linux puede limitar la cantidad de conexiones pendientes que se pueden mantener en la cola. Según esta convocatoria máxima

establece la longitud de la cola en . Las conexiones entrantes que no exceden la longitud máxima de la cola se mantienen esperando el socket; Las solicitudes de conexión posteriores serán denegadas y el intento de conexión del cliente fallará. Este mecanismo lo implementa la llamada para que las solicitudes de conexión pendientes se puedan mantener mientras el programa del servidor está ocupado procesando la solicitud del cliente anterior. Muy a menudo el parámetro es 5. Devolverá 0 en caso de éxito y -1 en caso de error. Al igual que con una llamada al sistema, los errores pueden indicarse mediante las constantes AND.

Recibir solicitudes de conexión

Después de crear y nombrar un socket, el programa del servidor puede esperar solicitudes para establecer una conexión al socket mediante una llamada al sistema.

:
#incluir
int aceptar(int socket, estructura sockaddr *dirección, size_t *dirección_len);

llamada al sistema

devuelve el control cuando el programa cliente intenta conectarse al socket especificado en el parámetro. Este cliente es la primera conexión que espera en la cola para este socket. La función crea un nuevo socket para comunicarse con el cliente y devuelve su identificador. El nuevo socket será del mismo tipo que el socket del servidor que escucha las solicitudes de conexión.

Primero se debe asignar un nombre al socket mediante una llamada al sistema.

y debe tener una cola de solicitudes de conexión, cuyo espacio es asignado por la llamada al sistema. La dirección del cliente que llama se colocará en la estructura a la que apunta el parámetro. Si la dirección del cliente no es de interés, este parámetro se puede establecer en un puntero nulo.

Parámetro

especifica la longitud de la estructura de la dirección del cliente. Si la dirección del cliente es más larga que este valor, se truncará. Antes de llamar, se debe especificar la longitud esperada de la dirección en el parámetro. Al regresar de la llamada, se establecerá la longitud real de la estructura de direcciones del cliente que solicita la conexión.

Si no hay solicitudes de conexión esperando en la cola del socket, la llamada para aceptar se bloqueará (por lo que el programa no puede continuar ejecutándose) hasta que el cliente realice una solicitud de conexión. Puedes cambiar este comportamiento usando la bandera

en el descriptor del archivo socket llamándolo en su programa de esta manera:
int banderas = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK | banderas);
Devuelve un nuevo descriptor de archivo de socket si hay una solicitud de cliente esperando una conexión y -1 si hay un error. Valores posibles Los errores son los mismos que para las llamadas, más una constante adicional en el caso de que la bandera esté configurada y no haya solicitudes de conexión pendientes. Se producirá un error si el proceso se interrumpe mientras se bloquea una función.

Solicitudes de conexión

Los programas cliente se conectan a los servidores estableciendo una conexión entre un socket sin nombre y un socket de servidor que escucha las conexiones. Lo hacen llamando

:
#incluir
int connect(int socket, const struct sockaddr *dirección, size_t dirección_len);

El socket especificado en el parámetro.

, se conecta al socket del servidor especificado en el parámetro, cuya longitud es igual a . El socket debe especificarse mediante un descriptor de archivo válido obtenido de la llamada al sistema.

Si la función

se completa exitosamente, devuelve 0, en caso de error devolverá -1. Los posibles errores esta vez incluyen los valores enumerados en la tabla. 15.3.

Tabla 15.3

Si no se puede establecer una conexión inmediatamente, llame

será bloqueado por un período de espera indefinido. Cuando se excede el tiempo de espera permitido, la conexión se desconectará y la llamada finalizará de manera anormal. Sin embargo, si la llamada es interrumpida por una señal que se está procesando, la conexión fallará (con errno establecido en ), pero el intento de conexión no se cancelará: la conexión se establecerá de forma asincrónica y el programa tendrá que verificar más tarde para ver si se estableció con éxito.

Como en el caso de una llamada.

, se puede excluir la posibilidad de bloquear una llamada configurando la bandera en el descriptor del archivo. En este caso, si la conexión no se puede establecer inmediatamente, la llamada fallará con la variable igual a y la conexión se realizará de forma asíncrona.

Aunque las conexiones asincrónicas son difíciles de manejar, puedes usar la llamada

al descriptor del archivo del socket para garantizar que el socket esté listo para escribir. Discutiremos el desafío un poco más adelante en este capítulo.

Cerrar un enchufe

Puede finalizar una conexión de socket en un servidor o programa cliente llamando a la función

, al igual que en el caso de los descriptores de archivos de bajo nivel. Los enchufes deben estar cerrados en ambos extremos. En el servidor, esto se debe hacer cuando devuelve cero. Tenga en cuenta que la llamada puede bloquearse si el socket que tiene datos no enviados es de tipo orientado a conexión y el archivo . Aprenderá más sobre cómo configurar las opciones de socket más adelante en este capítulo.

Comunicación mediante enchufes.

Ahora que hemos descrito las llamadas básicas al sistema asociadas con los sockets, echemos un vistazo más de cerca a los programas de ejemplo. Intentará reelaborarlos reemplazando el socket del sistema de archivos con un socket de red. La desventaja de un socket de sistema de archivos es que si el autor no usa el nombre de archivo completo, se crea en el directorio actual del programa del servidor. Para que sea útil en la mayoría de los casos, debes crear el socket en un directorio público (como /tmp) adecuado para el servidor y sus clientes. En el caso de servidores de red, basta con seleccionar un número de puerto no utilizado.

Por ejemplo, seleccione el número de puerto 9734. Esta es una elección arbitraria para evitar el uso de puertos de servicio estándar (no debe usar números de puerto inferiores a 1024, ya que están reservados para uso del sistema). Otros números de puerto y los servicios que proporcionan suelen aparecer en el archivo del sistema /etc/services. Al escribir programas que utilizan sockets, elija siempre un número de puerto que no esté en este archivo de configuración.

Nota

Debe tener en cuenta que hubo un error intencional en los programas client2.c y server2.c, que corregirá en los programas client3.c y server3.c. No utilice el texto de los ejemplos client2.c y server2.c en sus propios programas.

Estará ejecutando su servidor y sus programas cliente en una red local, pero los sockets de red son útiles no sólo en una red local; cualquier máquina con una conexión a Internet (incluso una línea de acceso telefónico) puede usar sockets de red para comunicarse con otras computadoras; . Un programa basado en conexiones de red, se puede utilizar incluso en computadora aislada con sistema operativo UNIX, ya que dicha computadora generalmente está configurada para usar una red virtual o un bucle interno (red loopback), incluyéndose solo a sí misma. Para fines de demostración este ejemplo utiliza una red virtual, que también puede ser útil para depurar aplicaciones de red, ya que elimina cualquier problema de red externa.

Una red virtual consta de una sola computadora, tradicionalmente llamada

, con una dirección IP estándar de 127.0.0.1. Esta es una máquina local. Puede encontrar su dirección en el archivo de nodos de red etc/hosts, junto con los nombres y direcciones de otros nodos incluidos en las redes compartidas.

Cada red con la que se comunica una computadora tiene una interfaz de hardware asociada. Una computadora en cada red puede tener su propio nombre y, por supuesto, tendrá diferentes direcciones IP. Por ejemplo, la máquina de Neil llamada tilde tiene tres interfaces de red y, por lo tanto, tres direcciones. Están escritos en el archivo /etc/hosts de la siguiente manera.

127.0.0.1 localhost # Bucle
192.168.1.1 tilde.localnet # Red Ethernet privada local
158.152.X.X tilde.demon.co.uk # Línea de comunicación por módem

La primera línea es un ejemplo de una red virtual, a la segunda red se accede mediante un adaptador Ethernet y la tercera es un enlace de módem a un proveedor de servicios de Internet. Puede escribir un programa que utilice sockets de red para comunicarse con servidores utilizando cualquiera de las interfaces proporcionadas sin ninguna modificación.

Completa los ejercicios 15.3 y 15.4.

Ejercicio 15.3. Cliente de red

El siguiente es un programa cliente client2.c modificado diseñado para utilizar una conexión de red basada en sockets en una red virtual. Contiene un error menor de dependencia del hardware, pero lo discutiremos más adelante en este capítulo.

1. Incluir las directivas necesarias

y establecer las variables:
#incluir
#incluir
estructura sockaddr_in dirección;

2. Cree un socket de cliente:

3. Asigne un nombre al socket según lo acordado con el servidor:

dirección.sin_addr.s_addr = inet_addr("127.0.0.1");
dirección.sin_port = 9734;

El resto del programa es el mismo que el ejemplo dado anteriormente en este capítulo. Cuando ejecute esta versión, fallará porque no hay ningún servidor ejecutándose en el puerto 9734 de esta computadora.

Vaya: cliente2: conexión rechazada

Cómo funciona esto

El programa cliente utiliza la estructura

desde el archivo de encabezado netinet/in.h para configurar la dirección. Está intentando conectarse a un servidor alojado en un host con una dirección IP 127.0.0.1. El programa utiliza una función para convertir la representación textual de una dirección IP en un formato adecuado para el direccionamiento por socket. Consulte las páginas de ayuda en línea de inet para obtener más información sobre otras funciones de traducción de direcciones. Ejercicio 15.4. Servidor de red

También debe modificar el programa del servidor que escucha las conexiones en el número de puerto que elija. El siguiente es el programa de servidor server2.c corregido.

1. Inserte los archivos de encabezado necesarios y configure las variables:

#incluir
#incluir

int servidor_sockfd, cliente_sockfd;

2. Cree un socket sin nombre para el servidor:

3. Asigne un nombre al socket:

dirección_servidor.sin_port.s_addr = inet_addr("127.0.0.1");
dirección_servidor.sin_puerto = 9734;
server_len = tamaño de (server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

Cómo funciona esto

El programa del servidor crea un socket de dominio.

y realiza las acciones necesarias para aceptar solicitudes para conectarse a él. El socket se comunica con el puerto que seleccionó. La dirección proporcionada determina qué máquinas pueden conectarse. Al especificar la misma dirección de red virtual que en el programa cliente, limita las conexiones a la máquina local únicamente.

Si desea permitir que el servidor establezca conexiones con clientes remotos, debe especificar un conjunto de direcciones IP permitidas. Puedes aplicar un valor especial

para indicar que aceptará solicitudes de conexión de todas las interfaces disponibles en su computadora. Si es necesario, puede demarcar interfaces en diferentes redes para separar las conexiones LAN de las conexiones WAN. Una constante es un entero de 32 bits que se puede utilizar en un campo de estructura de dirección. Pero primero debes resolver el problema.

Orden de bytes en la computadora y en la red.

Si ejecuta las versiones dadas de los programas de servidor y cliente en una máquina basada en un procesador Intel que ejecuta Linux, entonces use el comando

Puedes ver las conexiones de red. Este comando se incluye en la mayoría de los sistemas UNIX configurados para redes. Muestra una conexión cliente-servidor esperando a ser cerrada. La conexión se cierra después de un breve retraso. (Repetimos que la salida en diferentes versiones Linux puede variar.)
$ ./servidor2 y ./cliente2
Conexiones activas a Internet (sin servidores)
tcp 1 0 localhost:1574 localhost:1174 TIME_WAIT raíz
Nota

Antes de probar los siguientes ejemplos de este capítulo, asegúrese de terminar de ejecutar los programas de ejemplo del lado del servidor porque competirán por las conexiones de los clientes y verá resultados engañosos. Puede eliminarlos todos (incluidos los que se enumeran más adelante en este capítulo) con el siguiente comando:

Killall servidor1 servidor2 servidor3 servidor4 servidor5

Podrás ver los números de puerto asignados a la conexión servidor-cliente. La dirección local representa el servidor y la dirección externa representa el cliente remoto. (Incluso si el cliente está alojado en la misma máquina, aún se conecta a través de la red). Para mantener todos los sockets distintos, los puertos del cliente suelen ser diferentes del socket del servidor que escucha las solicitudes de conexión y son únicos dentro de la computadora.

Muestra la dirección local (zócalo del servidor) 1574 (o puede mostrar el nombre del servicio).

) y el puerto seleccionado en el ejemplo es 9734. ¿Por qué son diferentes? El hecho es que los números de puerto y las direcciones se transmiten a través de interfaces de socket como números binarios. EN diferentes computadoras Se utiliza un orden de bytes diferente para representar números enteros. Por ejemplo, Procesador Intel almacena un entero de 32 bits como cuatro bytes consecutivos de memoria en el siguiente orden 1-2-3-4, donde el primer byte es el más significativo. Los procesadores IBM PowerPC almacenarán números enteros en el siguiente orden de bytes: 4-3-2-1. Si la memoria utilizada para almacenar números enteros simplemente se copia byte a byte, las dos computadoras no estarán de acuerdo en los valores de los números enteros.

Para que diferentes tipos de computadoras coincidan en el significado de los números enteros multibyte enviados a través de una red, es necesario determinar el orden de los bytes de la red. Antes de transmitir datos, los programas cliente y servidor deben convertir su propia representación interna de números enteros al endianismo de la red. Esto se hace usando funciones definidas en el archivo de encabezado netinet/in.h. Estos incluyen lo siguiente:

#incluir
unsigned long int htonl(unsigned long int hostlong);
htons cortos sin firmar (int corto sin firmar hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

Estas funciones convierten números enteros de 16 y 32 bits del orden de bytes nativo al de red y viceversa. Sus nombres corresponden al nombre abreviado de las conversiones realizadas, por ejemplo, "host a red, largo" (htonl, computadora a red, enteros largos) y "host a red, corto" (htons, computadora a red, enteros cortos) . Para computadoras con orden de bytes de red, estas funciones proporcionan operaciones vacías.

Para garantizar un orden correcto al pasar un entero de 16 bits, su servidor y cliente deben aplicar estas funciones a la dirección del puerto. Se deben realizar los siguientes cambios en el programa server3.c:

dirección_servidor.sin_addr_s_addr = htonl(INADDR_ANY);

Resultado devuelto por la función.

, no es necesario realizar la conversión porque, por definición, devuelve un resultado en el orden de bytes de la red. Es necesario realizar el siguiente cambio en el programa client3.c:
dirección.sin_port = htons(9734);

Al servidor, gracias al uso de una constante

, se ha realizado un cambio para permitir que se acepten solicitudes de conexión desde cualquier dirección IP.

Ahora, al ejecutar los programas server3 y client3, verá el número de puerto correcto utilizado para la conexión local:

Proto Recv-Q Send-Q Dirección local Dirección extranjera (Estado) Usuario
tcp 1 0 localhost:9734 localhost:1175 TIME_WAIT raíz
Nota

Si está utilizando una computadora cuyo formato entero nativo coincide con el orden de bytes de la red, no verá ninguna diferencia. Pero para garantizar una interacción correcta entre clientes y servidores con diferentes arquitecturas, es importante utilizar siempre funciones de conversión.

Información de red

Hasta ahora, los programas cliente y servidor tenían direcciones y números de puerto compilados. En programas de servidor y cliente más generales, puede utilizar datos de red para determinar qué direcciones y puertos utilizar.

Si tiene permiso para hacerlo, puede agregar su servidor a la lista de servicios conocidos en el archivo /etc/services, que asigna nombres a los números de puerto para que los clientes puedan usar nombres de servicios simbólicos en lugar de números.

De la misma manera, conociendo el nombre de la computadora, puede determinar la dirección IP llamando a las funciones de la base de datos del host que encontrarán estas direcciones. Lo hacen pidiendo ayuda a archivos de configuración, como etc/hosts o a servicios de información de red como NIS (Network Information Services, anteriormente conocido como Páginas Amarillas) y DNS ( Nombre de dominio Servicio, servicio de nombres de dominio).

Las funciones de la base de datos del Host se declaran en el archivo de encabezado de la interfaz netdb.h:

struct hostent *gethostbyaddr(const void* addr, size_t len, int type);
struct hostent* gethostbyname(const char* nombre);

La estructura devuelta por estas funciones debe contener, como mínimo, los siguientes elementos.

char *h_nombre; /* Nombre del nodo */
char **h_aliases; /* Lista de apodos */
int h_addrtype; /* Tipo de dirección */
int h_longitud; /* Longitud de la dirección en bytes */
char **h_addr_list /* Lista de direcciones (orden de bytes de red) */

Si no hay ninguna entrada en la base de datos que coincida con el host o la dirección dados, las funciones de información devolverán un puntero nulo.

Del mismo modo, se puede obtener información sobre servicios y números de puerto asociados utilizando funciones de información servicios:

struct servent *getservbyname(const char *nombre, const char *proto);
struct servent *getservbyport(int puerto, const char *proto);

Parámetro

Especifica el protocolo que se utilizará para conectarse al servicio, ya sea "tcp" para conexiones TCP de tipo o "udp" para datagramas UDP de tipo.

Estructura

contiene al menos los siguientes elementos:
char *s_nombre; /* Nombre del servicio */
char **s_aliases; /* Lista de alias (nombres adicionales) */
int deporte_puerto; /* número de puerto IP */
char *s_proto; /* Tipo de servicio, normalmente "tcp" o "udp" */

Puede reunir información de la computadora desde la base de datos del host llamando a la función

y mostrando sus resultados. Tenga en cuenta que la dirección debe convertirse al tipo apropiado y pasar de la intercalación de red a una cadena imprimible mediante la conversión definida de la siguiente manera:
#incluir
char *inet_ntoa(struct in_addr in);

La función convierte la dirección de un nodo de Internet en una cadena de formato cuádruple con puntos. Devuelve -1 en caso de error, pero el estándar POSIX no define errores específicos. Otra nueva característica que aplicarás es

:
int gethostname(char *nombre, int longitud del nombre);

Esta función escribe el nombre del nodo actual en la cadena especificada por el parámetro

. El nombre del nodo será una cadena terminada en nulo. El argumento contiene la longitud del nombre de la cadena y si el nombre del nodo devuelto excede esta longitud, se truncará. La función devuelve 0 en caso de éxito y -1 en caso de error. Nuevamente, los errores no están definidos en el estándar POSIX.

Haga el ejercicio 15.5.

Ejercicio 15.5. Información de red

Este programa getname.c recupera información sobre la computadora.

1. Como de costumbre, inserte los archivos de encabezado apropiados y declare las variables:


char *host, **nombres, **dirección;

2. Asignar a una variable

el valor del argumento proporcionado al llamar al programa, o por defecto el nombre de la máquina del usuario:

3. Llame a la función gethostbyname e informe un error si no se encuentra información:

fprintf(stderr, "no se puede obtener información para el host: %s\n", host);

4. Muestre el nombre del nodo y los alias que pueda tener:

printf("resultados para el host %s:\n", host);
printf("Nombre: %s\n", hostinfo->h_name);
printf(" %s", *nombres); nombres++;

5. Si el nodo solicitado no es un nodo IP, informe esto y complete la ejecución:

si (hostinfo->h_addrtype! = AF_INET) (
fprintf(stderr, "¡no es un host IP!\n");

6. De lo contrario, imprima la(s) dirección(es) IP:

addrs = hostinfo->h_addr_list;
printf(" %s", inet_ntoa(*(struct in_addr*)*addrs));

Para determinar un host por una dirección IP determinada, puede utilizar la función

. Puede usarlo en el servidor para saber desde dónde solicita el cliente la conexión.

Cómo funciona esto

El programa getname llama a la función gethostbyname para recuperar información del host de la base de datos del host. Muestra el nombre de la computadora, sus alias (otros nombres por los que se conoce a la computadora) y las direcciones IP que utiliza en sus interfaces de red. En una de las máquinas de los autores, ejecutar el ejemplo y especificar el nombre tilde como argumento dio como resultado la salida de dos interfaces: una red Ethernet y una línea de comunicación por módem.

Cuándo usar el nombre de host

, la red virtual está configurada:

Ahora puede modificar su programa cliente para conectarse a cualquier host designado en la red. En lugar de conectarse al servidor en su ejemplo, se conectará a un servicio estándar y podrá recuperar el número de puerto.

La mayoría de los sistemas UNIX y algunos sistemas operativos Linux hacen que la fecha y la hora del sistema estén disponibles como un servicio estándar llamado

. Los clientes pueden conectarse a este servicio para saber qué piensa el servidor sobre la hora y fecha actuales. El ejercicio 15:6 muestra un programa cliente, getdate.c, que hace precisamente eso. Ejercicio 15.6. Conexión al servicio estándar

1. Comience con directivas regulares

y anuncios:
int principal(int argc, char *argv) (

2. Busque la dirección del host e informe un error si no se encuentra la dirección:

hostinfo = gethostbyname(host);

3.Asegúrate de que haya un servicio en tu computadora.

:
servinfo = getservbyname("diario", "tcp");
printf("el puerto diurno es %d\n", ntohs(servinfo->s_port));

4. Cree un socket:

sockfd = socket(AF_INET, SOCK_STREAM, 0);

5. Cree una dirección para la conexión:

dirección.sin_familia = AF_INET;
dirección.sin_port = servinfo->s_port;
dirección.sin_addr = *(struct in_addr *)*hostinfo->h_addr_list;

6. Luego conéctate y obtén información:

resultado = conectar(sockfd, (struct sockaddr *)&dirección, len);
resultado = leer(sockfd, buffer, sizeof(buffer));

Puedes usar el programa.

para obtener la hora del día de cualquier nodo de red conocido.
leer 26 bytes: 24 de junio de 2007 06:03:03 BST

Si recibe un mensaje de error como

Vaya: getdate: conexión rechazada
Vaya: getdate: no existe tal archivo o directorio

el motivo puede ser que el servicio no esté habilitado en la computadora a la que se está conectando

. Este comportamiento se ha convertido en estándar en la mayoría de los sistemas Linux modernos. En la siguiente sección, verá cómo habilitar este y otros servicios.

Cómo funciona esto

Cuando ejecuta este programa, puede especificar el host al que conectarse. Número de puerto de servicio

definido por la función de base de datos de red, que devuelve información sobre los servicios de red de la misma manera que cuando obtiene información sobre un nodo de red. El programa intenta conectarse a la dirección que aparece primero en la lista de direcciones adicionales para el host especificado. Si la conexión es exitosa, el programa lee la información devuelta por el servicio diurno, una cadena de caracteres que contiene la fecha y hora del sistema.

Demonio de Internet (xinetd/inetd)

Los sistemas UNIX que proporcionan una serie de servicios de red a menudo lo hacen utilizando un superservidor. Este programa (el demonio de Internet xinetd o inetd) escucha simultáneamente solicitudes de conexión con múltiples direcciones de puerto. Cuando un cliente se conecta a un servicio, el programa demonio inicia el servidor correspondiente. Con este enfoque, los servidores no necesitan funcionar constantemente; pueden iniciarse según demanda.

Nota

En los sistemas Linux modernos, el papel del demonio de Internet lo desempeña el programa xinetd. Reemplazó al programa UNIX original inetd, que todavía se puede encontrar en sistemas Linux anteriores y otros sistemas similares a UNIX.

El programa xinetd generalmente se configura mediante una interfaz gráfica de usuario para administrar los servicios de red, pero también puede editar los archivos de configuración del programa directamente. Estos incluyen el archivo /etc/xinetd.conf y los archivos en el directorio /etc/xinetd.d.

Cada servicio proporcionado por xinetd tiene un archivo de configuración en el directorio /etc/xinetd.d. xinetd lee todos estos archivos de configuración durante el inicio y nuevamente cuando los recibe. comando correspondiente.

.
# Predeterminado: deshabilitado
# Descripción: servidor diurno. Esta es la versión tcp.

El siguiente archivo de configuración es para el servicio de transferencia de archivos.

# Predeterminado: deshabilitado
# El servidor FTP vsftpd maneja conexiones FTP. el usa
# para autenticación, nombres de usuario simples y sin cifrar y
# contraseñas, vsftpd está diseñado para ser seguro.
# Nota: Este archivo contiene la configuración de inicio de vsftpd para xinetd.
# El archivo de configuración para el programa vsftpd se encuentra en
# log_on_success += DURACIÓN ID DE USUARIO
El socket al que se conecta el programa generalmente lo maneja el propio programa xinetd (está marcado como interno) y se puede habilitar usando el tipo de socket (tcp) o el tipo de socket (udp).

Servicio de transferencia de archivos

Se conecta sólo con sockets de tipo y es proporcionado por un programa externo, en este caso vsftpd. El demonio ejecutará este programa externo cuando un cliente se conecte al puerto.

Para habilitar los cambios en la configuración del servicio, puede editar la configuración de xinetd y enviar una señal de colgar al proceso del demonio, pero recomendamos utilizar una forma más amigable de configurar los servicios. Para permitir que su cliente se conecte al servicio

, habilite este servicio utilizando las herramientas proporcionadas por el sistema Linux. En los sistemas SUSE y openSUSE, los servicios se pueden configurar desde el Centro de control de SUSE, como se muestra en la Figura. 15.1. Las versiones de Red Hat (tanto Enterprise Linux como Fedora) tienen una interfaz de configuración similar. Habilita el servicio para solicitudes TCP y UDP.

Arroz. 15.1


Para sistemas que usan inetd en lugar de xinetd, el siguiente es un extracto equivalente del archivo de configuración de inetd, /etc/inetd.conf, que inetd usa para decidir cuándo iniciar los servidores:

#
# Eco, descarte, diurno y cargado se utilizan principalmente para
flujo diurno tcp nowait root interno
dgram diurno udp espera raíz interna
# Estos son servicios estándar.
flujo ftp tcp-nowait raíz /usr/sbin/tcpd /usr/sbin/wu.ftpd
telnet stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.telnetd
# Fin del archivo inetd.conf.

Tenga en cuenta que en nuestro ejemplo, el servicio ftp lo proporciona el programa externo wu.ftpd. Si su sistema ejecuta el demonio inetd, puede cambiar el conjunto de servicios proporcionados editando el archivo /etc/inetd.conf (un # al principio de la línea indica que se trata de una línea de comentario) y reiniciando el proceso inetd. Esto se puede hacer enviando una señal de colgar usando el comando

. Para facilitar este proceso, algunos sistemas están configurados para que el programa inetd escriba su ID en un archivo. De lo contrario, puedes usar el comando:

Opciones de enchufe

Hay muchas opciones que se pueden utilizar para controlar el comportamiento de las conexiones basadas en sockets; demasiadas para describirlas en detalle en este capítulo. Para manipular parámetros utilice la función.

:
#incluir
int setsockopt(int socket, int nivel, int nombre_opción,
const void *valor de opción, tamaño_t opción len);

Puede establecer parámetros en diferentes niveles Jerarquía de protocolos. Para configurar opciones a nivel de socket debe especificar

igual Para configurar parámetros en un nivel de protocolo inferior (TCP, UDP, etc.), establezca el parámetro de nivel en el número de protocolo (obtenido del archivo de encabezado netinet/in.h o de la función).

En discusión

Se indica el nombre del parámetro que se especifica y el argumento contiene un valor de longitud de byte arbitrario que se pasa sin cambios al controlador de protocolo de bajo nivel.

Los parámetros de nivel de socket se definen en el archivo de encabezado sys/socket.h e incluyen los que se muestran en la Tabla. 15,4 valores.


Tabla 15.5

Opciones

y tome un valor entero para establecer o habilitar (1) y restablecer o deshabilitar (0). El parámetro requiere una estructura de tipo, definida en el archivo sys/socket.h, que especifica el estado del parámetro y el valor del intervalo de retardo. devuelve 0 si tiene éxito y -1 en caso contrario. En las páginas del interactivo. guía de referencia Se describen parámetros y errores adicionales.

Múltiples clientes

Hasta ahora en este capítulo, ha visto cómo se utilizan los sockets para implementar sistemas cliente-servidor, tanto locales como operativos en una red. Una vez que se establece una conexión basada en sockets, se comportan como descriptores de archivos abiertos de bajo nivel y muy parecidos a tuberías bidireccionales.

Ahora debemos considerar el caso de varios clientes que se conectan simultáneamente al servidor. Ha visto que cuando un programa de servidor recibe una solicitud de conexión de un cliente, se crea un nuevo socket y el socket original que escucha las solicitudes de conexión permanece disponible para solicitudes posteriores. Si el servidor no puede aceptar inmediatamente solicitudes de conexión posteriores, permanecerán en una cola de espera.

El hecho de que el socket original todavía sea accesible y que los sockets se comporten como descriptores de archivos nos brinda un método para atender a muchos clientes al mismo tiempo. Si el servidor llama a la función

para crear una segunda copia de sí mismo, el nuevo proceso hijo heredará el socket abierto. Entonces podrá intercambiar datos con el cliente conectado, mientras servidor principal Continuará aceptando solicitudes de conexión posteriores. En realidad, necesita realizar un cambio muy simple en su programa de servidor, como se muestra en el Ejercicio 15.7.

Dado que está creando procesos secundarios pero no esperando a que se completen, debe hacer que el servidor ignore las señales.

, previniendo la aparición de procesos zombies. Ejercicio 15.7. Servidor para múltiples clientes

1. El programa server4.c comienza de la misma manera que el último servidor analizado con la importante adición de una directiva.

para el archivo de encabezado signal.h. Las variables y procedimientos para crear y nombrar un socket siguen siendo los mismos:
int servidor_sockfd, cliente_sockfd;
estructura sockaddr_in dirección_servidor;
estructura sockaddr_in dirección_cliente;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
dirección_servidor.sin_familia = AF_INET;
dirección_servidor.sin_puerto = htons(9734);
server_len = tamaño de (server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

2. Cree una cola de conexión, ignore los detalles de terminación del proceso secundario y espere las solicitudes de los clientes:

escuchar(server_sockfd, 5);
señal(SIGCHLD, SIG_IGN);
printf("servidor esperando\n");

3. Acepte la solicitud de conexión:

client_len = tamaño de (dirección_cliente);
cliente_sockfd = aceptar(servidor_sockfd,
(struct_sockaddr*)&dirección_cliente, &len_cliente);

4. Llamar

para crear un proceso para un cliente determinado y realizar una verificación para determinar si es padre o hijo: . Se necesita un retraso de cinco segundos para demostrar esto:
leer(client_sockfd, &ch, 1);
escribir(client_sockfd, &ch, 1);
cerrar(client_sockfd);

6. De lo contrario, debes ser el padre y tu trabajo con este cliente habrá terminado:

cerrar(socket_cliente);

El código incluye un retraso de cinco segundos al procesar una solicitud de cliente para simular el cálculo del servidor o el acceso a la base de datos. Si hubiera hecho esto en el servidor anterior, cada ejecución del programa cliente3 habría tomado cinco segundos. Con el nuevo servidor, podrá procesar múltiples programas cliente client3 en paralelo con un tiempo total transcurrido de poco más de cinco segundos.

$ ./cliente3 y ./cliente3 y ./cliente3 y psx

Cómo funciona esto

El programa del servidor ahora crea un nuevo proceso hijo para manejar cada cliente, por lo que es posible que vea varios mensajes de espera del servidor mientras el programa principal continúa esperando nuevas solicitudes de conexión. En la salida del comando

(editado) muestra el proceso maestro server4 con un PID de 26566 esperando nuevos clientes, mientras que tres procesos de cliente client3 son atendidos por tres servidores secundarios. Después de una pausa de cinco segundos, todos los clientes reciben sus resultados y salen. Los procesos del servidor secundario también salen, dejando solo un proceso del servidor maestro.

El programa del servidor aplica la llamada.

para manejar múltiples clientes. En una aplicación de base de datos, esta puede no ser la mejor solución, porque... El programa del servidor puede ser bastante grande y, además, existe el problema de coordinar las llamadas a la base de datos desde múltiples copias del servidor. Realmente, todo lo que necesita es una forma de manejar múltiples clientes mediante un solo servidor sin bloquear ni esperar la entrega. solicitudes de clientes. La solución a este problema implica procesar múltiples descriptores de archivos abiertos simultáneamente y no se limita a aplicaciones de socket. Consideremos la función.

seleccionar

Muy a menudo durante el desarrollo. aplicaciones linux es posible que deba verificar el estado de varias entradas para determinar la siguiente acción a realizar. Por ejemplo, un programa de comunicaciones como un emulador de terminal necesita una forma eficiente de leer desde el teclado y el puerto serie simultáneamente. En un sistema de un solo usuario, funcionaría un bucle de "espera activa", escaneando repetidamente la entrada en busca de datos y leyéndolos tan pronto como aparecen. Este comportamiento supone una gran pérdida de tiempo de CPU.

llamada al sistema

permite que un programa espere a que lleguen datos (o se complete la salida) en múltiples descriptores de archivos de bajo nivel simultáneamente. Esto significa que el programa emulador de terminal puede bloquearse hasta que tenga trabajo que hacer. Del mismo modo, un servidor puede tratar con numerosos clientes esperando solicitudes en muchos sockets abiertos simultáneamente. opera en estructuras de datos, que son conjuntos de descriptores de archivos abiertos. Para procesar estos conjuntos, se define un conjunto de macros:
#incluir #incluir
void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);

Como sus nombres sugieren, las macros

inicializa la estructura en el conjunto vacío y establece y borra los elementos del conjunto correspondientes al descriptor de archivo pasado como parámetro, y la macro devuelve un valor distinto de cero si el descriptor de archivo al que hace referencia es un elemento de la estructura a la que apunta el parámetro. La constante especifica el número máximo de descriptores de archivos en una estructura de tipo. También puede usar un valor para el tiempo de espera para evitar el bloqueo infinito. Este valor se especifica mediante la estructura. Se define en el archivo sys/time.h y contiene los siguientes elementos:
tiempo_t tv_sec; /* Segundos */
largo tv_usec; /* Microsegundos */
, definido en sys/types.h, es un número entero. La llamada al sistema se declara de la siguiente manera:
#incluir
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);

Llamar. Si el parámetro es un puntero nulo y no hay actividad en los sockets, la llamada puede bloquearse indefinidamente. devuelve el control al programa, el conjunto de descriptores se modificará para indicar descriptores de lectura o escritura listos o de error. Para comprobarlos, debe utilizar una macro para determinar qué descriptores requieren atención. Es posible cambiar el valor del tiempo de espera para indicar el tiempo restante hasta que se produzca el siguiente tiempo de espera, pero este comportamiento no está especificado en el estándar X/Open. Si se excede el tiempo de espera, se borrarán todos los conjuntos de identificadores.

La llamada de selección devuelve el número total de identificadores en los conjuntos modificados. Si falla devolverá -1 y establecerá el valor de la variable

, describiendo el error. Posibles errores - Haz el ejercicio 15.8. Ejercicio 15.8. Función

El siguiente programa muestra cómo utilizar la función de selección: select.c. Verás un ejemplo más complejo un poco más adelante. El programa lee datos del teclado (entrada estándar - descriptor 0) con una latencia de 2,5 segundos. Los datos se leen solo cuando la entrada está lista. Es natural ampliar el programa para incluir otros descriptores, como líneas seriales y enchufes, dependiendo de la naturaleza de la aplicación.

1. Comience como de costumbre con directivas.

y declaraciones, y luego inicializar para manejar la entrada del teclado:

2. Espere la entrada del archivo stdin durante un máximo de 2,5 segundos:

resultado = seleccionar(FD_SETSIZE, &testfds, (fd_set *)NULL,

3. Pasado este tiempo, comprueba

. Si no hubo entrada, el programa ejecutará el bucle nuevamente. Si ocurre un error, el programa sale:

4. Si experimenta alguna actividad relacionada con el descriptor de archivo mientras espera, lea la entrada de la entrada estándar e imprímala cada vez que se reciba un carácter EOL (fin de línea), antes de presionar la combinación de teclas. +:

$ El servidor puede usar la función

simultáneamente al socket que espera solicitudes de conexión y a los sockets de conexión del cliente. Una vez que se ha capturado la actividad, se puede utilizar una macro para recorrer todos los descriptores de archivos posibles y ver cuáles están activos.

Si el socket que escucha las solicitudes de conexión está listo para recibir entrada, eso significa que el cliente está intentando conectarse y usted puede llamar a la función.

sin riesgo de bloqueo. Si el identificador del cliente indica listo, significa que hay una solicitud de cliente esperando a que la lea y procese. Leer 0 bytes significa que el proceso del cliente ha salido y puede cerrar el socket y eliminarlo de su conjunto de identificadores.

Haga el ejercicio 15.9.

Ejercicio 15.9. Aplicación cliente-servidor mejorada

1. En el programa de ejemplo final server5.c, incluirá los archivos de encabezado sys/time.h y sys/ioctl.h en lugar del signal.h utilizado en programa anterior y declarar algunas variables adicionales para manejar la llamada

:
int servidor_sockfd, cliente_sockfd;
estructura sockaddr_in dirección_servidor;
estructura sockaddr_in dirección_cliente;

2. Cree un socket para el servidor y asígnele un nombre:

server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
dirección_servidor.sin_familia = AF_INET;
dirección_servidor.sin_addr.s_addr = htonl(INADDR_ANY);
dirección_servidor.sin_puerto = htons(9734);
server_len = tamaño de (server_address);
bind(serversockfd, (struct sockaddr *)&server_address, server_len);

3. Cree una cola de solicitudes de conexión e inicialice el conjunto.

para procesar la entrada del socket
servidor_sockfd

6. Si la actividad se registra en

, podría ser una solicitud para una nueva conexión y usted agrega la apropiada al conjunto de descriptores:
client_len = tamaño de (dirección_cliente);
cliente_sockfd = aceptar(servidor_sockfd,
(struct sockaddr*)&client_address, &client_len);
FD_SET(cliente_sockfd, &readfds);
printf("agregando cliente en fd %d\n", client_sockfd);

Si no es el servidor el que está activo, entonces el cliente está activo. Si se recibe

, el cliente desaparece y se puede eliminar del conjunto de identificadores. De lo contrario, estás "sirviendo" al cliente, como en los ejemplos anteriores.
printf("eliminando cliente en fd %d\n", fd);

Para completar, la analogía mencionada al comienzo del capítulo se encuentra en la Tabla. La figura 15.5 muestra los paralelos entre las conexiones basadas en sockets y conversaciones telefónicas.


Tabla 15.5

Datagramas

En este capítulo, nos centramos en la programación de aplicaciones que se comunican con sus clientes mediante conexiones TCP basadas en sockets. Hay situaciones en las que el coste de configurar y mantener una conexión de enchufe es innecesario.

Un buen ejemplo sería el servicio.

, utilizado anteriormente en el programa getdate.c. Usted crea un socket, establece una conexión, lee una única respuesta y cierra la conexión. ¡Tantas operaciones para simplemente conseguir una cita! También disponible a través de conexiones UDP utilizando datagramas. Para usarlo, simplemente envíe un único datagrama al servicio y reciba un único datagrama en respuesta que contenga la fecha y la hora. Es sencillo.

Los servicios proporcionados a través del protocolo UDP se utilizan en los casos en que el cliente necesita crear una solicitud breve al servidor y espera una respuesta breve única. Si el costo del tiempo de CPU es lo suficientemente bajo, el servidor puede brindar dicho servicio procesando las solicitudes de los clientes una a la vez y permitiendo que el sistema operativo mantenga una cola de solicitudes entrantes. Este enfoque simplifica la programación del servidor.

Debido a que UDP es un servicio no garantizado, es posible que experimente la pérdida de su datagrama o de la respuesta del servidor. Si los datos son importantes para usted, es posible que necesite programar sus clientes UDP cuidadosamente, verificando si hay errores y volviendo a intentarlo según sea necesario. En la práctica, los datagramas UDP son muy fiables en redes locales.

Para acceder a un servicio proporcionado por el protocolo UDP, debe utilizar llamadas al sistema

y, pero en lugar de usar y en el socket, usa dos llamadas al sistema específicas para datagramas: y.
/* Comience con las inclusiones y declaraciones habituales. */

int principal(int argc, char *argv) (
si (argc == 1) host = "localhost";
/* Busca la dirección del host e informa un error si no la encuentra. */
hostinfo = gethostbyname(host);
fprintf(stderr, "sin host: %s\n", host);
/* Comprueba la presencia del servicio diurno en el ordenador. */
servinfo = getservbyname("diario", "udp");
fprintf(stderr, "sin servicio diurno\n");
printf("el puerto diurno es %d\n", ntohs(servinfo->s_port));
/* Crea un socket UDP. */
sockfd = socket(AF_INEТ, SOCK_DGRAM, 0);
/* Genera una dirección para usar en llamadas sendto/recvfrom... */
dirección.sin_familia = AF_INET;
dirección.sin_port = servinfo->s_port;
dirección.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;
resultado = sendto(sockfd, buffer, 1, 0, (struct sockaddr *)&address, len);
resultado = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&dirección, &len);
printf("leer %d bytes: %s", resultado, buffer);

Como puede ver, sólo se necesitan cambios menores. Como antes, buscas servicio.

utilizando una llamada, pero especificando un servicio de datagrama solicitando el protocolo UDP. Un socket de datagrama se crea mediante una llamada con el archivo . La dirección de destino se especifica como antes, pero ahora en lugar de leer desde el socket, debes enviar un datagrama.

Dado que no está realizando una conexión explícita a servicios basados ​​en UDP, debe tener una forma de notificar al servidor que desea una respuesta. En este caso, envía un datagrama (en nuestro ejemplo, envía un byte del búfer en el que desea recibir una respuesta) al servicio y este envía la fecha y la hora en respuesta.

llamada al sistema

envía un datagrama desde un búfer a un socket utilizando la dirección del socket y la longitud de la dirección. Esta convocatoria en realidad tiene el siguiente prototipo:

int sendto(int sockfd, void *buffer, size_t len, int flags,

struct sockaddr *to, socklen_t tolen);

En el caso de uso normal, el parámetro

se puede dejar cero.

La llamada al sistema recvfrom espera un datagrama en una conexión de socket con la dirección dada y lo coloca en un búfer. Esta convocatoria tiene el siguiente prototipo:

int recvfrom(int sockfd, void *buffer, size_t len, int flags, y tiempos de espera para determinar si han llegado datos, al igual que con los servidores basados ​​en conexión. De lo contrario, puede utilizar una alarma para cancelar la operación de adquisición de datos. (ver capítulo 11).

Reanudar

En este capítulo, propusimos otra forma para que los procesos se comuniquen: sockets. Permiten el desarrollo de aplicaciones cliente-servidor verdaderamente distribuidas que se ejecutan en un entorno de red. Se brindó una breve descripción de algunas funciones de información de la base de datos del nodo de red y los métodos de procesamiento en el sistema. estándar de linux servicios del sistema utilizando demonios de Internet. Ha trabajado con varios ejemplos de programas cliente-servidor que demuestran el procesamiento y la conexión en red de múltiples clientes.

En conclusión, se ha familiarizado con la llamada al sistema.

, que le permite notificar al programa sobre la actividad de entrada y salida en varios sockets y descriptores de archivos abiertos a la vez.

Última actualización: 31/10/2015

Las comunicaciones por Internet que utilizan los protocolos TCP y UDP se basan en sockets. En .NET, los sockets están representados por la clase System.NET.Sockets.Socket, que proporciona una interfaz de bajo nivel para recibir y enviar mensajes a través de la red.

Veamos las principales propiedades de esta clase:

    AddressFamily: devuelve todas las direcciones utilizadas por el socket. Esta propiedad representa uno de los valores definidos en la enumeración AddressFamily del mismo nombre. La enumeración contiene 18 valores diferentes, los más utilizados son:

    • InterRed: dirección IPv4

      InterNetworkV6: dirección IPv6

      Ipx: dirección IPX o SPX

      NetBios: dirección de NetBios

    Disponible: Devuelve la cantidad de datos que están disponibles para lectura.

    Conectado: devuelve verdadero si el socket está conectado al host remoto

    LocalEndPoint: devuelve punto local, en el que se inicia el socket y en el que recibe datos

    ProtocolType: devuelve uno de los valores de enumeración ProtocolType que representa el protocolo utilizado por el socket. Existen los siguientes valores posibles:

    • Encabezado de autenticación IPSec (encabezado AH de IPv6)

      IPSecEncapsulatingSecurityPayload (encabezado ESP IPv6)

      IPv6DestinationOptions (encabezado Opciones de destino IPv6)

      IPv6FragmentHeader (encabezado de fragmento IPv6)

      IPv6HopByHopOptions (encabezado de opciones de salto a salto de IPv6)

      IPv6NoNextHeader (IPv6 sin encabezado siguiente)

      Encabezado de enrutamiento IPv6

      Desconocido (protocolo desconocido)

      No especificado (protocolo no especificado)

    Cada valor representa un protocolo correspondiente, pero los más utilizados son Tcp y Udp.

    RemoteEndPoint: devuelve la dirección del host remoto al que está conectado el socket

    SocketType: Devuelve el tipo de socket. Representa uno de los valores de la enumeración SocketType:

    • Dgram: el socket recibirá y enviará datagramas utilizando el protocolo Udp. Este tipo de socket funciona junto con el tipo de protocolo Udp y el valor AddressFamily.InterNetwork

      Sin formato: el socket tiene acceso al protocolo de la capa de transporte subyacente y puede utilizar protocolos como ICMP e IGMP para transmitir mensajes.

      Rdm: el socket puede comunicarse con hosts remotos sin instalación conexión permanente. Si los mensajes enviados por el socket no se pueden entregar, el socket recibirá una notificación al respecto.

      Seqpacket: proporciona una transferencia de datos bidireccional confiable con una conexión persistente

      Stream: proporciona una transferencia de datos bidireccional confiable con una conexión permanente. La comunicación utiliza el protocolo TCP, por lo que este tipo de socket se utiliza junto con el tipo de protocolo Tcp y el valor AddressFamily.InterNetwork.

      Desconocido: dirección NetBios

Puede utilizar uno de sus constructores para crear un objeto de socket. Por ejemplo, un socket que utiliza el protocolo TCP:

Socket socket = nuevo Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

O un socket usando el protocolo Udp:

Socket socket = nuevo Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

Así, al crear un socket podemos especificar diferentes combinaciones protocolos, tipos de sockets, valores de la enumeración AddressFamily. Sin embargo, no todas las combinaciones son correctas. Entonces, para trabajar a través del protocolo Tcp, debemos especificar los siguientes parámetros: AddressFamily.InterNetwork, SocketType.Stream y ProtocolType.Tcp. Para Udp, el conjunto de parámetros será diferente: AddressFamily.InterNetwork, SocketType.Dgram y ProtocolType.Udp. Para otros protocolos el conjunto de valores será diferente. Por lo tanto, el uso de sockets puede requerir cierto conocimiento de cómo funcionan los protocolos individuales. Aunque respecto a Tcp y Udp todo es relativamente sencillo.

Principio general de funcionamiento del enchufe.

Cuando trabajemos con sockets, independientemente de los protocolos seleccionados, nos basaremos en los métodos de la clase Socket:

    Accept(): crea un nuevo objeto Socket para manejar la conexión entrante.

    Bind(): vincula un objeto Socket a un punto final local

    Close(): cierra el socket

    Connect(): establece una conexión con un host remoto

    Listen(): comienza a escuchar las solicitudes entrantes.

    Poll(): determina el estado del socket

    Recibir(): Recibe datos.

    Enviar(): envía datos.

    Shutdown(): bloquea el envío y la recepción de datos en el socket

Dependiendo del protocolo utilizado (TCP, UDP, etc.), el principio general de trabajo con sockets variará ligeramente.

Cuando se utiliza un protocolo que requiere el establecimiento de una conexión, como TCP, el servidor debe llamar al método Bind para configurar un punto para escuchar las conexiones entrantes y luego comenzar a escuchar las conexiones utilizando el método Listen. A continuación, utilizando el método Aceptar, puede recibir solicitudes de conexión entrantes en forma de un objeto Socket, que se utiliza para interactuar con el nodo remoto. En el objeto Socket recibido, se llaman a los métodos Enviar y Recibir, respectivamente, para enviar y recibir datos. Si es necesario conectarse al servidor, se llama al método Connect. Los métodos Enviar o Recibir también se utilizan para intercambiar datos con el servidor.

Si está utilizando un protocolo sin conexión, como UDP, no necesita llamar al método Listen después de llamar al método Bind. Y en este caso, el método ReceiverFrom se usa para recibir datos y el método SendTo se usa para enviar datos.

Los enchufes se utilizan para proporcionar comunicaciones de red. Un socket es el punto final de las comunicaciones de la red. Cada socket en uso tiene un tipo y un proceso asociado. Los sockets existen dentro de los dominios de comunicación. Los dominios son abstracciones que implican una estructura de direccionamiento específica y un conjunto de protocolos que definen los diferentes tipos de sockets dentro de un dominio. Ejemplos de dominios de comunicación pueden ser: dominio UNIX, dominio de Internet, etc.

En el dominio de Internet, un socket es una combinación de una dirección IP y un número de puerto que identifica de forma única un único proceso de red en toda la Internet global. Dos sockets, uno para el host receptor y otro para el host emisor, definen la conexión para protocolos orientados a conexión como TCP.

  • Creando un enchufe
  • Vinculación a nombres locales
  • Estableciendo una conexión
  • Transferencia de datos
  • (Usado solo por procesos que administran tablas de enrutamiento).

Creando un enchufe

Para crear un socket, utilice la llamada al sistema de socket.

S = socket(dominio, tipo, protocolo);

Esta llamada se basa en información sobre el dominio de comunicación y el tipo de socket. Para utilizar las funciones de Internet, los valores de los parámetros deben ser los siguientes:

  • dominio de comunicación - AF_INET (protocolos de Internet).
  • tipo de socket - SOCK_STREAM; Este tipo proporciona un flujo de bytes consistente, confiable y orientado a la comunicación bidireccional.

El flujo de tipo socket se mencionó anteriormente. A continuación se proporciona una breve descripción de otros tipos de enchufes:

  • Socket de datagrama: admite flujo de datos bidireccional. No hay garantía de que este flujo sea consistente, confiable o que los datos no se dupliquen. Una característica importante de este conector es que los límites de registro de datos están predefinidos.
  • Socket sin formato: proporciona acceso al usuario a protocolos de comunicación subyacentes que admiten abstracciones de socket. Estos sockets suelen estar orientados a datagramas.

La función de socket crea un punto final para las comunicaciones y devuelve un descriptor de archivo que hace referencia al socket, o -1 en caso de error. Este descriptor se utiliza más adelante para establecer comunicación.

Para crear un socket de tipo flujo con protocolo TCP Para proporcionar soporte de comunicación, la llamada a la función de socket debe ser la siguiente:

S = enchufe(AF_INET, SOCK_STREAM, 0);

Vinculación a nombres locales

El socket se crea sin nombre. Hasta que un socket tenga un nombre asociado, los procesos remotos no pueden hacer referencia a él y, por lo tanto, no se pueden recibir mensajes en ese socket. Los procesos de comunicación utilizan asociaciones para estos fines. En un dominio de Internet, una asociación consta de una dirección local y remota y un puerto local y remoto. En la mayoría de los dominios, la asociación debe ser única.

En el dominio de Internet, asociar un socket y un nombre puede ser bastante complejo, pero afortunadamente generalmente no es necesario asociar específicamente una dirección y un número de puerto con un socket, ya que las funciones de conexión y envío asociarán automáticamente un socket determinado con un socket adecuado. dirección si esto no se ha hecho antes llamar.

Para vincular un socket con una dirección y un número de puerto, use la llamada al sistema de vinculación:

enlazar(s, nombre, nombrelen);

El nombre de enlace es una cadena de bytes de longitud variable interpretada por el protocolo admitido. La interpretación puede variar según los diferentes dominios de comunicación.

Estableciendo una conexión

En el lado del cliente, la comunicación se establece mediante la función de conexión estándar:

Error = conectar(s, serveraddr, serveraddrlen);

que inicia la comunicación en el socket utilizando los descriptores de socket e información de la estructura serveraddr, que es de tipo sockaddr_in, que contiene la dirección del servidor y el número de puerto en el que se debe establecer la comunicación. Si el socket no ha sido vinculado a una dirección, connect llamará automáticamente a la función del sistema de vinculación.

Connect devuelve 0 si la llamada fue exitosa. Un valor de retorno de -1 indica que se produjo algún error durante el proceso de protocolo de enlace. Si la llamada a la función es exitosa, el proceso puede operar en el identificador del socket usando las funciones de lectura y escritura, y cerrar el canal usando la función de cierre.

Del lado del servidor, el proceso de establecimiento de la conexión es más complicado. Cuando un servidor desea ofrecer uno de sus servicios, vincula un socket a una dirección públicamente conocida asociada con ese servicio y escucha pasivamente en ese socket. Para estos fines, se utiliza la llamada al sistema de escucha:

Error = escuchar(s, qlength);

donde s es el descriptor del socket y qlength es cantidad máxima solicitudes de protocolo de enlace que pueden estar en cola esperando ser procesadas por el servidor; este número puede estar limitado por las características del sistema.

Cuando el servidor recibe una solicitud de un cliente y decide establecer una conexión, crea un nuevo socket y lo asocia con una asociación equivalente a un "socket de escucha". Para un dominio de Internet esto significa el mismo número de puerto. La llamada al sistema de aceptación se utiliza para este propósito:

Newsock = aceptar(s, clientaddr, clientaddrlen);

El socket asociado con el cliente y el socket devuelto por la función de aceptación se utilizan para establecer comunicación entre el servidor y el cliente.

Transferencia de datos

Una vez establecida la conexión, el proceso de transferencia de datos puede comenzar utilizando varias funciones. Una vez conectado, el usuario puede enviar y recibir mensajes utilizando las funciones de lectura y escritura:

Escribir(s, buf, tamaño de(buf)); leer(s, buf, tamaño de(buf));

Las llamadas de envío y recepción son casi idénticas a las de lectura y escritura, excepto que se agrega un argumento de banderas.

Enviar(s, buf, sizeof(buf), banderas); recv(s, buf, tamaño de(buf), banderas);

Se pueden especificar uno o más indicadores utilizando valores distintos de cero, como los siguientes:

  • MSG_OOB: envía/recibe datos específicos de los sockets de transmisión.
  • MSG_PEEK: ver datos sin leer. cuando se especifica en recv, todos los datos presentes se devuelven al usuario, pero los datos en sí se dejan como "no leídos". La próxima lectura o recepción llamada en este socket devolverá los datos leídos la última vez.
  • MSG_DONTROUTE: envía datos sin enrutar paquetes. (Usado solo por procesos que administran tablas de enrutamiento).

(Usado solo por procesos que administran tablas de enrutamiento).

Cuando los módulos de comunicación deciden dejar de transmitir datos y cerrar la sesión de comunicación, intercambian un protocolo de enlace de tres vías con segmentos que contienen el bit No más datos del remitente (también llamado bit FIN).

"No hay más datos del remitente" (este bit también se llama bit FIN).

Cerrar(es);

cerrar(es);

Apagado(s, cómo);

apagado(s, cómo);

  • donde how tiene uno de los siguientes valores:
  • 0 - si el usuario ya no desea leer los datos
  • 1 - si ya no se enviarán datos

2 - si no se enviarán ni recibirán datos

/* La función MakeConnection asigna un socket y establece una conexión con el host remoto. Número de puerto predeterminado 80. Entrada: nombre del servidor WWW (con el número de puerto, si no es 80) Salida: descriptor de archivo en caso de éxito -1 en caso de error */ int MakeConnection(unsigned char* ServerName)( int s; struct sockaddr_in ssin; struct hostent* hp; int PortNum; unsigned char strHlp, *pch; /* use el número de puerto predeterminado: 80 o un número específico del nombre del servidor */ strcpy(strHlp,NombreServidor); pch = strchr(strHlp,":"); if(pch==NULL)( PortNum = 80; )else( pch = "\0"; pch++; PortNum = atoi(pch); if(PortNum==0)( PortNum = 80; ) )


Arriba