最简单的方法是在分叉其他进程之前打开并配置文件描述符。这将为您省去最大的麻烦。
假设这是不可能的,有办法。
创建一个 unix 域套接字。让主进程监听那个套接字。一旦有人连接到它,就发送一个带有 SCM_RIGHTS 的 cmsg。在我的 linux 系统上,这记录在手册页“cmsg(3)”中。在我的 MacOS(我编写示例代码的地方)上,我找不到它的记录位置。
现在,在需要特殊文件描述符副本的进程中,打开 unix 域套接字读取 cmsg,您现在有了文件描述符。
这是一些示例代码。这很长,因为整个过程非常冗长。它将做的是不共享任何文件描述符,它将描述符发送到文件'/tmp/magic'给客户端,客户端将向它写入一条消息。传递的文件描述符在客户端和服务器的局部变量magic中。
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <err.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#define SOCKET_NAME "/tmp/socket"
#define SPECIAL_FILE "/tmp/magic"
void
server(void)
{
struct sockaddr_un sun = { 0 };
socklen_t sunlen;
int l, fd, magic;
if ((magic = open(SPECIAL_FILE, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1)
err(1, "open");
if ((l = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
err(1, "server socket");
unlink(SOCKET_NAME);
sun.sun_family = AF_UNIX;
snprintf(sun.sun_path, sizeof(sun.sun_path), SOCKET_NAME);
if (bind(l, (struct sockaddr *)&sun, sizeof(sun)) == -1)
err(1, "bind");
if (listen(l, 5) == -1)
err(1, "listen");
sunlen = sizeof(sun);
while ((fd = accept(l, (struct sockaddr *)&sun, &sunlen)) != -1) {
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
struct iovec iov;
char buf[CMSG_SPACE(sizeof(magic))];
iov.iov_base = (void *)"ok";
iov.iov_len = 2;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(magic));
*((int *)(void *)CMSG_DATA(cmsg)) = magic;
if (sendmsg(fd, &msg, 0) == -1)
err(1, "sendmsg");
close(fd);
}
close(l);
unlink(SOCKET_NAME);
}
void
client(void)
{
struct sockaddr_un sun = { 0 };
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
struct iovec iov;
int fd, magic;
char cbuf[CMSG_SPACE(sizeof(int))];
char buf[16];
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
err(1, "socket");
}
sun.sun_family = AF_UNIX;
snprintf(sun.sun_path, sizeof(sun.sun_path), SOCKET_NAME);
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
err(1, "connect");
}
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cbuf;
msg.msg_controllen = sizeof(cbuf);
if (recvmsg(fd, &msg, 0) == -1)
err(1, "recvmsg");
cmsg = CMSG_FIRSTHDR(&msg);
assert(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS);
magic = *(int *)(void *)CMSG_DATA(cmsg);
if (write(magic, "hello", strlen("hello")) != strlen("hello"))
err(1, "write(magic)");
close(magic);
close(fd);
}
int
main(int argc, char **argv)
{
switch (fork()) {
case 0:
sleep(2); /* poor mans synchronization */
printf("starting client\n");
client();
_exit(0);
break;
case -1:
err(1, "fork");
default:
printf("starting server\n");
server();
exit(1);
}
return 1;
}
关于示例的一些说明:
- 它适用于 MacOS,我还没有在其他系统上测试过,但同样的方法肯定适用于 *BSD 和 Linux。我没有在其他系统上执行此操作的经验。据称这是 POSIX,因此它应该可以在更多系统上运行。
- 应该没有必要在消息中包含任何有效负载(因此不需要带有“ok”的 iovec),但由于某种原因,我无法在没有它的情况下工作。
- 您希望在套接字上有更好的位置和权限,“/tmp/socket”在真实系统上是一个糟糕的硬编码名称。
- 根据经验,我知道某些操作系统中的 CMSG_* 宏会导致对齐问题。我相信在分配缓冲区和所有魔法来回转换时我做的一切都是正确的,但 YMMV 在严格的对齐架构上。您可能需要分配与
malloc 甚至posix_memalign 更好对齐的缓冲区。