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);
}