Вот пример, адаптированный из того, что я делал долгое время go.
Хитрость заключается в том, чтобы полагаться на вспомогательные данные системных вызовов sendmsg()
/ recvmsg()
в локальном сокете для передачи открытого дескриптор файла.
В этом примере для иллюстрации передачи fd используются обычные файлы, но, конечно, вместо него можно использовать сокеты TCP (в среде, отличной от windows).
Есть много деталей, которые не так очевидно, не копаясь в документации, но после инкапсуляции в некоторые функции, такие как send_fd()
/ receive_fd()
в этом примере, его довольно просто использовать.
Обратите внимание, что при получении дескриптор файла не обязательно иметь тот же номер, что и при отправке.
Его следует рассматривать как своего рода dup()
между разными процессами: дескрипторы файлов с разными номерами фактически относятся к одному и тому же ресурсу.
Обратите внимание также, что, поскольку как только дескриптор файла был отправлен в другой процесс, его можно закрыть (точно так же, как с dup()
/ dup2()
), потому что новый файл скриптор, который будет получен другим процессом, по-прежнему относится к исходному ресурсу.
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
$ ./prog_c
parent-process: using fd 3
child-process: using fd 4
parent-process: using fd 3
child-process: using fd 4
parent-process: using fd 3
child-process: using fd 4
parent-process: using fd 3
child-process: using fd 4
parent-process: using fd 3
child-process: using fd 4
child-process: done
$ cat file_?.txt
file_0.txt written by parent-process when opening
Now child-process uses this open file.
file_1.txt written by parent-process when opening
Now child-process uses this open file.
file_2.txt written by parent-process when opening
Now child-process uses this open file.
file_3.txt written by parent-process when opening
Now child-process uses this open file.
file_4.txt written by parent-process when opening
Now child-process uses this open file.
**/
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int // 0: success -1: error
send_fd(int control_fd,
int fd_to_send)
{
int dummy_msg=1234;
struct iovec vec;
vec.iov_base=&dummy_msg;
vec.iov_len=sizeof(dummy_msg);
struct msghdr msg;
msg.msg_name=NULL;
msg.msg_namelen=0;
msg.msg_iov=&vec;
msg.msg_iovlen=1;
struct cmsghdr * cmsg;
char buffer[CMSG_SPACE(sizeof(int))];
msg.msg_control=buffer;
msg.msg_controllen=CMSG_SPACE(sizeof(int));
cmsg=CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level=SOL_SOCKET;
cmsg->cmsg_type=SCM_RIGHTS; // fd passing
cmsg->cmsg_len=CMSG_LEN(sizeof(int));
*((int *)CMSG_DATA(cmsg))=fd_to_send; // send new file descriptor
msg.msg_flags=0;
return sendmsg(control_fd, &msg, 0)==sizeof(dummy_msg) ? 0 : -1;
}
int // fd or -1
receive_fd(int control_fd)
{
char buffer[CMSG_SPACE(sizeof(int))];
struct msghdr msg;
struct iovec vec;
struct cmsghdr *cmsg;
int dummy_msg;
vec.iov_base=&dummy_msg;
vec.iov_len=sizeof(dummy_msg);
msg.msg_name=NULL;
msg.msg_namelen=0;
msg.msg_iov=&vec;
msg.msg_iovlen=1;
msg.msg_control=buffer;
msg.msg_controllen=CMSG_SPACE(sizeof(int));
msg.msg_flags=0;
if(recvmsg(control_fd, &msg, 0)!=sizeof(dummy_msg))
{
return -1;
}
int fd=-1;
cmsg=CMSG_FIRSTHDR(&msg); // ancillary data?
if(cmsg&&(cmsg->cmsg_len>=(socklen_t)CMSG_LEN(sizeof(int)))&&
(cmsg->cmsg_level==SOL_SOCKET)&&
(cmsg->cmsg_type==SCM_RIGHTS)) // fd passing?
{
fd=*((int *)CMSG_DATA(cmsg)); // store new file descriptor
}
return fd;
}
int
main(void)
{
int control_pair[2];
if(socketpair(PF_LOCAL, SOCK_STREAM, 0, control_pair)==-1)
{
perror("socketpair");
exit(1);
}
pid_t p=fork();
if(p==-1)
{
perror("fork");
exit(1);
}
if(p==0) // child process
{
close(control_pair[1]); // used by parent-process (arbitrary)
for(;;)
{
int fd=receive_fd(control_pair[0]);
if(fd==-1)
{
printf("child-process: done\n");
break;
}
printf("child-process: using fd %d\n", fd);
const char *text="Now child-process uses this open file.\n";
write(fd, text, strlen(text));
close(fd);
}
close(control_pair[0]);
exit(0); // child-process stops here
}
// parent process
close(control_pair[0]); // used by child-process (arbitrary)
for(int i=0; i<5; ++i)
{
char text[100];
sprintf(text, "file_%d.txt", i);
int fd=open(text, O_WRONLY|O_TRUNC|O_CREAT, 0644);
if(fd==-1)
{
perror("open");
break;
}
printf("parent-process: using fd %d\n", fd);
strcat(text, " written by parent-process when opening\n");
write(fd, text, strlen(text));
if(send_fd(control_pair[1], fd)==-1)
{
perror("send_fd");
close(fd);
break;
}
close(fd);
sleep(1);
}
close(control_pair[1]);
if(waitpid(p, NULL, 0)!=p)
{
perror("waitpid");
exit(1);
}
return 0;
}