В чем разница между read () и recv () и между send () и write ()?

198

В чем разница между read()и recv(), и между send()и write()в программировании сокетов с точки зрения производительности, скорости и других поведений?

Саджад Бахмани
источник
3
Подумайте о записи , как это реализовано , как: #define write(...) send(##__VA_ARGS__, 0).
внимательнее1

Ответы:

128

Разница в том, что recv()/ send()работают только с дескрипторами сокетов и позволяют указывать определенные параметры для реальной операции. Эти функции немного более специализированы (например, вы можете установить флаг, чтобы игнорировать SIGPIPEили отправлять внеполосные сообщения ...).

Функции read()/ write()- это универсальные функции файловых дескрипторов, работающие со всеми дескрипторами.

Гонсало
источник
3
Это неверно, есть еще одно отличие в случае дейтаграмм 0 длины - если датаграмма нулевой длины находится в ожидании, read (2) и recv () с аргументом flags, равным нулю, обеспечивают другое поведение. В этом случае read (2) не имеет никакого эффекта (дейтаграмма остается ожидающей), тогда как recv () использует ожидающую дейтаграмму.
Абхинав Гауниал
2
@AbhinavGauniyal Как это обеспечит другое поведение ? Если есть 0-байтовая дейтаграмма, и то, recvи другое readне доставит данные вызывающей стороне, но также не даст ошибки. Для звонящего поведение такое же. Вызывающий может даже не знать ничего о дейтаграммах (он может не знать, что это сокет, а не файл, он может не знать, что это сокет дейтаграммы, а не сокет потока). То, что датаграмма остается в ожидании, является неявным знанием того, как стеки IP работают в ядрах и невидимы для вызывающей стороны. С точки зрения вызывающих, они все равно будут обеспечивать равное поведение.
Меки
2
@Mecki, это не скрытое знание для всех, возьмите меня к примеру :)
Абхинав Гауниял
1
@Mecki, что означает неблокирующее успешное чтение 0 байтов? Датаграмма все еще остается в ожидании? Именно это и только это беспокоит меня: поведение, которое дейтаграмма может ожидать, даже если успешно прочитано. Я не уверен, что ситуация может возникнуть, поэтому я хотел бы иметь это в виду.
Се
2
@sehe Если вы беспокоитесь, почему бы вам не использовать recv? Причиной, по которой recvи sendгде в первую очередь была введена информация, был тот факт, что не все концепции дейтаграмм можно сопоставить с миром потоков. readи writeобрабатывать все как поток данных, будь то канал, файл, устройство (например, последовательный порт) или сокет. Тем не менее, сокет является только реальным потоком, если он использует TCP. Если он использует UDP, он больше похож на блочное устройство. Но если обе стороны используют его как поток, он будет работать как поток, и вы даже не сможете отправить пустой пакет UDP с помощью writeвызовов, поэтому такая ситуация не возникнет.
Меки
85

За первое попадание в Google

read () эквивалентно recv () с параметром flags равным 0. Другие значения параметра flags изменяют поведение recv (). Аналогично, write () эквивалентно send () с флагами == 0.

Джонатан Фейнберг
источник
31
Это не вся история. recvможет быть использован только на сокете, и выдаст ошибку , если вы пытаетесь использовать его, скажем, STDIN_FILENO.
Джои Адамс
77
Эта тема стала первым хитом в Google, Google любит
переполнение стека
12

read()и write()являются более общими, они работают с любым файловым дескриптором. Тем не менее, они не будут работать на Windows.

Вы можете передать дополнительные опции send()и recv(), поэтому вам, возможно, придется использовать их в некоторых случаях.

Бастьен Леонар
источник
7

Я только недавно заметил, что когда я использовал write()сокет в Windows, он почти работает (передаваемый FD write()не совпадает с передаваемым send(); я использовал _open_osfhandle()для передачи FD write()). Тем не менее, это не сработало, когда я попытался отправить двоичные данные, которые включали символ 10. write()где-то вставил символ 13 до этого. Изменение этого send()параметра с параметром flags, равным 0, решило эту проблему. read()может иметь обратную проблему, если 13-10 последовательных в двоичных данных, но я не проверял это. Но это, кажется, другая возможная разница между send()и write().

ajb
источник
2
+1. См. Также winsock, не поддерживающий чтение / запись
Джозеф Куинси
6

Еще одна вещь на Linux:

sendне позволяет работать на не-сокете fd. Таким образом, например, чтобы написать на USB-порт, writeнеобходимо.

Мерт Мертс
источник
2

«Производительность и скорость»? Разве это не ... синонимы здесь?

В любом случае, recv()вызов принимает флаги, которые read()этого не делают, что делает его более мощным или, по крайней мере, более удобным. Это одно из отличий. Я не думаю, что есть существенная разница в производительности, но я не проверял это.

размотать
источник
15
Возможно, отсутствие необходимости иметь дело с флагами может быть воспринято как более удобное.
Семай
2

В Linux я также заметил, что:

Прерывание системных вызовов и функций библиотеки обработчиками сигнала
Если обработчик сигнала вызывается, когда системный вызов или вызов функции библиотеки заблокированы, то либо:

  • вызов автоматически перезапускается после возврата обработчика сигнала; или

  • вызов не выполняется с ошибкой EINTR.

... Детали различаются в разных системах UNIX; ниже, подробности для Linux.

Если заблокированный вызов одного из следующих интерфейсов прерывается обработчиком сигнала, то вызов автоматически перезапускается после того, как обработчик сигнала возвращается, если использовался флаг SA_RESTART; в противном случае вызов завершается с ошибкой EINTR:

  • вызовы read (2), readv (2), write (2), writev (2) и ioctl (2) на «медленных» устройствах.

.....

Следующие интерфейсы никогда не перезапускаются после прерывания обработчиком сигнала, независимо от использования SA_RESTART; они всегда терпят неудачу с ошибкой EINTR при прерывании обработчиком сигнала:

  • «Входные» сокетные интерфейсы, когда тайм-аут (SO_RCVTIMEO) был установлен на сокете с помощью setsockopt (2): accept (2), recv (2), recvfrom (2), recvmmsg (2) (также с ненулевым значением) аргумент тайм-аута) и recvmsg (2).

  • «Выходные» интерфейсы сокетов, когда для сокета было установлено время ожидания (SO_RCVTIMEO) с использованием setsockopt (2): connect (2), send (2), sendto (2) и sendmsg (2).

Проверьте man 7 signalдля более подробной информации.


Простое использование будет использовать сигнал, чтобы избежать recvfromблокировки на неопределенный срок.

Пример из 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);
}
стог
источник