¿Cuál es la diferencia entre read()
y recv()
, y entre send()
y write()
en la programación de socket en términos de rendimiento, velocidad y otros comportamientos?
¿Cuál es la diferencia entre read()
y recv()
, y entre send()
y write()
en la programación de socket en términos de rendimiento, velocidad y otros comportamientos?
Respuestas:
La diferencia es que recv()
/ send()
funciona solo en descriptores de socket y le permite especificar ciertas opciones para la operación real. Esas funciones son un poco más especializadas (por ejemplo, puede establecer un indicador para ignorar SIGPIPE
o enviar mensajes fuera de banda ...).
Funciones read()
/ write()
son las funciones de descriptor de archivo universal que funcionan en todos los descriptores.
recv
y read
no entregarán datos a la persona que llama, pero tampoco ningún error. Para la persona que llama, el comportamiento es el mismo. Es posible que la persona que llama ni siquiera sepa nada sobre datagramas (es posible que no sepa que se trata de un socket y no de un archivo, puede que no sepa que se trata de un socket de datagrama y no un socket de flujo). Que el datagrama permanezca pendiente es un conocimiento implícito sobre cómo funcionan las pilas de IP en los núcleos y no son visibles para la persona que llama. Desde la perspectiva de la persona que llama, seguirán proporcionando un comportamiento igual.
recv
? La razón por la cual recv
y send
dónde se introdujo en primer lugar fue el hecho de que no todos los conceptos de datagramas podrían asignarse al mundo de las transmisiones. read
y write
trate todo como un flujo de datos, ya sea una tubería, un archivo, un dispositivo (por ejemplo, un puerto serie) o un zócalo. Sin embargo, un socket es solo una secuencia real si utiliza TCP. Si usa UDP es más como un dispositivo de bloque. Pero si ambas partes lo usan como un flujo, funcionará como un flujo y ni siquiera puede enviar un paquete UDP vacío usando write
llamadas, por lo que esta situación no surgirá.
read () es equivalente a recv () con un parámetro flags de 0. Otros valores para el parámetro flags cambian el comportamiento de recv (). Del mismo modo, write () es equivalente a send () con flags == 0.
recv
sólo puede ser utilizado en una toma de corriente, y producirá un error si se intenta utilizarlo en, por ejemplo, STDIN_FILENO
.
read()
y write()
son más genéricos, funcionan con cualquier descriptor de archivo. Sin embargo, no funcionarán en Windows.
Puede pasar opciones adicionales a send()
y recv()
, por lo que es posible que deba usarlas en algunos casos.
Hace poco me di cuenta de que cuando utilicé write()
un socket en Windows, casi funciona (el FD pasado write()
no es el mismo que el pasado send()
; solía _open_osfhandle()
hacer que pasara el FD write()
). Sin embargo, no funcionó cuando intenté enviar datos binarios que incluían el carácter 10. En write()
algún lugar insertó el carácter 13 antes de esto. Cambiarlo a send()
un parámetro de banderas de 0 solucionó ese problema. read()
podría tener el problema inverso si 13-10 son consecutivos en los datos binarios, pero no lo he probado. Pero eso parece ser otra posible diferencia entre send()
y write()
.
Otra cosa en Linux es:
send
no permite operar en fd sin socket. Por lo tanto, por ejemplo para escribir en el puerto usb, write
es necesario.
¿"Rendimiento y velocidad"? ¿No son ese tipo de ... sinónimos, aquí?
De todos modos, la recv()
llamada toma banderas que read()
no lo hacen, lo que la hace más poderosa, o al menos más conveniente. Esa es una diferencia. No creo que haya una diferencia de rendimiento significativa, pero no la he probado.
En Linux también noto que:
Interrupción de llamadas al sistema y funciones de la biblioteca por parte de los manejadores de señales
Si se invoca un manejador de señales mientras se bloquea una llamada al sistema o una función de la biblioteca, entonces:
la llamada se reinicia automáticamente después de que vuelve el controlador de señal; o
la llamada falla con el error EINTR.
... Los detalles varían según los sistemas UNIX; a continuación, los detalles para Linux.
Si un controlador de señal interrumpe una llamada bloqueada a una de las siguientes interfaces, la llamada se reinicia automáticamente después de que el controlador de señal regrese si se utilizó el indicador SA_RESTART; de lo contrario, la llamada falla con el error EINTR:
- leerllamadas (2), readv (2), write (2), writev (2) e ioctl (2) en dispositivos "lentos".
.....
Las siguientes interfaces nunca se reinician después de ser interrumpidas por un controlador de señal, independientemente del uso de SA_RESTART; siempre fallan con el error EINTR cuando son interrumpidos por un controlador de señal:
Interfaces de socket de "entrada", cuando se ha establecido un tiempo de espera (SO_RCVTIMEO) en el socket utilizando setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (también con un valor NULL argumento de tiempo de espera) y recvmsg (2).
Interfaces de socket de "salida", cuando se ha establecido un tiempo de espera (SO_RCVTIMEO) en el socket utilizando setsockopt (2): connect (2), send (2), sendto (2) y sendmsg (2).
Consulte man 7 signal
para más detalles.
Un uso simple sería usar señal para evitar recvfrom
bloqueo indefinido.
Un ejemplo de APUE :
#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#define BUFLEN 128
#define TIMEOUT 20
void
sigalrm(int signo)
{
}
void
print_uptime(int sockfd, struct addrinfo *aip)
{
int n;
char buf[BUFLEN];
buf[0] = 0;
if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
err_sys("sendto error");
alarm(TIMEOUT);
//here
if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {
if (errno != EINTR)
alarm(0);
err_sys("recv error");
}
alarm(0);
write(STDOUT_FILENO, buf, n);
}
int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err;
struct sigaction sa;
if (argc != 2)
err_quit("usage: ruptime hostname");
sa.sa_handler = sigalrm;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < 0)
err_sys("sigaction error");
memset(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_DGRAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
err_quit("getaddrinfo error: %s", gai_strerror(err));
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
err = errno;
} else {
print_uptime(sockfd, aip);
exit(0);
}
}
fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
exit(1);
}
#define write(...) send(##__VA_ARGS__, 0)
.