使用 Unix Domain Sockets 在不同进程间传递文件句柄

Posted by shensunbo on October 9, 2024

introduce

Note: 句柄通过 msg_control 消息来发送,但是 msg_iov 仍然需要正确填充,不然会报错,可以随便填充一个字符

  • 发送一个描述符会使描述符的引用计数加1,意味着即使在接收进程调用recvmsg之前,发送进程关闭了该描述符,对于接收进程他任然保持打开状态
  • 在接收进程中会创建一个新的描述符,所以发送进程和接收进程的描述符是不相等的

单个句柄

sender

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
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>

#define SOCKET_NAME "/tmp/my_socket"

void send_fd(int socket, int fd_to_send) {
    const char *str1 = "Hello, ";
    struct msghdr msg = {};
    struct iovec io = { .iov_base = (void *)str1, .iov_len = 1 };
    char control[CMSG_SPACE(sizeof(int))];
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);

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

    if (sendmsg(socket, &msg, 0) == -1) {
        perror("sendmsg");
    }
}

int main() {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un server_addr = { .sun_family = AF_UNIX, .sun_path = SOCKET_NAME };

    unlink(SOCKET_NAME);
    bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    listen(sockfd, 1);

    std::cout << "Awaiting connection..." << std::endl;
    int client_sock = accept(sockfd, NULL, NULL);
    std::cout << "Connection accepted!" << std::endl;

    int file_fd = open("example.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    send_fd(client_sock, file_fd);
    std::cout << "File descriptor sent!" << std::endl;

    close(file_fd);
    close(client_sock);
    close(sockfd);
    return 0;
}

receiver

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
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>

#define SOCKET_NAME "/tmp/my_socket"

int receive_fd(int socket) {
    struct msghdr msg = {};
    char buf[1];
    struct iovec io = { .iov_base = buf, .iov_len = sizeof(buf) };
    char control[CMSG_SPACE(sizeof(int))];
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);

    if (recvmsg(socket, &msg, 0) <= 0) {
        perror("recvmsg");
        return -1;
    }

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    return *((int *)CMSG_DATA(cmsg));
}

int main() {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un server_addr = { .sun_family = AF_UNIX, .sun_path = SOCKET_NAME };

    connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    std::cout << "Connected to sender!" << std::endl;

    int fd_received = receive_fd(sockfd);
    if (fd_received != -1) {
        std::cout << "File descriptor received: " << fd_received << std::endl;

        // Use the file descriptor (e.g., read/write to the file it points to)
        // Don't forget to close it eventually
        char buffer[100];
        read(fd_received, buffer, sizeof(buffer));
        std::cout << "Read from file: " << buffer << std::endl;
        close(fd_received);
    }

    close(sockfd);
    return 0;
}

句柄数组

sender

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
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define SOCKET_NAME "/tmp/my_socket"

int main() {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    struct sockaddr_un server_addr = { .sun_family = AF_UNIX };
    strncpy(server_addr.sun_path, SOCKET_NAME, sizeof(server_addr.sun_path) - 1);

    unlink(SOCKET_NAME);
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        close(sockfd);
        return 1;
    }

    if (listen(sockfd, 1) == -1) {
        perror("listen");
        close(sockfd);
        return 1;
    }

    std::cout << "Awaiting connection..." << std::endl;
    int client_sock = accept(sockfd, NULL, NULL);
    if (client_sock == -1) {
        perror("accept");
        close(sockfd);
        return 1;
    }
    std::cout << "Connection accepted!" << std::endl;

    int file_fd = open("example.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    int file_fd2 = open("log1.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    int file_fd3 = open("log2.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    int file_fd4 = open("log3.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);

    char dummy_data = 'X';
    struct iovec io = { .iov_base = &dummy_data, .iov_len = 1 };
    
    char cmsgbuf[CMSG_SPACE(sizeof(int) * 4)];
    struct msghdr msg = {};
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int) * 4);
    int *fdptr = (int *)CMSG_DATA(cmsg);
    fdptr[0] = file_fd;
    fdptr[1] = file_fd2;
    fdptr[2] = file_fd3;
    fdptr[3] = file_fd4;

    std::cout << "File descriptors to send: " << file_fd << " "
              << file_fd2 << " " << file_fd3 << " " << file_fd4 << std::endl;

    if (sendmsg(client_sock, &msg, 0) == -1) {
        perror("sendmsg");
        std::cerr << "errno: " << errno << std::endl;
    } else {
        std::cout << "File descriptors sent successfully!" << std::endl;
    }

    close(file_fd);
    close(file_fd2);
    close(file_fd3);
    close(file_fd4);
    close(client_sock);
    close(sockfd);
    return 0;
}

receiver

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
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>

#define SOCKET_NAME "/tmp/my_socket"

int main() {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un server_addr = { .sun_family = AF_UNIX, .sun_path = SOCKET_NAME };

    if(0 != connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr))){
        std::cout << "connect error!" << std::endl;
        return -1;
    }
    std::cout << "Connected to sender!" << std::endl;

    // int fd_received = receive_fd(sockfd);
    struct msghdr msg = {};
    char buf[1];
    struct iovec io = { .iov_base = buf, .iov_len = sizeof(buf) };
    char control[CMSG_SPACE(sizeof(int) * 4)];
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);

    if (recvmsg(sockfd, &msg, 0) <= 0) {
        perror("recvmsg");
        return -1;
    }

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    int* fdptr = ((int *)CMSG_DATA(cmsg));

    std::cout << "File descriptor received: " << fdptr[0] <<" "
        << fdptr[1] <<" " << fdptr[2] <<" " << fdptr[3] << std::endl;

    // Use the file descriptor (e.g., read/write to the file it points to)
    // Don't forget to close it eventually
    char buffer[100];
    read(fdptr[0], buffer, sizeof(buffer));
    std::cout << "Read from file 0: " << buffer << std::endl;
    close(fdptr[0]);

    read(fdptr[1], buffer, sizeof(buffer));
    std::cout << "Read from file 1: " << buffer << std::endl;
    close(fdptr[1]);

    read(fdptr[2], buffer, sizeof(buffer));
    std::cout << "Read from file 2: " << buffer << std::endl;
    close(fdptr[2]);

    read(fdptr[3], buffer, sizeof(buffer));
    std::cout << "Read from file 3: " << buffer << std::endl;
    close(fdptr[3]);

    close(sockfd);
    return 0;
}