read()和recv()的区别,以及send()和write()之间的区别是什么?

259

read()recv()send()write() 在套接字编程中有何不同,涉及性能、速度和其他行为方面的差异是什么?


5
将write函数的实现想象成这样:#define write(...) send(##__VA_ARGS__, 0)。请注意,此处省略了上下文信息和其他细节。 - carefulnow1
9个回答

169

区别在于recv()/send()仅适用于套接字描述符,并允许您为实际操作指定某些选项。这些函数稍微更加专业化(例如,您可以设置标志以忽略SIGPIPE,或发送带外消息...)。

read()/write()函数是处理所有文件描述符的通用函数。


8
这是不正确的,对于长度为0的数据报有另一个区别 - 如果有一个长度为零的数据报挂起,使用标志参数为零的read(2)和recv()提供不同的行为。在这种情况下,read(2)没有效果(数据报仍然挂起),而recv()会消耗挂起的数据报。 - Abhinav Gauniyal
2
@AbhinavGauniyal 如果有0字节数据报,那么它会提供“不同的行为”吗?如果有0字节数据报,recvread都不会向调用者传递任何数据,也不会出现错误。对于调用者来说,行为是相同的。调用者甚至可能不知道数据报的任何信息(它可能不知道这是一个套接字而不是文件,它可能不知道这是一个数据报套接字而不是流套接字)。数据报保持挂起是内核中IP堆栈工作的隐含知识,对调用者不可见。从调用者的角度来看,它们仍然提供相同的行为。 - Mecki
2
@Mecki,这对每个人来说都不是隐含的知识,以我为例 :) - Abhinav Gauniyal
2
@Mecki 一个非阻塞的成功读取0字节表示什么?数据报是否仍然保持挂起状态?这正是我所担心的,也只有这个:即使成功读取,数据报仍然可能保持挂起状态。我不确定这种情况是否会出现,这就是为什么我想记住它的原因。 - sehe
2
@sehe 如果你担心的话,为什么不使用 recvrecvsend 被引入的原因是并非所有数据报概念都能映射到流的世界中。readwrite 将所有东西都视为数据流,无论是管道、文件、设备(例如串行端口)还是套接字。但是,如果套接字使用 UDP,则它更像是块设备,只有在使用 TCP 时,套接字才是真正的流。但是,如果双方都像流一样使用它,它将像流一样工作,甚至不能使用 write 调用发送空的 UDP 数据包,因此不会出现这种情况。 - Mecki
显示剩余4条评论

105

根据谷歌搜索的第一个结果

read() 相当于使用 flags 参数为 0 的 recv()。flags 参数的其他值会改变 recv() 的行为。同样地,write() 相当于使用 flags 参数为 0 的 send()。


36
这不是整个故事。recv 只能用于套接字,如果你尝试在 STDIN_FILENO 上使用它,它将产生一个错误。请注意,本文只做翻译,不得添加解释或其他内容。 - Joey Adams
100
此讨论串现在是谷歌搜索结果的第一条,谷歌喜欢stackoverflow。 - Eloff
1
@JoeyAdams:在大多数系统上,recv 在非套接字(例如 STDIN_FILENO)上也可以正常工作。只有少数系统会失败(例如 Windows)。 - Chris Dodd

19

read()write()更加通用,它们适用于任何文件描述符。但它们在Windows上不起作用。

您可以向send()recv()传递附加选项,因此在某些情况下可能需要使用它们。


8
我最近注意到,当我在Windows上使用write()函数时,它几乎可以工作(传递给write()的FD与传递给send()的FD不同;我使用了_open_osfhandle()来获取用于传递给write()的FD)。但是,当我尝试发送包含字符10的二进制数据时,它并没有起作用。在某个位置,write()会在此之前插入字符13。将其更改为带有flags参数0的send()函数解决了该问题。如果二进制数据中的13-10连续,则read()可能会出现相反的问题,但我还没有测试过。但这似乎是send()write()之间可能存在的另一个差异。

4
参见winsock不支持read/write - Joseph Quinsey
Windows也不会接收少于3个字节数据的UDP数据包,所以,在Windows上存在问题。 :) - JonS

7
在Linux上还有一件事是: send 不允许在非套接字文件描述符(fd)上操作。因此,例如,在 USB 端口写入数据需要使用write

5
在Linux上,我还注意到以下内容:
通过信号处理程序打断系统调用和库函数调用
如果在阻塞的系统调用或库函数调用时调用信号处理程序,则有以下两种情况:
- 信号处理程序返回后,调用将自动重新启动。 - 如果未使用SA_RESTART标志,则调用失败并显示EINTR错误。
UNIX系统的详细信息因版本而异。以下是适用于Linux的详细信息。
如果以下任何一个接口的受阻调用被信号处理程序中断,则如果使用了SA_RESTART标志,则信号处理程序返回后,调用将自动重新启动;否则,调用失败并显示EINTR错误:
- 读取(2),readv(2),写入(2),writev(2)和ioctl(2)调用"slow"设备。 ……
以下接口一旦被信号处理程序中断,无论是否使用了SA_RESTART,都不会重新启动;当由信号处理程序中断时,它们总是失败并显示EINTR错误:
- 在套接字上设置了超时(SO_RCVTIMEO)的“输入”套接字接口:accept(2),recv(2),recvfrom(2),recvmmsg(2)(也带有非NULL超时参数)和recvmsg(2)。 - 在套接字上设置了超时(SO_RCVTIMEO)的“输出”套接字接口: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);
}

3

"性能和速度"?这不是同义词吗?

无论如何,recv()调用需要的标志read()没有,这使它更强大或至少更方便。这是一个区别。我认为它们在性能上没有显著差异,但我没有测试过。


16
不必处理旗帜可能被视为更方便。 - semaj

1

recv()和read()之间唯一的区别在于存在标志。如果使用零标志参数,则recv()通常等效于read()


0

你可以使用write()和read()代替send()和recv(),但是send()和recv()提供了更大的数据传输控制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接