socketpair + sendmsg/recvmsg进行父子进程间文件描述符传递

函数接口

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

int sockpair(int domain, int type, int protocol, int sv[2]);

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


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

socketpair创建的描述符任意一端既可以读也可以写,但只局限于父子进程中,是全双工的管道

  • domin 表示协议族,必须为AF_LOCAL或者AF_UNIX
  • type    表示类型,SOCK_STREAM或SOCK_DGRAM为前者时,创建的是管道
  • protocol  必须是0
  • sv 用于保存创建的套接字对

sendmsg的使用——重点在于结构体msghdr

struct msghdr{

    //1
    void         *msg_name;       //optional address
    socklen_t     msg_namelen;    //size of address
    //2
    struct iovec *msg_iov;        //scatter/gather array
    size_t        msg_iovlen;     //elements in msg_iov
    //3
    void         *msg_control;    //ancillary data, see be low
    size_t        msg_controllen; //ancillary data buffer len
    //4
    int           msg_flags;      //flags(unused)
  • 第一组参数,套接口地址成员(未使用)
  • 第二组参数,IO向量缓冲区(聚集写,填充)
  • 第三组参数,附属数据缓冲区(填充)
  • 第四组参数,接收信息标记位(未使用)

msg_iovlen 指数组元素个数

msg_controllen指控制信息所占用的总字节数

控制信息使用cmsghdr结构体来填充

结构体struct iovec的使用

  • writev 聚集写,是一个系统调用
  • readv 分散读,是一个系统调用
#include<sys/uio.h>

struct iovec{
    void* iov_base; //指向的是一个缓冲区的首地址
    size_t iov_len; //指向的是缓冲区中数据的长度
};

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

//其中第二个参数指针iov指向的是一个数组,数组中元素为结构体iovec

聚集写示例

#include <func.h>
#include <sys/uio.h>

int main(int argc, char *argv[]){
    char buf1[] = "hello,";
    char buf2[] = "world";

    int fd;
    fd = open("file.txt", O_RDWR | O_CREAT , 0666);
    if (fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }

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

    int ret = writev(fd, vecs, 2);
    if (ret < 0)
    {
        perror("writev");
    }
    
    printf("ret:%d\n",ret);

    close(fd);
    return 0;
}

可以把不同地方的缓冲区聚集起来通过writev一次性全部发送出去,此时只执行一次系统调用,可以提高程序的执行效率。

分散读示例

#include <func.h>
#include <sys/uio.h>

int main(){
    char buf1[6] = {0};
    char buf2[6] = {0};

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

    int fd = open("file.txt", O_RDWR);
    if (fd < 0)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    
    int ret = readv(fd, vecs, 2);
    if (ret < 0)
    {
        perror("readv");
        exit(EXIT_FAILURE);
    }
    printf("read ret : %d\n",ret);

    printf("buf1: %s ,buf2: %s\n",buf1, buf2);

    close(fd);

    return 0;
}

当第一个缓冲区没有填满时,会一直写数据,直到第二个缓冲区填满之后,再填第二个缓冲区。

附属数据的填充——结构体struct cmsghdr

struct cmsghdr{
    size_t cmsg_len;  //Data byte count,including header(type is socklen_t in POSIX)
    int cmsg_level;   // Originating protocol
    int cmsg_type;    // Protocol-specific type
    //follower by 
    //unsigned char cmsg_data[];
}

其成员描述为

成员描述
cmsg_len附属数据的字节技术,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。
cmsg_level这个值表明了原始的协议级别(例如,SOL_SOCKET)。
cmsg_type这个值表明了控制信息类型(例如,SCM_RIGHT)。
cmsg_data这个成员并不实际存在,只是用来指明实际的额外附属数据所在的位置。

cmsg_type类型:

  • SCM_RIGHT 附属数据对象是一个文件描述符
  • SCM_CREDENTIALS 附属数据对象是一个包含证书信息的结构

由于附属数据结构的复杂性,Linux系统提供了一系列的C宏来简化我们的工作。另外这些宏可以在不同的UNIX平台之间进行移植,并且采取了一些措施来防止将来的改变。

size_t CMSG_LEN(size_t length);
//获取整个结构体的长度
unsigned char* CMSG_DATA(struct cmsghdr* cmsg);
//获取data所在的首地址

CMSG_LEN是通过length再加上结构体前三项的长度,从而得出总长度

结构体struct msghdr的发送和接收实现

父进程发送

int sendFd(int pipefd, int fd){
    //1. 准备一个iovec结构体
    struct iovec iov;
    memset(&iov, 0, sizeof(iov));
    char buf[10] = {0};
    strcpy(buf, "hello");
    iov.iov_base = buf;
    iov.iov_len = strlen(buf);
    //2. 准备一个cmsghsr结构体
    size_t len = CMSG_LEN(sizeof(fd));
    struct cmsghdr* cmsg = calloc(1, fd);
    cmsg->cmsg_len = len;
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    int *pfd = (int*)CMSG_DATA(cmsg);
    *pfd = fd;
    //3.组建一个msghdr结构体
    struct msghdr msg;
    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;//必填
    msg.msg_iovlen = 1;
    msg.msg_control = cmsg;
    msg.msg_controllen = len;
    //4.发送
    int ret = sendmsg(pipefd, &msg, 0);
    if (ret == -1)
    {
        perror("sendmsg");
        exit(EXIT_FAILURE);
    }
    
    return 0;
}

子进程接收

#include<func.h>

int recvFd(int pipeFd, int *fd){
    //1.准备一个iovec结构体
    struct iovec iov;
    memset(&iov, 0, sizeof(iov));
    char buf[10];
    iov.iov_base = buf;
    iov.iov_len = sizeof(buf);

    //2.准备一个cmsghdr结构体
    size_t len = CMSG_LEN(sizeof(fd));
    struct cmsghdr* pcmsg = calloc(1, len);
    pcmsg->cmsg_len = len;
    pcmsg->cmsg_level = SOL_SOCKET;
    pcmsg->cmsg_type = SCM_RIGHTS;

    //3.组建一个msghdr结构体
    struct msghdr msg;
    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = pcmsg;
    msg.msg_controllen = len;

    //4.进行接收
    int ret = recvmsg(*fd, &msg, 0);
    if (ret < 0)
    {
        perror("recvmsg");
        exit(EXIT_FAILURE);
    }
    printf("recv %d byte.\n", ret);

    int *p = (int*)CMSG_DATA(pcmsg);
    *fd = *p;
    free(pcmsg);

    return 0;
    
}

sendmsg and recvmsg

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

int recvmsg(int fd, const struct msghdr* msg, int flags);

recvmsg中flags:指定函数行为的标志,比如是否阻塞等,常见的标志有:

  • MSG_WAITALL:等待直到接收到指定大小的数据。
  • MSG_DONTWAIT:设置为非阻塞模式。
  • MSG_PEEK:查看消息但不从队列中移除

但是两个函数中通常flags为0。

socketpair 使用匿名管道通信

通过一次sendmsg,一次recvmsg来完成进程间的dup机制

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Unix/Linux 系统下,我们可以使用 `sendmsg` 和 `recvmsg` 函数来传递文件句柄。下面是一个简单的示例代码: #### 发送进程代码 ```c #include <sys/types.h> #include <sys/socket.h> #include <sys/uio.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> #define CONTROLLEN CMSG_LEN(sizeof(int)) int send_fd(int fd, int fd_to_send) { struct iovec iov[1]; struct msghdr msg; char buf[2]; // 发送任意数据 int ret; union { struct cmsghdr cm; char control[CONTROLLEN]; } control_un; struct cmsghdr *pcmsg; iov[0].iov_base = buf; iov[0].iov_len = 2; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; if (fd_to_send >= 0) { msg.msg_control = control_un.control; msg.msg_controllen = CONTROLLEN; pcmsg = CMSG_FIRSTHDR(&msg); pcmsg->cmsg_len = CONTROLLEN; pcmsg->cmsg_level = SOL_SOCKET; pcmsg->cmsg_type = SCM_RIGHTS; *(int *)CMSG_DATA(pcmsg) = fd_to_send; } else { msg.msg_control = NULL; msg.msg_controllen = 0; buf[1] = 0; } buf[0] = 0; if ((ret = sendmsg(fd, &msg, 0)) < 0) { perror("sendmsg"); } return ret; } ``` #### 接收进程代码 ```c int recv_fd(int fd) { struct iovec iov[1]; struct msghdr msg; char buf[2]; int ret; union { struct cmsghdr cm; char control[CONTROLLEN]; } control_un; struct cmsghdr *pcmsg; iov[0].iov_base = buf; iov[0].iov_len = 2; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = control_un.control; msg.msg_controllen = CONTROLLEN; if ((ret = recvmsg(fd, &msg, 0)) < 0) { perror("recvmsg"); return -1; } if (buf[0] != 0) { printf("error: received %d bytes\n", ret); return -1; } if ((pcmsg = CMSG_FIRSTHDR(&msg)) != NULL && pcmsg->cmsg_len == CONTROLLEN) { if (pcmsg->cmsg_level != SOL_SOCKET) { printf("error: control level != SOL_SOCKET\n"); return -1; } if (pcmsg->cmsg_type != SCM_RIGHTS) { printf("error: control type != SCM_RIGHTS\n"); return -1; } return *(int *)CMSG_DATA(pcmsg); } else { return -1; } } ``` 使用示例: ```c #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> int main() { int fd[2]; int ret; char buf[256]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { perror("socketpair"); exit(1); } int send_fd = open("file.txt", O_RDONLY); if (send_fd < 0) { perror("open"); exit(1); } if ((ret = fork()) < 0) { perror("fork"); exit(1); } if (ret == 0) { // 子进程 close(fd[0]); int recv_fd = recv_fd(fd[1]); if (recv_fd < 0) { exit(1); } printf("received fd: %d\n", recv_fd); read(recv_fd, buf, 256); printf("read: %s\n", buf); close(recv_fd); exit(0); } else { // 进程 close(fd[1]); send_fd(fd[0], send_fd); close(send_fd); waitpid(ret, NULL, 0); exit(0); } } ``` 在这个示例中,进程打开了 `file.txt` 文件,并将其文件描述符通过 `send_fd` 函数发送给了子进程子进程通过 `recv_fd` 函数接收到了文件描述符,并读取了文件中的内容。注意,这个示例中并没有考虑错误处理,实际应用中需要根据实际情况进行完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值