readv、recv和recvmsg三个函数的区别


概述

readv、recv和recvmsg三个函数都是用于从文件或套接字接收数据的函数,但它们在功能和使用场景上存在一些区别。

  1. readv函数:
    readv函数主要用于从文件描述符读取数据到多个缓冲区中。它允许在一次系统调用中读取多个缓冲区的数据,这有助于减少多次系统调用的开销,提高读操作的性能。readv函数通常用于文件I/O操作。

  2. recv函数:
    recv函数用于从套接字接收数据。它提供了类似于read函数的功能,但还额外提供了一个flags参数,用于控制一些特殊的行为。例如,通过设置MSG_PEEK标志,recv可以预览数据而不将其从缓冲区中移除;通过设置MSG_WAITALL标志,recv可以阻止在接收的数据少于预期时返回。recv函数通常用于网络编程中的TCP和UDP套接字。

  3. recvmsg函数:
    recvmsg函数是另一个用于从套接字接收数据的函数,它提供了更为复杂和灵活的功能。与recv函数相比,recvmsg函数提供了更多的控制和选项,例如可以接收辅助数据(如带外数据)和更详细的控制消息标志。recvmsg函数还支持分散/聚集I/O,即可以将接收到的数据分散到多个缓冲区中,或将来自多个源的数据聚集到一个缓冲区中。recvmsg函数通常用于更高级的网络编程场景。

总结来说,readv函数主要用于文件I/O操作,从文件描述符读取数据到多个缓冲区中;recv函数用于从套接字接收数据,并提供了额外的Flags参数来控制特殊行为;recvmsg函数则提供了更为复杂和灵活的功能,支持分散/聚集I/O和更多的控制选项。根据具体的使用场景和需求,可以选择适合的函数来进行数据接收操作。


readv 示例 (文件I/O)

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buf1[10];
    char buf2[10];

    struct iovec iov[2];
    iov[0].iov_base = buf1;
    iov[0].iov_len = sizeof(buf1);
    iov[1].iov_base = buf2;
    iov[1].iov_len = sizeof(buf2);

    ssize_t bytes_read = readv(fd, iov, 2);
    if (bytes_read == -1) {
        perror("readv");
        return 1;
    }

    printf("Read %zd bytes:\n", bytes_read);
    printf("Buffer 1: %s\n", buf1);
    printf("Buffer 2: %s\n", buf2);

    close(fd);
    return 0;
}

recv 示例 (套接字通信)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(12345);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    char buffer[1024];
    ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (bytes_received == -1) {
        perror("recv");
        exit(EXIT_FAILURE);
    }

    buffer[bytes_received] = '\0'; // Null-terminate the received string
    printf("Received: %s\n", buffer);

    close(sockfd);
    return 0;
}

recvmsg 示例 (套接字通信)

这个示例展示了如何使用recvmsg来从一个套接字接收数据,并且处理可能存在的辅助数据(例如控制消息)。

请注意,这个示例假设您已经建立了一个TCP连接,并且服务器正在发送数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[1024];
    struct msghdr msg;
    struct iovec iov;
    char control_buffer[256];
    struct cmsghdr *cmsg;
    int flags;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址信息
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(12345);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 连接到服务器
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    // 初始化消息结构
    memset(&msg, 0, sizeof(msg));
    iov.iov_base = buffer;
    iov.iov_len = sizeof(buffer);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);

    // 接收数据
    ssize_t bytes_received = recvmsg(sockfd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg");
        exit(EXIT_FAILURE);
    }

    // 处理接收到的数据
    buffer[bytes_received] = '\0'; // Null-terminate the received string
    printf("Received: %s\n", buffer);

    // 检查是否有辅助数据
    if (msg.msg_flags & MSG_CTRUNC) {
        fprintf(stderr, "Control message was truncated\n");
        exit(EXIT_FAILURE);
    }

    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
            int *fds = (int *) CMSG_DATA(cmsg);
            int num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
            // 处理文件描述符
            for (int i = 0; i < num_fds; i++) {
                printf("Received file descriptor: %d\n", fds[i]);
            }
        }
    }

    // 关闭套接字
    close(sockfd);
    return 0;
}

在这个示例中,我们定义了一个msghdr结构体来配置接收操作,包括数据缓冲区、辅助数据缓冲区以及它们的大小。recvmsg函数接收这些参数,并返回接收到的数据量和可能存在的控制消息。

我们还检查了MSG_CTRUNC标志,它表示控制消息被截断。如果设置了此标志,我们需要处理错误。然后,我们遍历了所有接收到的控制消息,并检查是否有SCM_RIGHTS类型的消息,它通常用于传递文件描述符。

这只是一个基本的示例,实际应用中可能需要对错误进行更详细的处理,并且可能需要根据具体的应用协议来解析接收到的数据。此外,对于非阻塞套接字,您可能还需要检查EAGAINEINTR错误,并相应地重试接收操作。

posted @   guanyubo  阅读(680)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示