Quelle est la différence entre read () et recv (), et entre send () et write ()?

198

Quelle est la différence entre read()et recv(), et entre send()et write()dans la programmation de socket en termes de performances, de vitesse et d'autres comportements?

Sajad Bahmani
la source
3
Pensez à écrire comme mis en œuvre comme ceci: #define write(...) send(##__VA_ARGS__, 0).
prudencenow1

Réponses:

128

La différence est que recv()/ send()ne fonctionne que sur les descripteurs de socket et vous permet de spécifier certaines options pour l'opération réelle. Ces fonctions sont légèrement plus spécialisées (par exemple, vous pouvez définir un indicateur pour ignorer SIGPIPE, ou pour envoyer des messages hors bande ...).

Les fonctions read()/ write()sont les fonctions de descripteur de fichiers universelles fonctionnant sur tous les descripteurs.

Gonzalo
la source
3
C'est incorrect, il y a une autre différence en cas de datagrammes de longueur 0 - Si un datagramme de longueur nulle est en attente, read (2) et recv () avec un argument drapeaux de zéro fournissent un comportement différent. Dans ce cas, read (2) n'a aucun effet (le datagramme reste en attente), tandis que recv () consomme le datagramme en attente.
Abhinav Gauniyal
2
@AbhinavGauniyal Comment cela fournirait-il un comportement différent ? S'il y a un datagramme de 0 octet, les deux, recvet readne fourniront aucune donnée à l'appelant mais également aucune erreur. Pour l'appelant, le comportement est le même. L'appelant peut même ne rien savoir sur les datagrammes (il peut ne pas savoir qu'il s'agit d'un socket et non d'un fichier, il peut ne pas savoir qu'il s'agit d'un socket de datagramme et non d'un socket de flux). Le fait que le datagramme reste en attente est une connaissance implicite du fonctionnement des piles IP dans les noyaux et non visible par l'appelant. Du point de vue de l'appelant, ils offriront toujours un comportement égal.
Mecki
2
@Mecki ce n'est pas une connaissance implicite pour tout le monde, prenez-moi par exemple :)
Abhinav Gauniyal
1
@Mecki qu'indique une lecture réussie non bloquante de 0 octet? Le datagramme reste-t-il toujours en attente? C'est exactement cela, et seulement cela, qui m'inquiète: le comportement qu'un datagramme peut rester en attente même s'il est lu avec succès. Je ne sais pas si la situation peut survenir, c'est pourquoi j'aimerais garder cela à l'esprit.
sehe
2
@sehe Si vous êtes inquiet, pourquoi ne l'utilisez-vous pas recv? La raison pour laquelle recvet sendoù a été introduit en premier lieu était le fait que tous les concepts de datagrammes ne pouvaient pas être mappés au monde des flux. readet writetraiter tout comme un flux de données, que ce soit un canal, un fichier, un périphérique (par exemple un port série) ou une socket. Pourtant, un socket n'est un vrai flux que s'il utilise TCP. S'il utilise UDP, c'est plus comme un périphérique bloc. Mais si les deux côtés l'utilisent comme un flux, cela fonctionnera comme un flux et vous ne pouvez même pas envoyer un paquet UDP vide à l'aide d' writeappels, donc cette situation ne se produira pas.
Mecki,
85

Par le premier hit sur Google

read () est équivalent à recv () avec un paramètre flags de 0. D'autres valeurs pour le paramètre flags changent le comportement de recv (). De même, write () est équivalent à send () avec des drapeaux == 0.

Jonathan Feinberg
la source
31
Ce n'est pas toute l'histoire. recvne peut être utilisé sur une prise, et produira une erreur si vous essayez de l' utiliser, disons, STDIN_FILENO.
Joey Adams
77
Ce fil est maintenant le premier hit sur Google, Google adore stackoverflow
Eloff
12

read()et write()sont plus génériques, ils fonctionnent avec n'importe quel descripteur de fichier. Cependant, ils ne fonctionneront pas sous Windows.

Vous pouvez transmettre des options supplémentaires à send()et recv(), vous devrez donc peut-être les utiliser dans certains cas.

Bastien Léonard
la source
7

Je viens de remarquer récemment que lorsque je l'utilisais write()sur un socket dans Windows, cela fonctionne presque (le FD transmis à write()n'est pas le même que celui transmis à send(); j'avais l'habitude de _open_osfhandle()faire passer le FD à write()). Cependant, cela n'a pas fonctionné lorsque j'ai essayé d'envoyer des données binaires qui comprenaient le caractère 10. write()quelque part inséré le caractère 13 avant cela. Le changer en send()avec un paramètre drapeaux de 0 a résolu ce problème. read()pourrait avoir le problème inverse si 13-10 sont consécutifs dans les données binaires, mais je ne l'ai pas testé. Mais cela semble être une autre différence possible entre send()et write().

ajb
la source
6

Une autre chose sur Linux est:

sendne permet pas de fonctionner sur un fd non socket. Ainsi, par exemple pour écrire sur le port USB, writeest nécessaire.

Mert Mertce
la source
2

"Performance et vitesse"? N'est-ce pas ce genre de ... synonymes, ici?

Quoi qu'il en soit, l' recv()appel prend des drapeaux qui read()ne le font pas, ce qui le rend plus puissant, ou du moins plus pratique. Voilà une différence. Je ne pense pas qu'il y ait une différence de performance significative, mais je n'ai pas testé pour cela.

se détendre
la source
15
Peut-être que ne pas avoir à traiter avec des drapeaux peut être perçu comme plus pratique.
semaj
2

Sous Linux, je remarque également que:

Interruption des appels système et des fonctions de bibliothèque par les gestionnaires de signaux
Si un gestionnaire de signaux est appelé alors qu'un appel système ou un appel de fonction de bibliothèque est bloqué, alors:

  • l'appel est automatiquement redémarré après le retour du gestionnaire de signal; ou

  • l'appel échoue avec l'erreur EINTR.

... Les détails varient selon les systèmes UNIX; ci-dessous, les détails pour Linux.

Si un appel bloqué à l'une des interfaces suivantes est interrompu par un gestionnaire de signal, l'appel est automatiquement redémarré après le retour du gestionnaire de signal si l'indicateur SA_RESTART a été utilisé; sinon l'appel échoue avec l'erreur EINTR:

  • read (2), readv (2), write (2), writev (2) et ioctl (2) sur les périphériques "lents".

.....

Les interfaces suivantes ne sont jamais redémarrées après avoir été interrompues par un gestionnaire de signaux, quelle que soit l'utilisation de SA_RESTART; ils échouent toujours avec l'erreur EINTR lorsqu'ils sont interrompus par un gestionnaire de signaux:

  • Interfaces de socket "Input", lorsqu'un timeout (SO_RCVTIMEO) a été défini sur le socket à l'aide de setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (également avec une valeur non NULL) argument timeout) et recvmsg (2).

  • Interfaces de socket "Sortie", lorsqu'un délai d'expiration (SO_RCVTIMEO) a été défini sur le socket à l'aide de setsockopt (2): connect (2), send (2), sendto (2) et sendmsg (2).

Vérifiez man 7 signalpour plus de détails.


Une utilisation simple serait d'utiliser un signal pour éviter de recvfrombloquer indéfiniment.

Un exemple d' 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);
}
Meule
la source