11.1.6 传递文件描述符的例子
本节中使用一个实例来介绍进程间传递文件描述符的例子。分为两个进程,进程A中打开一个文件描述符,通过消息传送的方式将文件描述符传递给进程B。
1.进程A的代码
进程A根据用户输入的文件名打开一个文件,将文件描述符打包到消息结构中,然后发送给进程B。
01 #include <sys/types.h>
02 #include <sys/socket.h>
03 #include <Linux/un.h>
04 #include <string.h>
05 #include <signal.h>
06 #include <stdio.h>
07 #include <errno.h>
08 #include <unistd.h>
09
10 ssize_t send_fd(int fd, void*data, size_t bytes, int sendfd)
11 {
12 struct msghdr msghdr_send; /*发送消息*/
13 struct iovec iov[1]; /*向量*/
14 size_t n; /*大小*/
15 int newfd; /*文件描述符*/
16 /*方便操作msg的结构*/
17 union{
18 struct cmsghdr cm; /*control msg结构*/
19 char control[CMSG_SPACE(sizeof(int))];
/*字符指针,方便控制*/
20 }control_un;
21 struct cmsghdr*pcmsghdr=NULL; /*控制头部的指针*/
22 msghdr_send.msg_control = control_un.control; /*控制消息*/
23 msghdr_send.msg_controllen = sizeof(control_un.control);
/*长度*/
24
25 pcmsghdr = CMSG_FIRSTHDR(&msghdr_send); /*取得第一个消息头*/
26 pcmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); /*获得长度*/
27 pcmsghdr->cmsg_level = SOL_SOCKET; /*用于控制消息*/
28 pcmsghdr->cmsg_type = SCM_RIGHTS;
29 *((int*)CMSG_DATA(pcmsghdr))= sendfd; /*socket值*/
30
31
32 msghdr_send.msg_name = NULL; /*名称*/
33 msghdr_send.msg_namelen = 0; /*名称长度*/
34
35 iov[0].iov_base = data; /*向量指针*/
36 iov[0].iov_len = bytes; /*数据长度*/
37 msghdr_send.msg_iov = iov; /*填充消息*/
38 msghdr_send.msg_iovlen = 1;
39
40 return (sendmsg(fd, &msghdr_send, 0)); /*发送消息*/
41 }
42
43
44 int main(int argc, char*argv[])
45 {
46 int fd;
47 ssize_t n;
48
49 if(argc != 4){
50 printf("socketpair error/n");
51 if((fd = open(argv[2],atoi(argv[3])))<0) /*打开输入的文件名称*/
52 return(0);
53
54 if((n =send_fd(atoi(argv[1]),"",1,fd))<0) /*发送文件描述符*/
55 return(0);
56 }
分为如下的步骤:
q 第10~41行为函数send_fd(),它向文件描述符fd发送消息,将sendfd打包到消息体中。
q 第12行建立一个消息,之后填充此消息的成员数据,并发送给fd。
q 第13行为向量,消息的数据在此项两种保存。
q 第17~20行建立一个联合结构,便于进行消息的处理。
q 第22行填充消息的控制部分,第23行为控制部分的长度。
q 第25行取得消息的第一个头部。
q 第26行为长度,由于发送的是一个文件描述符,所以长度为一个int类型的长度。
q 第27行设置消息的level为SOL_SOCKET,第28行填充消息的类型为SCM_RIGHTS。
q 第32行和第33行用于将消息的名称置空。
q 第35行和第36行将传入的数据和长度传递给向量成员。
q 第37行将向量填充给消息,第38行设置向量的个数。
q 第40行将消息发送给fd。
q 第44~56行为main()函数,用于将打开的文件描述符传递给输入的socket。
q 第51行打开传入路径的文件。
q 第54行将打开的文件传递给输入的某个套接字文件描述符。
2.进程B的代码
进程B获得进程A中发送过来的消息,并从中取得文件描述符。根据获得的文件描述符,直接从文件中读取数据,并将数据在标准输出打印出来。
001 #include <sys/types.h>
002 #include <sys/socket.h>
003 #include <Linux/un.h>
004 #include <string.h>
005 #include <signal.h>
006 #include <stdio.h>
007 #include <errno.h>
008 #include <unistd.h>
009
010 /*
011 * 从fd中接收消息,并将文件描述符放在指针recvfd中
012 */
013 ssize_t recv_fd(int fd, void*data, size_t bytes, int*recvfd)
014 {
015 struct msghdr msghdr_recv; /*接收消息接收*/
016 struct iovec iov[1]; /*接收数据的向量*/
017 size_t n;
018 int newfd;
019
020 union{
021 struct cmsghdr cm;
022 char control[CMSG_SPACE(sizeof(int))];
023 }control_un;
024 struct cmsghdr*pcmsghdr; /*消息头部*/
025 msghdr_recv.msg_control = control_un.control; /*控制消息*/
026 msghdr_recv.msg_controllen = sizeof(control_un.control);
/*控制消息的长度*/
027
028 msghdr_recv.msg_name = NULL; /*消息的名称为空*/
029 msghdr_recv.msg_namelen = 0; /*消息的长度为空*/
030
031 iov[0].iov_base = data; /*向量的数据为传入的数据*/
032 iov[0].iov_len = bytes; /*向量的长度为传入数据的长度*/
033 msghdr_recv.msg_iov = iov; /*消息向量指针*/
034 msghdr_recv.msg_iovlen = 1; /*消息向量的个数为1个*/
035 if((n = recvmsg(fd, &msghdr_recv, 0))<=0) /*接收消息*/
036 return n;
037
038 if((pcmsghdr = CMSG_FIRSTHDR(&msghdr_recv))!= NULL &&
/*获得消息的头部*/
039 pcmsghdr->cmsg_len == CMSG_LEN(sizeof(int))){
/*获得消息的长度为int*/
040 if(pcmsghdr->cmsg_level != SOL_SOCKET)
/*消息的level应该为SOL_SOCKET*/
041 printf("control level != SOL_SOCKET/n");
042
043 if(pcmsghdr->cmsg_type != SCM_RIGHTS) /*消息的类型判断*/
044 printf("control type != SCM_RIGHTS/n");
045
046 *recvfd =*((int*)CMSG_DATA(pcmsghdr));
/*获得打开文件的描述符*/
047 }else
048 *recvfd = -1;
049
050 return n; /*返回接收消息的长度*/
051 }
052
053 int my_open(const char*pathname, int mode)
054 {
055 int fd, sockfd[2],status;
056 pid_t childpid;
057 char c, argsockfd[10],argmode[10];
058
059 socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd); /*建立socket*/
060 if((childpid = fork())==0){ /*子进程*/
061 close(sockfd[0]); /*关闭sockfd[0]*/
062 snprintf(argsockfd, sizeof(argsockfd),"%d",sockfd[1]); /*socket描述符*/
063 snprintf(argmode, sizeof(argmode),"%d",mode);
/*打开文件的方式*/
064 execl("./openfile","openfile",argsockfd, pathname,
argmode,(char*)NULL) ;/*执行进程A*/
065 printf("execl error/n");
066 }
067 /*父进程*/
068 close(sockfd[1]);
069 /*等待子进程结束*/
070 waitpid(childpid, &status,0);
071
072 if(WIFEXITED(status)==0){ /*判断子进程是否结束*/
073 printf("child did not terminate/n") ;
074 if((status = WEXITSTATUS(status))==0){ /*子进程结束*/
075 recv_fd(sockfd[0],&c,1,&fd); /*接收进程A打开的文件描述符*/
076 } else{
077 errno = status;
078 fd = -1;
079 }
080
081 close(sockfd[0]); /*关闭sockfd[0]*/
082 return fd; /*返回进程A打开文件的描述符*/
083
084 }
085 }
086
087 #define BUFFSIZE 256 /*接收的缓冲区大小*/
088 int main(int argc, char*argv[])
089 {
090 int fd, n;
091 char buff[BUFFSIZE]; /*接收缓冲区*/
092
093 if(argc !=2)
094 printf("error argc/n");
095
096 if((fd = my_open(argv[1], O_RDONLY))<0)
/*获得进程A打开的文件描述符*/
097 printf("can't open %s/n",argv[1]);
098
099 while((n = read(fd, buff, BUFFSIZE))>0) /*读取数据*/
100 write(1,buff,n); /*写入标准输出*/
101
102 return(0);
103 }
分为如下的步骤:
q 第013~051行为函数recv_fd(),它从fd接收消息,并返回获得消息中的信息:打开文件的描述符。
q 第015行建立一个消息,之后填充此消息的成员数据,并发送给fd。
q 第016行为向量,消息的数据在此项中保存。
q 第020~023行建立一个联合结构,便于进行消息的处理。
q 第025行填充消息的控制部分,第026行为控制部分的长度。
q 第028行和第029行用于将消息的名称置空。
q 第031行和第032行将传入的数据和长度传递给向量成员。
q 第033行将向量填充给消息,第034行设置向量的个数。
q 第035行接收消息。
q 第038行取得消息的第一个头部。
q 第039行判断消息长度是否为int长度。
q 第040行判断消息level是否为SOL_SOCKET。
q 第043行判断消息的类型是否为SCM_RIGHTS。
q 第046行获得传入的文件描述符。
q 第053~085行为my_open()函数,按照传入的路径和模式打开文件。
q 第059行调用socketpair()函数获得socket对。
q 第060行为fork()进程
q 第061~066行在子进程中调用外部进程打开文件。
q 第069~084行为父进程处理过程。等待子进程处理函数的结束,并接收传过来 的值。
q 第087~103行为主函数。它调用my_open()获得进程A传入的文件描述符,从文件中读取数据并显示到标准输出。