Was ist der Unterschied zwischen read()
und recv()
und zwischen send()
und write()
in der Socket-Programmierung in Bezug auf Leistung, Geschwindigkeit und andere Verhaltensweisen?
Was ist der Unterschied zwischen read()
und recv()
und zwischen send()
und write()
in der Socket-Programmierung in Bezug auf Leistung, Geschwindigkeit und andere Verhaltensweisen?
Antworten:
Der Unterschied besteht darin, dass recv()
/ send()
nur an Socket-Deskriptoren arbeitet und Sie bestimmte Optionen für die eigentliche Operation angeben können. Diese Funktionen sind etwas spezialisierter (Sie können beispielsweise ein Flag zum Ignorieren setzenSIGPIPE
oder zum Senden von Out-of-Band-Nachrichten setzen ...).
Funktionen read()
/ write()
sind die universellen Dateideskriptorfunktionen, die an allen Deskriptoren arbeiten.
recv
und es read
werden keine Daten an den Aufrufer liefern , sondern auch kein Fehler. Für den Anrufer ist das Verhalten dasselbe. Der Anrufer weiß möglicherweise nicht einmal etwas über Datagramme (er weiß möglicherweise nicht, dass dies ein Socket und keine Datei ist, er weiß möglicherweise nicht, dass dies ein Datagramm-Socket und kein Stream-Socket ist). Dass das Datagramm aussteht, ist implizites Wissen darüber, wie IP-Stacks in Kerneln funktionieren und für den Aufrufer nicht sichtbar sind. Aus Sicht des Anrufers bieten sie weiterhin das gleiche Verhalten.
recv
? Der Grund, warum recv
und send
wo überhaupt eingeführt wurde, war die Tatsache, dass nicht alle Datagrammkonzepte auf die Welt der Streams abgebildet werden konnten. read
und write
behandeln Sie alles als einen Datenstrom, sei es eine Pipe, eine Datei, ein Gerät (z. B. eine serielle Schnittstelle) oder ein Socket. Ein Socket ist jedoch nur dann ein echter Stream, wenn er TCP verwendet. Wenn es UDP verwendet, ist es eher ein Blockgerät. Wenn beide Seiten es jedoch wie einen Stream verwenden, funktioniert es wie ein Stream, und Sie können nicht einmal ein leeres UDP-Paket mithilfe von write
Aufrufen senden , sodass diese Situation nicht auftritt.
Nach dem ersten Treffer bei Google
read () entspricht recv () mit einem Flags-Parameter von 0. Andere Werte für den Flags-Parameter ändern das Verhalten von recv (). In ähnlicher Weise entspricht write () send () mit Flags == 0.
recv
kann nur für einen Socket verwendet werden und erzeugt einen Fehler, wenn Sie versuchen, ihn beispielsweise für einen Socket zu verwenden STDIN_FILENO
.
read()
und write()
sind allgemeiner, sie arbeiten mit jedem Dateideskriptor. Unter Windows funktionieren sie jedoch nicht.
Sie können zusätzliche Optionen an send()
und übergeben recv()
, sodass Sie sie in einigen Fällen möglicherweise verwenden müssen.
Ich habe erst kürzlich bemerkt, dass write()
es fast funktioniert , wenn ich einen Socket in Windows verwendet habe (der FD, an den übergeben wurde, write()
ist nicht der gleiche wie der, an den übergeben wurde send()
; ich habe _open_osfhandle()
den FD dazu gebracht, an zu übergeben write()
). Es hat jedoch nicht funktioniert, als ich versucht habe, Binärdaten zu senden, die Zeichen 10 enthielten. write()
Irgendwo zuvor wurde Zeichen 13 eingefügt. Das Ändern send()
mit einem Flags-Parameter von 0 hat dieses Problem behoben. read()
könnte das umgekehrte Problem haben, wenn 13-10 in den Binärdaten aufeinanderfolgend sind, aber ich habe es nicht getestet. Aber das scheint ein weiterer möglicher Unterschied zwischen send()
und zu sein write()
.
Eine andere Sache unter Linux ist:
send
erlaubt nicht, auf Nicht-Socket-FD zu arbeiten. So ist beispielsweise das Schreiben auf den USB-Port write
erforderlich.
"Leistung und Geschwindigkeit"? Sind das nicht ... Synonyme hier?
Auf jeden Fall recv()
nimmt der Anruf Flags entgegen, die read()
dies nicht tun, was ihn leistungsfähiger oder zumindest bequemer macht. Das ist ein Unterschied. Ich glaube nicht, dass es einen signifikanten Leistungsunterschied gibt, habe ihn aber nicht getestet.
Unter Linux stelle ich außerdem fest, dass:
Unterbrechung von Systemaufrufen und Bibliotheksfunktionen durch Signalhandler
Wenn ein Signalhandler aufgerufen wird, während ein Systemaufruf oder ein Bibliotheksfunktionsaufruf blockiert ist, gilt Folgendes:
Der Anruf wird automatisch neu gestartet, nachdem der Signalhandler zurückgekehrt ist. oder
Der Aufruf schlägt mit dem Fehler EINTR fehl.
... Die Details variieren zwischen UNIX-Systemen. unten die Details für Linux.
Wenn ein blockierter Anruf an eine der folgenden Schnittstellen von einem Signalhandler unterbrochen wird, wird der Anruf automatisch neu gestartet, nachdem der Signalhandler zurückgekehrt ist, wenn das Flag SA_RESTART verwendet wurde. Andernfalls schlägt der Aufruf mit dem Fehler EINTR fehl:
- read (2), readv (2), write (2), writev (2) und ioctl (2) rufen auf "langsamen" Geräten auf.
..... .....
Die folgenden Schnittstellen werden unabhängig von der Verwendung von SA_RESTART niemals neu gestartet, nachdem sie von einem Signalhandler unterbrochen wurden. Sie schlagen immer mit dem Fehler EINTR fehl, wenn sie von einem Signalhandler unterbrochen werden:
Socket-Schnittstellen "Eingabe", wenn für das Socket mit setsockopt (2) ein Timeout (SO_RCVTIMEO) festgelegt wurde: accept (2), recv (2), recvfrom (2), recvmmsg (2) (auch mit einem Nicht-NULL-Wert) Timeout-Argument) und recvmsg (2).
"Output" -Socket-Schnittstellen, wenn mit setsockopt (2) ein Timeout (SO_RCVTIMEO) für den Socket festgelegt wurde: connect (2), send (2), sendto (2) und sendmsg (2).
Überprüfen Sie man 7 signal
für weitere Details.
Eine einfache Verwendung wäre die Verwendung eines Signals, um ein recvfrom
unbegrenztes Blockieren zu vermeiden .
Ein Beispiel aus 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)
.