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ń?
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ń?
Odpowiedzi:
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ć SIGPIPE
lub wysyłać wiadomości pozapasmowe ...).
Funkcje read()
/ write()
to uniwersalne funkcje deskryptorów plików działające na wszystkich deskryptorach.
recv
i read
nie 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.
recv
? Powodem, dla którego recv
i send
gdzie wprowadzono go na początku, był fakt, że nie wszystkie koncepcje datagramów można zmapować na świat strumieni. read
i write
traktuj 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ą write
połączeń, więc taka sytuacja nie wystąpi.
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.
recv
mogą być wykorzystywane wyłącznie na gnieździe, i będzie produkować błąd, jeśli spróbujesz użyć go, powiedzmy STDIN_FILENO
.
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.
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()
.
Kolejną rzeczą na Linuksie jest:
send
nie pozwala na działanie na fd bez gniazda. Dlatego write
konieczne jest na przykład pisanie na porcie USB .
„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.
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 signal
więcej szczegółów.
Prostym zastosowaniem byłoby użycie sygnału, aby uniknąć recvfrom
blokowania 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);
}
#define write(...) send(##__VA_ARGS__, 0)
.