传递的实质
一个进程向另一个进程传递文件描述符时,实质是传递并共享同一文件描述符的表项, 也就是共享文件指针的当前位置/文件状态标志等
在技术实现上就是把文件表项的指针传递给另一个进程
通常发送进程与接受进程对传递的文件描述符的编号(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;
}