¿Cuál es la diferencia entre read () y recv (), y Between send () y write ()?

198

¿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?

Sajad Bahmani
fuente
3
Piense en escribir como se aplica la siguiente manera: #define write(...) send(##__VA_ARGS__, 0).
cuidadoso1 de

Respuestas:

128

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 SIGPIPEo enviar mensajes fuera de banda ...).

Funciones read()/ write()son las funciones de descriptor de archivo universal que funcionan en todos los descriptores.

Gonzalo
fuente
3
Esto es incorrecto, hay otra diferencia en el caso de datagramas de longitud 0: si hay un datagrama de longitud cero pendiente, read (2) y recv () con un argumento de banderas de cero proporcionan un comportamiento diferente. En esta circunstancia, leer (2) no tiene efecto (el datagrama permanece pendiente), mientras que recv () consume el datagrama pendiente.
Abhinav Gauniyal
2
@AbhinavGauniyal ¿Cómo proporcionaría un comportamiento diferente ? Si hay un datagrama de 0 bytes, ambos, recvy readno 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.
Mecki
2
@Mecki que no es conocimiento implícito para todos,
tómame
1
@Mecki, ¿qué indica una lectura exitosa sin bloqueo de 0 bytes? ¿El datagrama sigue pendiente? Exactamente eso, y solo eso, me preocupa: el comportamiento de que un datagrama puede permanecer pendiente incluso si se lee con éxito. No estoy seguro de si la situación puede surgir, por lo que me gustaría tenerla en cuenta.
sehe
2
@sehe Si estás preocupado, ¿por qué no lo usas recv? La razón por la cual recvy senddó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. ready writetrate 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 writellamadas, por lo que esta situación no surgirá.
Mecki
85

Por el primer golpe en Google

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.

Jonathan Feinberg
fuente
31
Esta no es toda la historia. recvsólo puede ser utilizado en una toma de corriente, y producirá un error si se intenta utilizarlo en, por ejemplo, STDIN_FILENO.
Joey Adams
77
Este hilo es ahora el primer éxito en Google, a Google le encanta stackoverflow
Eloff
12

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.

Bastien Léonard
fuente
7

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().

ajb
fuente
2
+1. Consulte también winsock que no admite lectura / escritura
Joseph Quinsey
6

Otra cosa en Linux es:

sendno permite operar en fd sin socket. Por lo tanto, por ejemplo para escribir en el puerto usb, writees necesario.

Mert Mertce
fuente
2

¿"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.

relajarse
fuente
15
Quizás no tener que lidiar con banderas puede ser percibido como más conveniente.
semaj
2

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 signalpara 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);
}
Almiar
fuente