进程间传递句柄

被传递句柄可以是文件句柄或socket句柄。

方法

在 Linux 下,进程间可以通过以下几种方式传递句柄(文件描述符):

  1. 父子进程继承:当一个子进程派生(fork)自父进程时,子进程会继承父进程打开的文件描述符。子进程可以直接使用这些文件描述符。

  2. 命令行参数:父进程可以将打开的文件描述符的值作为命令行参数传递给子进程。子进程可以从 argv 参数列表中获取这些文件描述符的值,并在需要时进行使用。

  3. UNIX 域套接字(UNIX domain socket):UNIX 域套接字是一种进程间通信的机制,除了传递字节流数据外,它还可以传递文件描述符。通过使用 sendmsg()recvmsg() 系统调用,发送进程可以将文件描述符附加到消息中发送给接收进程。

  4. 消息队列(Message queues):消息队列是一种进程间通信的机制,允许进程通过发送和接收消息进行通信。通过设置消息类型和在消息中包含文件描述符,发送进程可以将文件描述符传递给接收进程。

  5. 共享内存(Shared memory):通过共享内存,多个进程可以访问同一块内存区域。如果句柄信息(文件描述符)保存在共享内存中,进程可以通过共享内存来传递文件描述符。

  6. UNIX 域套接字配合 SCM_RIGHTS 控制消息:通过发送控制消息(control message)并设置 SCM_RIGHTS 控制消息标志,进程可以将文件描述符传递给其他进程。这需要使用 sendmsg()recvmsg() 系统调用,并指定消息的类型为文件描述符。

请注意,传递文件描述符的方法可能因操作系统或编程语言的不同而有所不同。以上列出的是一些常用的方法,您可以根据自己的需求和环境选择合适的方法来传递文件描述符。

示例

以下是使用 UNIX 域套接字(UNIX domain socket)传递文件描述符的简单示例:

发送进程(发送方):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>

int main() {
int fd = open("file.txt", O_RDONLY); // 打开要传递的文件
if (fd == -1) {
perror("open");
return 1;
}

int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建 UNIX 域套接字
if (sockfd == -1) {
perror("socket");
return 1;
}

struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "socket", sizeof(addr.sun_path) - 1);

if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { // 连接到接收进程
perror("connect");
return 1;
}

struct msghdr msg = { 0 };
struct iovec iov[1];
char buf[1];
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf);

char cmsgbuf[CMSG_SPACE(sizeof(fd))];
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);

struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA(cmsg)) = fd;

msg.msg_iov = iov;
msg.msg_iovlen = 1;

if (sendmsg(sockfd, &msg, 0) == -1) { // 发送文件描述符
perror("sendmsg");
return 1;
}

close(fd); // 关闭文件
close(sockfd); // 关闭套接字

return 0;
}

接收进程(接收方):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>

int main() {
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建 UNIX 域套接字
if (sockfd == -1) {
perror("socket");
return 1;
}

struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "socket", sizeof(addr.sun_path) - 1);

if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { // 绑定套接字
perror("bind");
return 1;
}

if (listen(sockfd, 1) == -1) { // 监听连接请求
perror("listen");
return 1;
}

int connfd = accept(sockfd, NULL, NULL); // 接受连接请求
if (connfd == -1) {
perror("accept");
return 1;
}

struct msghdr msg = { 0 };
char buf[1];

struct iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;

char cmsgbuf[CMSG_SPACE(sizeof(int))];
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);

if (recvmsg(connfd, &msg, 0) == -1) { // 接收文件描述符
perror("recvmsg");
return 1;
}

int fd;
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg != NULL && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
fd = *((int *) CMSG_DATA(cmsg));
}
else {
fprintf(stderr, "Received invalid file descriptor\n");
return 1;
}

FILE* file = fdopen(fd, "r");
if (file == NULL) {
perror("fdopen");
return 1;
}

char line[100分];
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line); // 打印文件内容
}

fclose(file); // 关闭文件
close(connfd); // 关闭连接套接字
close(sockfd); // 关闭监听套接字

return 0;
}

在上面的示例中,发送进程打开了文件 “file.txt”,并通过 UNIX 域套接字将文件描述符发送给接收进程。接收进程接收文件描述符,并使用 fdopen 函数将文件描述符转换为文件指针,最后打印文件的内容。

这仅是一个简单的示例,实际使用时需要进行错误处理和适当的错误检查。确保运行示例时,套接字的命名、路径和权限正确,并确保发送进程在接收进程之前启动。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!