传递文件描述符

传递的实质

一个进程向另一个进程传递文件描述符时,实质是传递并共享同一文件描述符的表项, 也就是共享文件指针的当前位置/文件状态标志等
在技术实现上就是把文件表项的指针传递给另一个进程
通常发送进程与接受进程对传递的文件描述符的编号(int fd)是不一样的

涉及的结构和函数

要发送描述符,需要用sendmsg函数,sendmsg函数里的消息参数是struct msghdr, 而fd的相关信息保存在msghdr里面的另一个结构cmsghdr中
由于cmsghdr的结构对齐原因,要正确取出里面的数据需要调用相应的宏

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

struct msghdr {
    void            *msg_name;         /* optional address */
    socklen_t        msg_namelen;      /* address size in bytes */
    struct iovec    *msg_iov;          /* array of I/O buffers */
    int              msg_iovlen;       /* number of elements in array */
    void            *msg_control;      /* ancillary data */
    socklen_t        msg_controllen;   /* number of ancillary bytes */
    int              msg_flags;        /* flags for received message */
};

struct iovec {
    void *iov_base;   /* Starting address */
    size_t iov_len;   /* Number of bytes */
};

struct cmsghdr {
    socklen_t        cmsg_len;      /* data byte count, including header */
    int              cmsg_level;    /* originating protocol */
    int              cmsg_type;     /* protocol-specific type */
    /* unsigned char *newfd;    fd数据保存在这里*/
};

unsigned char *CMSG_DATA(struct cmsghdr *cp);
返回值:指向与cmsghdr结构相关联的数据的指针, 即上面结构中的unsigned char *newfd部分
 
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
返回值:指向与msghdr结构相关联的第一个cmsghdr结构的指针,若无这样的结构则返回NULL
 
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);
返回值:指向与msghdr结构相关联的下一个cmsghdr结构的指针,该msghdr结构给出了当前cmsghdr结构,若当前cmsghdr结构已是最后一个则返回NULL
 
unsigned int CMSG_LEN(unsigned int nbytes);
返回值:为nbytes大小的数据对象分配的长度

子进程向父进程传递文件描述符

#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
 
static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
 
void send_fd(int fd, int fd_to_send){
    struct iovec iov[1];
    struct msghdr msg;
    char buf[0];
 
    iov[0].iov_base = buf;
    iov[0].iov_len = 1;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
 
    struct cmsghdr cm;
    cm.cmsg_len = CONTROL_LEN;
    cm.cmsg_level = SOL_SOCKET;
    cm.cmsg_type = SCM_RIGHTS;
    *(int*)CMSG_DATA(&cm) = fd_to_send;
    msg.msg_control = &cm; /*设置辅助数据*/
    msg.msg_controllen = CONTROL_LEN;
 
    sendmsg(fd, &msg, 0);
}
 
/*接收文件描述符*/
int recv_fd(int fd){
    struct iovec iov[1];
    struct msghdr msg;
    char buf[0];
 
    iov[0].iov_base = buf;
    iov[0].iov_len = 1;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
 
    struct cmsghdr cm;
    msg.msg_control = &cm;
    msg.msg_controllen = CONTROL_LEN;
 
    recvmsg(fd, &msg, 0);
 
    int fd_to_read = *(int*)CMSG_DATA(&cm);
 
    return fd_to_read;
}
 
int main(int argc, char* argv[]){
    int pipefd[2];
    int ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, pipefd);
    assert(ret != -1);
 
    pid_t pid = fork();
    if(pid == 0)    {
        close(pipefd[0]);
        int fd_to_pass = open("passfd.c", O_RDONLY,0666);
 
        send_fd(pipefd[1], (fd_to_pass > 0) ? fd_to_pass : 0);
        close(fd_to_pass);
 
        exit(0);
    }
 
    close(pipefd[1]);
    int fd_recived = recv_fd(pipefd[0]);
    char buf[1024];
    memset(buf, '', 1024);
    read(fd_recived, buf, 1024);
    printf("I got fd %d and data %s
", fd_recived, buf);
 
    close(fd_recived);
 
    return 0;
}

unix域传递描述符

在echo例子上多了一步, client连接到server后, server将一个fd传送给client, client读取fd内容后先转换成大写再发送到server, server收到后回射回来
fileno(FILE *fp): FILE *fp转换到int fd的形式
Fdopen(int fd,const char mode): int fd转换到FILE *fp的形式

client.c

#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
 
#define MAXLINE 1024
#define UN_PATH "/tmp/un_path" 
static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
 
void err_quit(const char *s){
    perror(s);
    exit(1);
}
 
int recv_fd(int fd){
    struct iovec iov[1];
    struct msghdr msg;
    char buf[0];
 
    iov[0].iov_base=buf;
    iov[0].iov_len=1;
    msg.msg_name=NULL;
    msg.msg_namelen=0;
    msg.msg_iov=iov;
    msg.msg_iovlen=1;
 
    struct cmsghdr cm;
    msg.msg_control=&cm;
    msg.msg_controllen=CONTROL_LEN;
 
    recvmsg(fd,&msg,0);
    int fd_to_read=*(int*)CMSG_DATA(&cm);
 
    return fd_to_read;
}
void cli_echo(int sockfd){
    int n;
    char sendline[MAXLINE],recvline[MAXLINE];
 
    int recvfd=recv_fd(sockfd);
    FILE *fp=fdopen(recvfd,"r");
    if(fp == NULL)
        err_quit("fdopen");
 
    while(fgets(sendline,MAXLINE,fp) != NULL){
        bzero(recvline,sizeof(recvline));
        int i;
        for(i=0;i<strlen(sendline);++i)
            sendline[i]=toupper(sendline[i]);
        write(sockfd,sendline,strlen(sendline));
        n=read(sockfd,recvline,MAXLINE);
        if(n == -1){
            if(errno == EINTR)
                continue;
            else
                err_quit("write");
        }
        if(n == 0){
            puts("peer closed");
            return ;
        }
        recvline[n]=0;
        fputs(recvline,stdout);
    }
    fclose(fp);
}
 
int main(int argc,char *argv[]){
    int sockfd;
    struct sockaddr_un servaddr;
 
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sun_family=AF_LOCAL;
    strcpy(servaddr.sun_path,UN_PATH);
 
    sockfd=socket(AF_LOCAL,SOCK_STREAM,0);
    if(sockfd == -1)
        err_quit("socket");
 
    if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
            err_quit("connect");
 
    cli_echo(sockfd);
 
    return 0;
}

server.c

#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
#define MAXLINE 1024
#define UN_PATH "/tmp/un_path"
static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
 
void send_fd(int fd,int fd_to_send){
    struct iovec iov[1];
    struct msghdr msg;
    char buf[0];
 
    iov[0].iov_base=buf;
    iov[0].iov_len=1;
    msg.msg_name=NULL;
    msg.msg_namelen=0;
    msg.msg_iov=iov;
    msg.msg_iovlen=1;
 
    struct cmsghdr cm;
    cm.cmsg_len=CONTROL_LEN;
    cm.cmsg_level=SOL_SOCKET;
    cm.cmsg_type=SCM_RIGHTS;
    *(int *)CMSG_DATA(&cm)=fd_to_send;
    msg.msg_control=&cm;
    msg.msg_controllen=CONTROL_LEN;
 
    sendmsg(fd,&msg,0);
}
void err_quit(const char *s){
    perror(s);
    exit(1);
}
 
void serv_echo(int sockfd){
    int n;
    char mesg[MAXLINE];
 
    int fd=open("server.c",O_RDONLY);
    if(fd < 0)
        err_quit("open");
 
    send_fd(sockfd,fd);
    close(fd);
    for(;;){
        bzero(mesg,sizeof(mesg));
        n=read(sockfd,mesg,MAXLINE);
        if(n == -1){
            if(errno == EINTR)
                continue;
            else
                err_quit("recvfrom");
        }
        if(n == 0){
            puts("peer closed");
            return ;
        }
        fputs(mesg,stdout);
        write(sockfd,mesg,n);
    }
}
 
 
void sig_chld(int signo){
    while(waitpid(-1,NULL,WNOHANG) > 0)
        ;
}
 
int main(int argc,char *argv[]){
    int sockfd,connfd;
    pid_t pid;
    struct sockaddr_un servaddr;
 
    sockfd=socket(AF_LOCAL,SOCK_STREAM,0);
 
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sun_family=AF_LOCAL;
    unlink(UN_PATH);
    strcpy(servaddr.sun_path,UN_PATH);
 
    if(bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
        err_quit("bind");
 
    if(listen(sockfd,SOMAXCONN) < 0)
        err_quit("listen");
 
    signal(SIGCHLD,sig_chld);
 
    while(1){
        connfd=accept(sockfd,NULL,NULL);
        if(connfd == -1){
            if(errno == EINTR)
                continue;
            else
                err_quit("accept");
        }
 
        pid=fork();
        if(pid == -1)
            err_quit("fork");
        else if(pid == 0){
            close(sockfd);  
            serv_echo(connfd);
            exit(0);
        }
 
        close(connfd);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/cfans1993/p/6144819.html