recvmsg 和 sendmsg 函数详解 以及如何进行多进程之间文件描述符的发送与接收

recvmsg 和 sendmsg 函数

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

这两个函数把大部分参数封装到一个 msghdr 结构体中:

struct msghdr {
    void          *msg_name;        // protocol address
    socklen_t      msg_namelen;     // size of protocol address
    struct iovec  *msg_iov;         // scatter/gather array
    int            msg_iovlen;      // elements in msg_iov
    size_t        msg_iovlen;     // ancillary data (cmsghdr struct)
    socklen_t      msg_controllen;  // length of ancillary data
    int            msg_flags;       // flags returned by recvmsg()
};


/*有效数据*/
struct iovec {
    void *io_base; /* buffer空间的基地址 */
    size_t iov_len; /* 该buffer空间的长度 */
};

/*控制数据*/
struct cmsghdr {
    socklen_t cmsg_len; /* 包含该头部的数据长度 */
    int cmsg_level; /* 具体的协议标识 */
    int cmsg_type; /* 协议中的类型 */
};
/*其中的cmsg_level主要包含IPPROTO_IP(ipv), IPPROTO_IPV6(ipv6), SOL_SOCKET(unix domain).
其中的cmsg_type是根据上述的类型有分别有不同的内容,比如SOL_SOCKET中主要包含:SCM_RIGHTS(发送接收描述字), SCM_CREDS(发送接收用户凭证)*/
  • msg_name 和 msg_namelen 这两个成员用于套接字未连接的场合(如未连接 UDP 套接字)。它们类似 recvfrom 和 sendto 的第五个和第六个参数:
    • msg_name 指向一个套接字地址结构,调用者在其中存放接收者(对于 sendmsg 调用)或发送者(对于recvmsg调用)的协议地址。如果无需指明协议地址(如对于 TCP 套接字或已连接 UDP 套接字),msg_name 应置为空指针。
    • msg_namelen 对于 sendmsg 是一个值参数,对于 recvmsg 却是一个值-结果参数。
  • msg_iov 和 msg_iovlen 这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似 readv 或 writev 的第二个和第三个参数。
  • msg_control 和 msg_controllen 这两个成员指定可选的辅助数据的位置和大小。msg_controllen 对于 recvmsg 是一个值-结果参数。

对于 recvmsg 和 sendmsg,必须区别它们的两个标志变量:

  • 一个是传递值的 flags 参数;
  • 另一个是所传递 msghdr 结构的 msg_flags 成员,它传递的是引用,因为传递给函数的是该结构的地址。
  • 只有 recvmsg 使用 msg_flags 成员。recvmsg 被调用时,flags 参数被复制到 msg_flags 成员,并由内核使用其值驱动接收处理过程。内核还依据 recvmsg 的结果更新 msg_flags 成员的值。
  • sendmsg 则忽略 msg_flags 成员,因为它直接使用 flags 参数驱动发送处理过程。这一点意味着如果想在某个 sendmsg 调用中设置 MSG_DONTWAIT 标志,那就把 flags 参数设置为该值,把 msg_flags 成员设置为该值不起作用。

recvmsg 返回的 7 个标志如下:

  • MSG_BCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据包作为链路层广播收取或者其目的 IP 地址是一个广播地址。与 IP_RECVD-STADDR 套接字选项相比,本标志是用于判定一个 UPD 数据包是否发往某个广播地址的更好方法。
  • MSG_MCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据报作为链路层多播收取。
  • MSG_TRUNC:本标志的返回条件是本数据报被截断,也就是说,内核预备返回的数据超过进程事先分配的空间(所有 iov_len 成员之和)。
  • MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断,也就是说,内核预备返回的辅助数据超过进程事先分配的空间(msg_controllen)。
  • MSG_EOR:本标志的返回条件是返回数据结束一个逻辑记录。TCP 不使用本标志,因为它是一个字节流协议。
  • MSG_OOB:本标志绝不为 TCP 带外数据返回。它用于其他协议族(如 OSI 协议族)。
  • MSG_NOTIFICATION:本标志由 SCTP 接收者返回,指示读入的消息是一个事先通知,而不是数据消息。

下图展示了一个 msghdr 结构以及它指向的各种信息。图中假设进程即将对一个 UDP 套接字调用 recvmsg:
img
图中给协议地址分配了 16 个字节,给辅助数据分配了 20 个字节。为缓冲数据初始化了一个由 3 个 iovec 结构构成的数组:第一个指定一个 100 字节的缓冲区,第二个指定一个 60 字节的缓冲区,第三个指定一个 80 字节的缓冲区。假设已为这个套接字设置了 IP_RECVDSTADDR 套接字选项,以接收所读取 UDP 数据包的目的 IP 地址。

假设从 198.38.100:2000 到达一个 170 字节的 UDP 数据报,它的目的地是我们的 UDP 套接字,目的 IP 地址为 206.168.112.96.下图展示了 recvmsg 返回时 msghdr 结构中的所有信息。
img
图中被 recvmsg 修改过的字段标上了阴影。从第一幅图到第二幅图的变动包括以下几点:

  • 由 msg_name 成员指向的缓冲区被填以一个网际网套接字地址结构,其中有所收到数据报的源 IP 地址和源 UPD 端口号。
  • msg_namelen 成员(一个值-结果参数)被更新为存放在 msg_name 所指缓冲区中的数据量。本成员并无变化,因为 recvmsg 调用前和返回后其值均为 16.
  • 所收取数据报的前 100 个字节数据存放在第一个缓冲区,中 60 字节数据存放在第二个缓冲区,后 10 字节数据存放在第三个缓冲区。最后那个缓冲区的后 70 字节没有改动。recvmsg 函数的返回值(即 170)就是该数据报的大小。
  • 由 msg_control 成员指向的缓冲区被填以一个 cmsghdr 结构。该 cmsghdr 结构中,cmsg_len 成员值为 16,cmsg_level 成员值为 IPPROTO_IP,cmsg_type 成员值为 IP_RECVDSTADDR,随后 4 个字节存放所收到 UDP 数据报的目的 IP 地址。这个 20 字节缓冲区的后 4 个字节没有改动。
  • msg_controllen 成员被更新为所存放辅助数据的实际数据量。本成员也是一个值-结果参数,recvmsg 返回时其结果为 16。
  • msg_flags 成员同样被 recvmsg 更新,不过没有标志返回给进程。

如何进行文件描述符的传递?

#include "unp.h"

int main(int argc, char *argv[])
{
    int clifd, listenfd;
    struct sockaddr_un servaddr, cliaddr;
    int ret;
    socklen_t clilen;
    struct msghdr msg;
    struct iovec iov[1];
    char buf[100];
    char *testmsg = "test msg.\n";
    
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr *pcmsg;
    int recvfd;
    
    listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (listenfd < 0) {
        printf("socket failed.\n");
        return -1;
    }
    
    unlink(UNIXSTR_PATH);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);
    
    ret = bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
    if (ret < 0) {
        printf("bind failed. errno = %d.\n", errno);
        close (listenfd);
        return -1;
    }
    
    listen(listenfd, 5);
    
    while (1) {
        clilen = sizeof(cliaddr);
        clifd = accept(listenfd, (SA *)&cliaddr, &clilen);
        if (clifd < 0) {
            printf("accept failed.\n");
            continue;
        }
        
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
        iov[0].iov_base = buf;
        iov[0].iov_len = 100;
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;
        msg.msg_control = control_un.control;
        msg.msg_controllen = sizeof(control_un.control);
        
        ret = recvmsg(clifd, &msg, 0);
        if (ret <= 0) {
            return ret;
        }
        
        if ((pcmsg = CMSG_FIRSTHDR(&msg)) != NULL && (pcmsg->cmsg_len == CMSG_LEN(sizeof(int)))) {
            if (pcmsg->cmsg_level != SOL_SOCKET) {
                printf("cmsg_leval is not SOL_SOCKET\n");
                continue;
            }
            
            if (pcmsg->cmsg_type != SCM_RIGHTS) {
                printf("cmsg_type is not SCM_RIGHTS");
                continue;
            }
            
            recvfd = *((int *) CMSG_DATA(pcmsg));
            printf("recv fd = %d\n", recvfd);
            
            write(recvfd, testmsg, strlen(testmsg) + 1);
        }
    }
    
    return 0;
}
#include "unp.h"
#define OPEN_FILE  "test"
int main(int argc, char *argv[])
{
    int clifd;
    struct sockaddr_un servaddr;
    int ret;
    struct msghdr msg;
    struct iovec iov[1];
    char buf[100];
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr *pcmsg;
    int fd;
    
    clifd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (clifd < 0) {
        printf("socket failed.\n");
        return -1;
    }
    
    fd = open(OPEN_FILE, O_CREAT| O_RDWR, 0777);
    if (fd < 0) {
        printf("open test failed.\n");
        return -1;
    }
    
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);
    
    ret = connect(clifd,(SA *)&servaddr, sizeof(servaddr));
    if (ret < 0) {
        printf("connect failed.\n");
        return 0;
    }
    
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = buf;
    iov[0].iov_len = 100;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    
    pcmsg = CMSG_FIRSTHDR(&msg);
    pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
    pcmsg->cmsg_level = SOL_SOCKET;
    pcmsg->cmsg_type = SCM_RIGHTS;
    *((int *)CMSG_DATA(pcmsg)) = fd;
    
    ret = sendmsg(clifd, &msg, 0);
    printf("ret = %d.\n", ret);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值