Was ist der Unterschied zwischen read () und recv () und zwischen send () und write ()?

198

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?

Sajad Bahmani
quelle
3
Stellen Sie sich das Schreiben so vor : #define write(...) send(##__VA_ARGS__, 0).
carefulnow1

Antworten:

128

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.

Gonzalo
quelle
3
Dies ist falsch, es gibt einen weiteren Unterschied bei Datagrammen mit einer Länge von 0 - Wenn ein Datagramm mit einer Länge von Null ansteht, bieten read (2) und recv () mit einem Flags-Argument von Null ein anderes Verhalten. Unter diesen Umständen hat read (2) keine Auswirkung (das Datagramm steht noch aus), während recv () das anstehende Datagramm verwendet.
Abhinav Gauniyal
2
@AbhinavGauniyal Wie würde das ein anderes Verhalten bewirken ? Wenn es eine 0 - Byte - Datagramm ist, beide, recvund es readwerden 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.
Mecki
2
@Mecki, das ist nicht implizites Wissen für alle, nehmen Sie mich zum Beispiel :)
Abhinav Gauniyal
1
@Mecki Was bedeutet ein nicht blockierendes erfolgreiches Lesen von 0 Bytes? Bleibt das Datagramm noch ausstehend? Genau das und nur das beunruhigt mich: das Verhalten, dass ein Datagramm auch bei erfolgreichem Lesen anstehend bleiben kann. Ich bin mir nicht sicher, ob die Situation eintreten kann, weshalb ich sie gerne im Auge behalten würde.
sehe
2
@sehe Wenn Sie sich Sorgen machen, warum verwenden Sie nicht recv? Der Grund, warum recvund sendwo überhaupt eingeführt wurde, war die Tatsache, dass nicht alle Datagrammkonzepte auf die Welt der Streams abgebildet werden konnten. readund writebehandeln 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 writeAufrufen senden , sodass diese Situation nicht auftritt.
Mecki
85

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.

Jonathan Feinberg
quelle
31
Das ist nicht die ganze Geschichte. recvkann nur für einen Socket verwendet werden und erzeugt einen Fehler, wenn Sie versuchen, ihn beispielsweise für einen Socket zu verwenden STDIN_FILENO.
Joey Adams
76
Dieser Thread ist jetzt der erste Treffer bei Google, Google liebt Stackoverflow
Eloff
12

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.

Bastien Léonard
quelle
7

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

ajb
quelle
2
+1. Siehe auch Winsock, der Lesen / Schreiben nicht unterstützt
Joseph Quinsey
6

Eine andere Sache unter Linux ist:

senderlaubt nicht, auf Nicht-Socket-FD zu arbeiten. So ist beispielsweise das Schreiben auf den USB-Port writeerforderlich.

Mert Mertce
quelle
3

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

entspannen
quelle
15
Vielleicht wird es als bequemer empfunden, sich nicht mit Flaggen befassen zu müssen.
Semaj
2

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 signalfür weitere Details.


Eine einfache Verwendung wäre die Verwendung eines Signals, um ein recvfromunbegrenztes 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);
}
Rick
quelle