需要注意一点,传递一个文件描述符,并不是传递一个文件描述符的值,而是要在接收进程中创建一个新的文件描述符,并且该文件描述符和发送进程中被传递的文件描述符,指向同一个文件。
例程
#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));
//发送文件描述符,fd参数是用来传递信息的UNIX域socket,fd_to_send参数是待发送的文件描述符
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;
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;
cmsghdr cm;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
recvmsg(fd,&msg,0);
int fd_to_read = *(int *) CMSG_DATA(&cm);
return fd_to_read;
}
int main(int argc, char const *argv[])
{
int pipefd[2];
int fd_to_pass1 = 0;
int fd_to_pass = 0;
int ret = socketpair(PF_UNIX,SOCK_DGRAM,0,pipefd);
assert(ret != -1);
pid_t pid = fork();
assert(pid >= 0);
if (pid ==0)
{
close(pipefd[0]);
fd_to_pass1 = open("test1.txt",O_RDWR,0666);
send_fd(pipefd[1],(fd_to_pass1 >0)? fd_to_pass1 :0);
printf("this is chile process pid= %d,open file test1.txt,it's fd =%d\n",getpid(),fd_to_pass1);
fd_to_pass = open("test2.txt",O_RDWR,0666);
send_fd(pipefd[1],(fd_to_pass >0)? fd_to_pass :0);
printf("this is chile process pid= %d,open file test2.txt,it's fd =%d\n",getpid(),fd_to_pass);
close(fd_to_pass1);
close(fd_to_pass);
exit(0);
}
close(pipefd[1]);
fd_to_pass1 = recv_fd(pipefd[0]); //父进程从管道接收目标文件描述符
char buf[1024];
memset(buf,'\0',1024);
read(fd_to_pass1,buf,1024);//读目标文件描述符,以验证其有效性
printf("this is father process pid= %d,getfile fd =%d and data:%s\n",getpid(),fd_to_pass1,buf);
close(fd_to_pass1);
fd_to_pass = recv_fd(pipefd[0]); //父进程从管道接收目标文件描述符
memset(buf,'\0',1024);
read(fd_to_pass,buf,1024);//读目标文件描述符,以验证其有效性
printf("this is father process pid= %d,getfile fd =%d and data:%s\n",getpid(),fd_to_pass,buf);
close(fd_to_pass);
return 0;
}
结果
cwd@cwd:~/code/test$ ./passfd_vis_process
this is chile process pid= 18009,open file test1.txt,it's fd =3
this is father process pid= 18008,getfile fd =4 and data:heihei,cwd.this is test1
this is chile process pid= 18009,open file test2.txt,it's fd =5
this is father process pid= 18008,getfile fd =4 and data:heihei,cwd.this is test2
可见程序对文件描述符进行了正确的传递,但是直接输出各个进程对同一个文件的描述符的值并不相同。
补充
man手册上对该程序中几个函数的讲解
recvmsg()
The recvmsg() call uses a msghdr structure to minimize the number of
directly supplied arguments. This structure is defined as follows in
<sys/socket.h>:
struct iovec { /* Scatter/gather array items */
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
The msg_name field points to a caller-allocated buffer that is used to
return the source address if the socket is unconnected. The caller
should set msg_namelen to the size of this buffer before this call;
upon return from a successful call, msg_namelen will contain the length
of the returned address. If the application does not need to know the
source address, msg_name can be specified as NULL.
The fields msg_iov and msg_iovlen describe scatter-gather locations, as
discussed in readv(2).
The field msg_control, which has length msg_controllen, points to a
buffer for other protocol control-related messages or miscellaneous
ancillary data. When recvmsg() is called, msg_controllen should con‐
tain the length of the available buffer in msg_control; upon return
from a successful call it will contain the length of the control mes‐
sage sequence.
The messages are of the form:
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 */
/* followed by
unsigned char cmsg_data[]; */
};
Ancillary data should be accessed only by the macros defined in
cmsg(3).
As an example, Linux uses this ancillary data mechanism to pass
extended errors, IP options, or file descriptors over UNIX domain sock‐
ets.
sendmsg()
The field msg_control, which has length msg_controllen, points to a
buffer for other protocol control-related messages or miscellaneous
ancillary data. When recvmsg() is called, msg_controllen should con‐
tain the length of the available buffer in msg_control; upon return
from a successful call it will contain the length of the control mes‐
sage sequence.
The messages are of the form:
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 */
/* followed by
unsigned char cmsg_data[]; */
};
Ancillary data should be accessed only by the macros defined in
cmsg(3).
As an example, Linux uses this ancillary data mechanism to pass
extended errors, IP options, or file descriptors over UNIX domain sock‐
ets.