Jaka jest różnica między read () a recv () oraz pomiędzy send () i write ()?


198

Jaka jest różnica między read()i recv()oraz między send()i write()w programowaniu gniazda pod względem występów, prędkości i innych zachowań?


3
Pomyśl napisać jak realizowane tak: #define write(...) send(##__VA_ARGS__, 0).
ostrożny1,

Odpowiedzi:


128

Różnica polega na tym, że recv()/ send()działa tylko na deskryptorach gniazd i pozwala określić określone opcje dla rzeczywistej operacji. Funkcje te są nieco bardziej wyspecjalizowane (na przykład możesz ustawić flagę, aby ignorować SIGPIPElub wysyłać wiadomości pozapasmowe ...).

Funkcje read()/ write()to uniwersalne funkcje deskryptorów plików działające na wszystkich deskryptorach.


3
Jest to niepoprawne, istnieje jeszcze jedna różnica w przypadku datagramów o długości 0 - Jeśli oczekuje na datagram o zerowej długości, read (2) i recv () z argumentem flag o wartości zero zapewniają inne zachowanie. W tej sytuacji read (2) nie działa (datagram pozostaje w toku), a recv () zużywa oczekujący datagram.
Abhinav Gauniyal

2
@AbhinavGauniyal Jak to zapewni inne zachowanie ? Jeśli jest datagram 0-bajtowy, oba, recvi readnie dostarczy danych do dzwoniącego, ale również nie spowoduje błędu. Dla osoby dzwoniącej zachowanie jest takie samo. Osoba dzwoniąca może nawet nic nie wiedzieć o datagramach (może nie wiedzieć, że jest to gniazdo, a nie plik, może nie wiedzieć, że jest to gniazdo datagramu, a nie gniazdo strumieniowe). To, że datagram pozostaje w toku, jest domyślną wiedzą o tym, jak stosy IP działają w jądrach i nie są widoczne dla osoby dzwoniącej. Z perspektywy dzwoniącego nadal będą zapewniać równe zachowanie.
Mecki

2
@Mekki, która nie jest niejawną wiedzą dla wszystkich, weź mnie na przykład :)
Abhinav Gauniyal

1
@Mecki co oznacza nieblokujący pomyślny odczyt 0 bajtów? Czy datagram nadal pozostaje w toku? Dokładnie to i tylko to mnie martwi: zachowanie, że datagram może pozostać w toku nawet po pomyślnym odczytaniu. Nie jestem pewien, czy sytuacja może się zdarzyć, a więc chciałbym o tym pamiętać.
sehe

2
@sehe Jeśli martwisz się, dlaczego nie używasz recv? Powodem, dla którego recvi sendgdzie wprowadzono go na początku, był fakt, że nie wszystkie koncepcje datagramów można zmapować na świat strumieni. readi writetraktuj wszystko jak strumień danych, czy to potok, plik, urządzenie (np. port szeregowy) czy gniazdo. Jednak gniazdo jest prawdziwym strumieniem tylko wtedy, gdy używa TCP. Jeśli używa UDP, to bardziej przypomina urządzenie blokowe. Ale jeśli obie strony użyją go jak strumienia, będzie działał jak strumień i nie będziesz mógł nawet wysłać pustego pakietu UDP za pomocą writepołączeń, więc taka sytuacja nie wystąpi.
Mecki

85

Za pierwsze trafienie w Google

read () jest równoważny recv () z parametrem flags wynoszącym 0. Inne wartości parametru flags zmieniają zachowanie recv (). Podobnie write () jest równoważne send () z flagami == 0.


31
To nie jest cała historia. recvmogą być wykorzystywane wyłącznie na gnieździe, i będzie produkować błąd, jeśli spróbujesz użyć go, powiedzmy STDIN_FILENO.
Joey Adams,

76
Wątek ten jest teraz pierwszym hitem w Google, Google uwielbia stackoverflow
Eloff

12

read()i write()są bardziej ogólne, działają z dowolnym deskryptorem pliku. Nie będą one jednak działać w systemie Windows.

Możesz przekazać dodatkowe opcje do send()i recv(), więc w niektórych przypadkach może być konieczne ich użycie.


7

Niedawno zauważyłem, że kiedy korzystałem write()z gniazda w systemie Windows, to prawie działa (FD przeszedł do innego write()niż ten, do którego przeszedł send(); Miałem _open_osfhandle()już FD do przejścia write()). Jednak nie zadziałało, gdy próbowałem wysłać dane binarne zawierające znak 10. wcześniej write()wstawiono znak 13. Zmiana tego send()parametru na parametr flagi 0 rozwiązała ten problem. read()Mógłbym mieć odwrotny problem, jeśli 13-10 są kolejne w danych binarnych, ale nie przetestowałem tego. Ale to wydaje się być kolejną możliwą różnicą między send()i write().


2
+1. Zobacz także winsock
nieobsługujący

6

Kolejną rzeczą na Linuksie jest:

sendnie pozwala na działanie na fd bez gniazda. Dlatego writekonieczne jest na przykład pisanie na porcie USB .


3

„Wydajność i szybkość”? Czy to nie są takie ... synonimy?

W każdym razie recv()połączenie przyjmuje flagi, które read()tego nie robią, co czyni go bardziej wydajnym lub przynajmniej wygodniejszym. To jedna różnica. Nie sądzę, że istnieje znacząca różnica w wydajności, ale nie przetestowałem tego.


15
Być może brak konieczności zajmowania się flagami może być postrzegany jako wygodniejszy.
semaj

2

W systemie Linux zauważam również, że:

Przerwanie wywołań systemowych i funkcji bibliotecznych przez procedury obsługi sygnałów
Jeśli wywoływana jest procedura obsługi sygnału, gdy wywołanie systemowe lub wywołanie funkcji biblioteki jest zablokowane, wówczas:

  • połączenie jest automatycznie wznawiane po powrocie procedury obsługi sygnału; lub

  • połączenie kończy się niepowodzeniem z błędem EINTR.

... Szczegóły różnią się w zależności od systemu UNIX; poniżej szczegóły dla Linuksa.

Jeśli zablokowane wywołanie jednego z poniższych interfejsów zostanie przerwane przez procedurę obsługi sygnału, wówczas połączenie zostanie automatycznie wznowione po powrocie procedury obsługi sygnału, jeśli użyto flagi SA_RESTART; w przeciwnym razie połączenie nie powiedzie się z błędem EINTR:

  • Wywołania read (2), readv (2), write (2), writev (2) i ioctl (2) na „wolnych” urządzeniach.

.....

Następujące interfejsy nigdy nie są restartowane po przerwie przez procedurę obsługi sygnału, niezależnie od zastosowania SA_RESTART; zawsze kończą się błędem EINTR, gdy są przerywane przez moduł obsługi sygnału:

  • Interfejsy gniazda „Input”, gdy limit czasu (SO_RCVTIMEO) został ustawiony w gnieździe za pomocą setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (również z wartością inną niż NULL argument limitu czasu) i recvmsg (2).

  • Interfejsy gniazda wyjściowego, gdy limit czasu (SO_RCVTIMEO) został ustawiony w gnieździe za pomocą setsockopt (2): connect (2), send (2), sendto (2) i sendmsg (2).

Sprawdź man 7 signalwięcej szczegółów.


Prostym zastosowaniem byłoby użycie sygnału, aby uniknąć recvfromblokowania na czas nieokreślony.

Przykład z 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);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.