[Red informática] Interpretación de la API de socket de interfaz de programación de red (6)

         Socket es una API expuesta por la pila de protocolos de red a los programadores. En comparación con los complejos protocolos de red informática, la API abstrae operaciones clave y datos de configuración, lo que simplifica la programación del programa.

        El contenido del socket descrito en este artículo proviene de Linux man . Este artículo presenta principalmente cada API en detalle para comprender mejor la programación de sockets.


recv

recv() cumple con POSIX.1 - 2008

1.Biblioteca

标准 c 库,libc, -lc

2.Archivo de encabezado

<sys/socket.h>

3.Definición de interfaz

       ssize_t recv(int sockfd, void buf[.len], size_t len,
                        int flags);

       ssize_t recvfrom(int sockfd, void buf[restrict .len], size_t len,
                        int flags,
                        struct sockaddr *_Nullable restrict src_addr,
                        socklen_t *_Nullable restrict addrlen);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

4.Descripción de la interfaz

        Las llamadas recv(), recvfrom() y recvmsg() se utilizan para recibir mensajes del socket y se pueden utilizar en sockets conectados o no conectados. Primero describimos las características comunes de varias llamadas al sistema y luego presentamos sus diferencias.

        La única diferencia entre recv() y read(2) es si hay una marca de bandera. Cuando la marca de recv() es 0, es básicamente equivalente a read(2) (consulte la sección de notas para obtener más detalles). Del mismo modo, la siguiente llamada:

recv(sockfd, buf, len, flags);

        Equivalente a

recvfrom(sockfd, buf, len, flags, NULL, NULL);

         Las tres llamadas devuelven la longitud del mensaje en caso de éxito. Si el búfer proporcionado no contiene el mensaje, el mensaje sobrante puede descartarse, según el tipo de socket receptor.

        Si no hay ningún mensaje en el socket, la llamada de recepción esperará hasta que llegue un mensaje, a menos que el socket no sea bloqueante (consulte fcntl(2)), en cuyo caso se devolverá -1 y errno se establecerá en EAGAIN o EWOLOBLOQUEAR. Normalmente, la llamada de recepción regresará siempre que haya datos disponibles, que estén cerca de la cantidad de datos solicitada, en lugar de esperar hasta que se reciba toda la cantidad de datos solicitada.

        Las aplicaciones pueden usar select(2), poll(2) y epoll(7) para decidir cuándo hay más datos disponibles en el socket.

        parámetro de banderas

        flags es el OR bit a bit de los siguientes valores:

       MSG_CMSG_CLOEXEC (solo recvmsg() disponible)

        Establece el indicador de cierre de excepción del descriptor del archivo receptor, implementado mediante la operación SCM_RIGHTS del descriptor de archivo del dominio UNIX. El propósito de esta bandera es similar a O_CLOEXEC.

        MSG_DONTWAIT

        Habilite operaciones sin bloqueo. Si la operación es para bloquear, la llamada informará errores EAGAIN y EWOULDBLOCK. Esto es similar a configurar el indicador O_NONBLOCK (a través de la operación fcntl (2) F_SETFL), pero MSG_DONTWAIT solo es efectivo para esta llamada, y O_NONBLOCK se establece en el descriptor de archivo, lo que afectará a todos los subprocesos de todos los procesos que llaman y a otros subprocesos que contienen El proceso del mango del zócalo.

        MSG_ERRQUEQUE

        Este indicador especifica que los errores en cola deben recibirse en la cola de errores del socket y que el mensaje de error se pasará con un tipo específico de protocolo (IP_RECVERR para IPv4). El usuario debe proporcionar un tamaño de búfer suficiente. El tamaño de la carga útil del mensaje original que causó el error se pasa en el formato msg_iovec de datos normales, y la dirección del mensaje original que causó el error se proporciona en el formato msg_name.

        Los errores se proporcionan en la estructura sock_extended_err:

                  #define SO_EE_ORIGIN_NONE    0
                  #define SO_EE_ORIGIN_LOCAL   1
                  #define SO_EE_ORIGIN_ICMP    2
                  #define SO_EE_ORIGIN_ICMP6   3

                  struct sock_extended_err
                  {
                      uint32_t ee_errno;   /* Error number */
                      uint8_t  ee_origin;  /* Where the error originated */
                      uint8_t  ee_type;    /* Type */
                      uint8_t  ee_code;    /* Code */
                      uint8_t  ee_pad;     /* Padding */
                      uint32_t ee_info;    /* Additional information */
                      uint32_t ee_data;    /* Other data */
                      /* More data may follow */
                  };

                  struct sockaddr *SO_EE_OFFENDER(struct sock_extended_err *);

        ee_errno contiene el valor errno del error en cola, ee_origin es el código del origen del error y otros campos están relacionados con el protocolo. La macro SO_EE_OFFENDER devuelve como información auxiliar la dirección del objeto de red donde ocurrió el error. Si la dirección es desconocida, sa_family en sockaddr contendrá AF_UNSPEC y otros campos tendrán valores desconocidos. La carga útil del mensaje que provocó el error se entrega como datos normales.

        Para errores locales, no se pasa ninguna dirección (esto se puede confirmar mediante el valor cmsg_len de cmsghdr). Cuando se recibe un error, msghdr establece el indicador MSG_ERRQUEQUE. Después de pasar el error, el código de error del socket se regenera en función del siguiente error en cola y se pasa cuando ocurre la siguiente operación del socket.

        MSG_OOB

        Esta solicitud de etiqueta recibe datos fuera de banda que normalmente no se recibirían en volúmenes de datos normales. Algunos protocolos colocarán datos acelerados delante de la cola de datos normal, lo que hará que esta etiqueta no se utilice en estos protocolos.

        MSG_PEEK

        Este indicador especifica que los datos se reciben del encabezado de la cola de recepción y no elimina los datos de la cola. Por lo tanto, la siguiente llamada recibida devuelve el mismo valor.

        MSG_TRUNC

        Para el protocolo original (AF_PACKET), el datagrama de Internet, el enlace de red, el datagrama UNIX y los sockets de paquetes secuenciados devolverán la longitud real del paquete o mensaje, incluso si es mayor que el búfer proporcionado.

        Para sockets de flujo de red, consulte tcp(7).

        MSG_WAITALL

        Esta operación de solicitud de marca espera hasta que el tamaño de la solicitud se cumpla por completo. Luego, cuando ocurre una señal, un error, una desconexión, los datos recibidos posteriormente son de un tipo diferente a los datos anteriores, la llamada aún puede devolver menos datos que el tamaño solicitado.

recvdesde()

        recvfrom() coloca el mensaje recibido en el buffer buf. La persona que llama debe especificar el tamaño de buf a través del parámetro len.

        Si src_addr no es NULL y el protocolo subyacente proporciona la dirección original del mensaje, entonces la dirección original se completará con src_addr. En este caso, addrlen es un parámetro de entrada y salida. Antes de llamar, se debe inicializar al tamaño del búfer src_addr y se actualizará con el tamaño real de la dirección original cuando se devuelva. Si el búfer proporcionado es demasiado pequeño, la dirección de retorno se truncará, en cuyo caso el valor de addrlen será mayor que el valor proporcionado.

        Si la persona que llama no está interesada en la dirección original, tanto src_addr como addrlen deben configurarse en NULL.

recv()

        recv() generalmente solo se puede usar en sockets conectados (ver connect(2)), es equivalente a la siguiente llamada:

           recvfrom(fd, buf, len, flags, NULL, 0);

recvmsg()

         La llamada recvmsg() utiliza la estructura msghdr para reducir el número de parámetros que deben pasarse. La estructura se define en <sys/socket.h> de la siguiente manera:

           struct msghdr {
               void         *msg_name;       /* Optional address */
               socklen_t     msg_namelen;    /* Size of address */
               struct iovec *msg_iov;        /* Scatter/gather array */
               size_t        msg_iovlen;     /* # elements in msg_iov */
               void         *msg_control;    /* Ancillary data, see below */
               size_t        msg_controllen; /* Ancillary data buffer len */
               int           msg_flags;      /* Flags on received message */
           };

        El campo msg_name apunta a un búfer asignado por el usuario que se utiliza para almacenar la dirección de origen de un socket no conectado. La persona que llama debe establecer el tamaño del búfer a través de msg_namelen. Una vez devuelto con éxito, msg_namelen se establecerá en el tamaño real de la dirección de origen. Si a la aplicación no le importa la dirección de origen, msg_name se puede configurar en NULL.

        msg_iov y msg_iovlen describen regiones de dispersión y recopilación (es decir, algún tipo de lista de búfer dispersa), que se analiza en readv(2). 

        El campo msg_control tiene la longitud de msg_controllen y es un búfer preparado para otros mensajes de control de protocolo o varios datos auxiliares. Cuando se llama a recvmsg(), msg_controllen debe especificar el tamaño del búfer msg_control disponible, que contendrá el tamaño de la secuencia de mensajes de control tras un retorno exitoso. El formato del mensaje es el siguiente:

           struct cmsghdr {
               size_t cmsg_len;    /* Data byte count, including header
                                      (type is socklen_t in POSIX) */
               int    cmsg_level;  /* Originating protocol */
               int    cmsg_type;   /* Protocol-specific type */
           /* followed by
               unsigned char cmsg_data[]; */
           };

        Sólo se debe acceder a los datos auxiliares mediante macros definidas en cmsg(3).

        Como ejemplo, Linux utiliza este mecanismo de datos auxiliar para pasar errores extendidos, opciones de IP y descriptores de archivos en sockets de dominio UNIX; consulte unix(7) e ip(7).

        El campo msg_fags en msghdr se actualiza cuando recvmsg() regresa y puede contener algunas de las siguientes etiquetas:

        MSG_EOR

        Indica el final del registro y se han devuelto todos los datos del registro (normalmente utilizado en SOCK_SEQPACKET).

        MSG_TRUNC

        Indica que el final del datagrama se descartó porque era mayor que el tamaño del búfer proporcionado.

        MSG_OOB

        Indica la llegada de datos fuera de banda o acelerados

5.Valor de retorno

        La llamada devuelve el número de bytes de datos recibidos.

        Cuando ocurre un error, se devuelve -1 y se configura errno para indicar el tipo de error.

        Cuando el par del socket de flujo se cierra, se devolverá 0 (el retorno EOF tradicional).

        Los sockets de datagramas en cada dominio permiten mensajes de longitud 0. Cuando se recibe dicho mensaje, el valor devuelto es 0.

        El valor de retorno también puede ser 0 cuando una solicitud de socket de flujo recibe 0 bytes.

        El valor del error se define de la siguiente manera:

OTRA VEZ/EWOULDBLOCK Si el socket está marcado como sin bloqueo y la operación de recepción está destinada a bloquear, o se establece un valor de tiempo de espera, se produce un tiempo de espera antes de que lleguen los datos. POSIX.1 permite el uso de cualquiera de los dos valores de error y no supone que los dos valores sean iguales, lo que requiere comprobaciones de la aplicación para comprobar ambos errores.
EBADF El parámetro sockfd es un descriptor de archivo ilegal
ECONNRECHAZADO El host remoto rechazó la conexión de red (normalmente el servicio solicitado no se está ejecutando)
FALLA El puntero del búfer de recepción apunta a una dirección fuera de proceso
EINTR La operación de recepción se ve interrumpida por una señal pasada antes de que lleguen los datos.
ELECCIÓN ÚNICA El parámetro es ilegal.
ENOMEMA No se puede asignar memoria para recvmsg()
ENOTCONN El socket es un socket orientado a la conexión, pero no hay conexión (ver conectar (2) y aceptar (2))
ENOTSOCK el descriptor de archivo no es un socket

6.Atención

       Si hay un paquete de longitud 0 en espera, el comportamiento de procesamiento de read(2) y recv() marcado con 0 es diferente. read(2) no tiene ningún efecto (el mensaje aún está esperando), mientras que recv() consume el mensaje.

        Consulte recvmmsg(2) para obtener una lista de llamadas al sistema específicas de Linux para manejar múltiples mensajes en una sola llamada.

7.Código

   Server program

       #include <netdb.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/socket.h>
       #include <sys/types.h>
       #include <unistd.h>

       #define BUF_SIZE 500

       int
       main(int argc, char *argv[])
       {
           int                      sfd, s;
           char                     buf[BUF_SIZE];
           ssize_t                  nread;
           socklen_t                peer_addrlen;
           struct addrinfo          hints;
           struct addrinfo          *result, *rp;
           struct sockaddr_storage  peer_addr;

           if (argc != 2) {
               fprintf(stderr, "Usage: %s port\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           memset(&hints, 0, sizeof(hints));
           hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
           hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
           hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
           hints.ai_protocol = 0;          /* Any protocol */
           hints.ai_canonname = NULL;
           hints.ai_addr = NULL;
           hints.ai_next = NULL;

           s = getaddrinfo(NULL, argv[1], &hints, &result);
           if (s != 0) {
               fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
               exit(EXIT_FAILURE);
           }

           /* getaddrinfo() returns a list of address structures.
              Try each address until we successfully bind(2).
              If socket(2) (or bind(2)) fails, we (close the socket
              and) try the next address. */

           for (rp = result; rp != NULL; rp = rp->ai_next) {
               sfd = socket(rp->ai_family, rp->ai_socktype,
                            rp->ai_protocol);
               if (sfd == -1)
                   continue;

               if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
                   break;                  /* Success */

               close(sfd);
           }

           freeaddrinfo(result);           /* No longer needed */

           if (rp == NULL) {               /* No address succeeded */
               fprintf(stderr, "Could not bind\n");
               exit(EXIT_FAILURE);
           }

           /* Read datagrams and echo them back to sender. */

           for (;;) {
               char host[NI_MAXHOST], service[NI_MAXSERV];

               peer_addrlen = sizeof(peer_addr);
               nread = recvfrom(sfd, buf, BUF_SIZE, 0,
                                (struct sockaddr *) &peer_addr, &peer_addrlen);
               if (nread == -1)
                   continue;               /* Ignore failed request */

               s = getnameinfo((struct sockaddr *) &peer_addr,
                               peer_addrlen, host, NI_MAXHOST,
                               service, NI_MAXSERV, NI_NUMERICSERV);
               if (s == 0)
                   printf("Received %zd bytes from %s:%s\n",
                          nread, host, service);
               else
                   fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

               if (sendto(sfd, buf, nread, 0, (struct sockaddr *) &peer_addr,
                          peer_addrlen) != nread)
               {
                   fprintf(stderr, "Error sending response\n");
               }
           }
       }
   Client program

       #include <netdb.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/socket.h>
       #include <sys/types.h>
       #include <unistd.h>

       #define BUF_SIZE 500

       int
       main(int argc, char *argv[])
       {
           int              sfd, s;
           char             buf[BUF_SIZE];
           size_t           len;
           ssize_t          nread;
           struct addrinfo  hints;
           struct addrinfo  *result, *rp;

           if (argc < 3) {
               fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           /* Obtain address(es) matching host/port. */

           memset(&hints, 0, sizeof(hints));
           hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
           hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
           hints.ai_flags = 0;
           hints.ai_protocol = 0;          /* Any protocol */

           s = getaddrinfo(argv[1], argv[2], &hints, &result);
           if (s != 0) {
               fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
               exit(EXIT_FAILURE);
           }

           /* getaddrinfo() returns a list of address structures.
              Try each address until we successfully connect(2).
              If socket(2) (or connect(2)) fails, we (close the socket
              and) try the next address. */

           for (rp = result; rp != NULL; rp = rp->ai_next) {
               sfd = socket(rp->ai_family, rp->ai_socktype,
                            rp->ai_protocol);
               if (sfd == -1)
                   continue;

               if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
                   break;                  /* Success */

               close(sfd);
           }

           freeaddrinfo(result);           /* No longer needed */

           if (rp == NULL) {               /* No address succeeded */
               fprintf(stderr, "Could not connect\n");
               exit(EXIT_FAILURE);
           }

           /* Send remaining command-line arguments as separate
              datagrams, and read responses from server. */

           for (size_t j = 3; j < argc; j++) {
               len = strlen(argv[j]) + 1;
                       /* +1 for terminating null byte */

               if (len > BUF_SIZE) {
                   fprintf(stderr,
                           "Ignoring long message in argument %zu\n", j);
                   continue;
               }

               if (write(sfd, argv[j], len) != len) {
                   fprintf(stderr, "partial/failed write\n");
                   exit(EXIT_FAILURE);
               }

               nread = read(sfd, buf, BUF_SIZE);
               if (nread == -1) {
                   perror("read");
                   exit(EXIT_FAILURE);
               }

               printf("Received %zd bytes: %s\n", nread, buf);
           }

           exit(EXIT_SUCCESS);
       }

Artículo siguiente [Red informática] Interpretación de la API de socket de interfaz de programación de red (7 )

Supongo que te gusta

Origin blog.csdn.net/BillyThe/article/details/132852181
Recomendado
Clasificación