使用UNIX域Socket传递文件描述符

[使用UNIX域Socket传递文件描述符]

使用sendmsg(),recvmsg()

  1. fork后子进程继承父进程进程表象的描述符表(所有描述符项目),所以子进程和父进程相同的fd都共享了相同文件表象.

    1

  1. 不同进程打开相同路径文件的情况:

    在这里插入图片描述

操作文件的i节点是相同的,但是在内核文件表中维护了2个不同的文件表象(2个独立的进程),

2个进程间传递fd的目的就是使2个进程的fd共享一个相同的文件表项

3

fork实现了父进程->子进程的fd传递,

独立进程间通过sendmsg/recvmsg的cmsghdr实现

1
2
3
4
5
6
7
8
9
struct msghdr{
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
socklen_t msg_controllen;
int msg_flags;
};

msg_control字段指向struct cmsghdr(控制信息头)结构

msg_controllen=控制信息的字节数

1
2
3
4
5
6
struct cmsghdr{
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
//这里是跟随的额外数据
};

发送fd需要将:

  1. cmsg_len设为cmsghdr+int(描述符)长度
  2. cmg_level设置为SOL_SOCKET
  3. cmsg_type设置为SCM_RIGHTS(表明在传送访问权,SCM=’Socket-level Control Message’套接字级别控制消息) 访问权仅能通过UNIX域Socket传送,fd在cmsg_type字段之后存储,通过CMSG_DATA()获得次整形量的指针
1
2
3
4
#include <sys/socket.h>

unsigned char* CMSG_DATA(struct cmsghdr* cp);//返回指针,指向与cmsghdr结构相关联的数据
unsigned int CMSG_LEN(unsigned int nbytes);//返回为cmsghdr添加nbytes长的数据对象后的cmsghdr的长度

查看CMSG_LEN返回的值大小

1
2
3
4
5
6
7
#include "me.h"

int main()
{
printf("[cmsghdr size] = %d\n[CMSG_LEN] = %d\n",sizeof(struct cmsghdr),CMSG_LEN(sizeof(int)));
return 0;
}

子进程向父进程传递fd

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
87
88
#include "me.h"

int CONTROL_LEN = CMSG_LEN(sizeof(int));//返回为sizeof(int)长度的数据对象分配的长度

void send_fd(int fd,int fd_to_send)
{
struct iovec iov[1];
struct msghdr msg;
char buf[2];

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;

struct 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;

// printf("[cmsghdr size] = %d\n[send_fd CONTROL_LEN] = %d\n",sizeof(struct cmsghdr),CONTROL_LEN);

sendmsg(fd,&msg,0);
}

int recv_fd(int fd)
{
struct iovec iov[1];
struct msghdr msg;
char buf[2];

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;

struct cmsghdr cm;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;
printf("[recv_fd CONTROL_LEN] = %d\n",CONTROL_LEN);

recvmsg(fd,&msg,0);
int fd_to_read = *(int*)CMSG_DATA(&cm);
return fd_to_read;//转化为此进程的最小可用fd了
}

int main()
{
printf("[parent pid]: %d\n",getpid());
int pipefd[2];
int fd_to_pass = 0;
int ret = socketpair(AF_UNIX,SOCK_DGRAM,0,pipefd);
assert(ret != -1);

pid_t pid = fork();
assert(pid >= 0);

if (pid == 0)
{
printf("[child pid]: %d\n",getpid());
close(pipefd[0]);
fd_to_pass = open("1.in",O_RDWR,0666);
printf("[child process]: fd_to_pass = %d\n",fd_to_pass);
send_fd(pipefd[1],(fd_to_pass > 0) ? fd_to_pass : 0);
sleep(400);
close(fd_to_pass);
exit(0);
}
close(pipefd[1]);
fd_to_pass = recv_fd(pipefd[0]);

char buf[1024];
memset(buf,'\0',1024);
read(fd_to_pass,buf,1024);
printf("[parent process]: fd_to_pass = %d\n",fd_to_pass);
printf("I got fd %d and data %s \n",fd_to_pass,buf);
sleep(500);
close(fd_to_pass);
exit(0);
}

查看当前目录下1.in文件

通过lsof查看传递过去的fd的num并不是和原来的一样的,但是传递成功(得到了文件内容)

lsof -p 父进程

4

lsof -p 子进程

5

可以看到传递过去后fd的num是不同的