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?
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?
Réponses:
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.
recv
et read
ne 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.
recv
? La raison pour laquelle recv
et send
où 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. read
et write
traiter 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' write
appels, donc cette situation ne se produira pas.
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.
recv
ne peut être utilisé sur une prise, et produira une erreur si vous essayez de l' utiliser, disons, STDIN_FILENO
.
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.
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()
.
Une autre chose sur Linux est:
send
ne permet pas de fonctionner sur un fd non socket. Ainsi, par exemple pour écrire sur le port USB, write
est nécessaire.
"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.
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 signal
pour plus de détails.
Une utilisation simple serait d'utiliser un signal pour éviter de recvfrom
bloquer 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);
}
#define write(...) send(##__VA_ARGS__, 0)
.