Qual è la differenza tra read()
e recv()
, e tra send()
e write()
nella programmazione socket in termini di prestazioni, velocità e altri comportamenti?
Qual è la differenza tra read()
e recv()
, e tra send()
e write()
nella programmazione socket in termini di prestazioni, velocità e altri comportamenti?
Risposte:
La differenza è che recv()
/ send()
funziona solo con i descrittori di socket e consente di specificare determinate opzioni per l'operazione effettiva. Tali funzioni sono leggermente più specializzate (ad esempio, è possibile impostare un flag da ignorare SIGPIPE
o per inviare messaggi fuori banda ...).
Le funzioni read()
/ write()
sono le funzioni del descrittore di file universale che funzionano su tutti i descrittori.
recv
e read
non fornirà dati al chiamante ma anche nessun errore. Per il chiamante, il comportamento è lo stesso. Il chiamante potrebbe anche non sapere nulla dei datagrammi (potrebbe non sapere che si tratta di un socket e non di un file, potrebbe non sapere che si tratta di un socket di datagramma e non di un socket di flusso). Il fatto che il datagramma rimanga in sospeso è una conoscenza implicita del funzionamento degli stack IP nei kernel e non visibile al chiamante. Dal punto di vista del chiamante, forniranno comunque un comportamento uguale.
recv
? Il motivo per cui recv
e send
dove è stato introdotto in primo luogo è stato il fatto che non tutti i concetti di datagramma potevano essere mappati sul mondo dei flussi. read
e write
considera tutto come un flusso di dati, sia esso una pipe, un file, un dispositivo (ad esempio una porta seriale) o un socket. Tuttavia, un socket è un flusso reale solo se utilizza TCP. Se utilizza UDP è più simile a un dispositivo a blocchi. Ma se entrambe le parti lo usano come un flusso, funzionerà come un flusso e non è nemmeno possibile inviare un pacchetto UDP vuoto usando le write
chiamate, quindi questa situazione non si presenterà.
Per il primo successo su Google
read () equivale a recv () con un parametro flags di 0. Altri valori per il parametro flags cambiano il comportamento di recv (). Allo stesso modo, write () equivale a send () con flags == 0.
recv
può essere utilizzato solo su un socket, e produrrà un errore se si tenta di utilizzarlo su, diciamo, STDIN_FILENO
.
read()
e write()
sono più generici, funzionano con qualsiasi descrittore di file. Tuttavia, non funzioneranno su Windows.
Puoi passare opzioni aggiuntive a send()
e recv()
, quindi potresti doverle utilizzare in alcuni casi.
Ho notato di recente che quando ho usato write()
su un socket in Windows, funziona quasi (l'FD passato write()
non è lo stesso di quello passato send()
; ero solito _open_osfhandle()
far passare l'FD write()
). Tuttavia, non ha funzionato quando ho provato a inviare dati binari che includevano il carattere 10. write()
da qualche parte ha inserito il carattere 13 prima di questo. Modificandolo in send()
con un parametro flags di 0 risolto quel problema. read()
potrebbe avere il problema inverso se 13-10 sono consecutivi nei dati binari, ma non l'ho provato. Ma questa sembra essere un'altra possibile differenza tra send()
e write()
.
Un'altra cosa su Linux è:
send
non consente di operare su fd senza socket. Pertanto, ad esempio per scrivere sulla porta USB, write
è necessario.
"Prestazioni e velocità"? Quel tipo di ... sinonimi non sono qui?
Comunque, la recv()
chiamata prende flag che read()
non lo fanno, il che la rende più potente, o almeno più conveniente. Questa è una differenza. Non penso che ci sia una differenza significativa nelle prestazioni, ma non l'ho testato.
Su Linux noto anche che:
Interruzione delle chiamate di sistema e delle funzioni di libreria da parte dei gestori di segnale
Se un gestore di segnale viene richiamato mentre una chiamata di sistema o una chiamata di funzione di libreria viene bloccata, allora:
la chiamata viene riavviata automaticamente dopo il ritorno del gestore del segnale; o
la chiamata fallisce con l'errore EINTR.
... I dettagli variano tra i sistemi UNIX; sotto, i dettagli per Linux.
Se una chiamata bloccata a una delle seguenti interfacce viene interrotta da un gestore di segnale, la chiamata viene riavviata automaticamente dopo che il gestore di segnale ritorna se è stato utilizzato il flag SA_RESTART; altrimenti la chiamata fallisce con l'errore EINTR:
- leggi (2), readv (2), write (2), writev (2) e ioctl (2) su dispositivi "lenti".
.....
Le seguenti interfacce non vengono mai riavviate dopo essere state interrotte da un gestore di segnale, indipendentemente dall'uso di SA_RESTART; falliscono sempre con l'errore EINTR quando vengono interrotti da un gestore di segnale:
Interfacce socket "Input", quando un timeout (SO_RCVTIMEO) è stato impostato sul socket utilizzando setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (anche con un non-NULL argomento timeout) e recvmsg (2).
Interfacce socket "Output", quando un timeout (SO_RCVTIMEO) è stato impostato sul socket utilizzando setsockopt (2): connect (2), send (2), sendto (2) e sendmsg (2).
Controlla man 7 signal
per maggiori dettagli.
Un semplice utilizzo sarebbe usare il segnale per evitare il recvfrom
blocco indefinito.
Un esempio da 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)
.