read() 和 recv() 以及 send() 和 write() 之间有啥区别?

Posted

技术标签:

【中文标题】read() 和 recv() 以及 send() 和 write() 之间有啥区别?【英文标题】:What is the Difference Between read() and recv() , and Between send() and write()?read() 和 recv() 以及 send() 和 write() 之间有什么区别? 【发布时间】:2010-12-19 22:20:33 【问题描述】:

在socket编程中read()recv()以及send()write()在性能、速度和其他行为方面有什么区别?

【问题讨论】:

想像这样实现的写:#define write(...) send(##__VA_ARGS__, 0). 【参考方案1】:

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

函数read()/write()通用 文件描述符函数,作用于所有描述符。

【讨论】:

这是不正确的,在长度为 0 的数据报的情况下还有另一个区别 - 如果零长度的数据报处于挂起状态,带有零标志参数的 read(2) 和 recv() 提供不同的行为.在这种情况下,read(2) 无效(数据报保持挂起状态),而 recv() 消耗挂起的数据报。 @AbhinavGauniyal 这将如何提供不同的行为?如果有一个 0 字节的数据报,recvread 都不会向调用者传递数据,但也不会出错。对于调用者,行为是相同的。调用者甚至可能对数据报一无所知(它可能不知道这是一个套接字而不是文件,它可能不知道这是一个数据报套接字而不是流套接字)。数据报保持未决是关于 IP 堆栈如何在内核中工作的隐含知识,并且对调用者不可见。从调用者的角度来看,它们仍然会提供相同的行为。 @Mecki 这不是每个人的隐含知识,以我为例:) @Mecki 0 字节的非阻塞成功读取表示什么?数据报是否仍处于待处理状态?正是这一点,而且只有这一点让我担心:即使成功读取数据报也可以保持挂起的行为。我不确定是否会出现这种情况,因此我想牢记这一点。 @sehe 如果您担心,为什么不使用recv?之所以首先引入recvsend,是因为并非所有数据报概念都可以映射到流的世界。 readwrite 将一切都视为数据流,无论是管道、文件、设备(例如串行端口)还是套接字。然而,只有使用 TCP 的套接字才是真正的流。如果它使用 UDP,它更像是一个块设备。但是如果双方都像流一样使用它,它就会像流一样工作,你甚至不能使用write调用发送一个空的UDP数据包,所以不会出现这种情况。【参考方案2】:

每the first hit on Google

read() 等价于 flags 参数为 0 的 recv()。 flags 参数的其他值会改变 recv() 的行为。同样,write() 等价于 flags == 0 的 send()。

【讨论】:

这不是全部。 recv 只能在套接字上使用,如果您尝试在 STDIN_FILENO 上使用它会产生错误。 这个帖子现在是 Google 上的第一个热门话题,Google 喜欢 ***【参考方案3】:

read()write() 更通用,它们适用于任何文件描述符。 但是,它们无法在 Windows 上运行。

您可以将其他选项传递给send()recv(),因此在某些情况下您可能必须使用它们。

【讨论】:

【参考方案4】:

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

【讨论】:

+1。另见winsock not supporting read/write Windows 也不会收到小于 3 字节数据的 UDP 数据包,因此,Windows 存在问题。 :)【参考方案5】:

linux 上的另一件事是:

send 不允许在非套接字 fd 上操作。因此,例如在 USB 端口上写入,write 是必需的。

【讨论】:

【参考方案6】:

在 Linux 上,我还注意到:

信号处理程序中断系统调用和库函数 如果在阻止系统调用或库函数调用时调用信号处理程序,则:

信号处理程序返回后自动重新开始调用;或

调用失败并出现错误 EINTR。

...细节因 UNIX 系统而异;下面是 Linux 的详细信息。

如果对以下接口之一的阻塞调用被中断 由信号处理程序,然后调用会在之后自动重新启动 如果使用了 SA_RESTART 标志,则信号处理程序返回;否则调用将失败并返回错误 EINTR:

read(2)、readv(2)、write(2)、writev(2) 和 ioctl(2) 在“慢”设备上调用。

.....

以下接口在被信号处理程序中断后永远不会重新启动,无论是否使用 SA_RESTART;他们 当被信号处理程序中断时,总是失败并返回错误 EINTR:

“输入”套接字接口,当使用 setsockopt(2) 在套接字上设置超时 (SO_RCVTIMEO) 时:accept(2)、recv(2)、 recvfrom(2)、recvmmsg(2)(也带有非 NULL 超时参数)和 recvmsg(2)。

“输出”套接字接口,当使用 setsockopt(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);

【讨论】:

【参考方案7】:

“性能和速度”?这些不是...的同义词吗?

无论如何,recv() 调用带有 read() 没有的标志,这使它更强大,或者至少更方便。这是一个区别。我认为没有显着的性能差异,但尚未对其进行测试。

【讨论】:

也许不必处理标志可能会被认为更方便。

以上是关于read() 和 recv() 以及 send() 和 write() 之间有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

linux中read,write和recv,send的区别

read VS recv?关于 send 和 recv 的第四个参数 flag

read VS recv?关于 send 和 recv 的第四个参数 flag

套接字I/O函数write/read writev/readv send/recv sendto/recvfrom sendmsg/recvmsg

ECONNRESET和WSAECONNRESET怎么产生的以及如何避免

高级套接字函数