传送文件描述符的简单示例

Posted Monster-Z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了传送文件描述符的简单示例相关的知识,希望对你有一定的参考价值。

     传送进程描述符,简单的来说,就是进程A打开一个文件f,获得了一个文件描述符fd1,然后进程A将该描述符通过某些方式,传递给了B,此时B就具有了描述符fd2(注意,fd1 不一定等于fd2),从而可以通过fd2对文件f进行读写等一系列的操作。其实本质上

相当于A,B两个进程同时打开了文件f。

     具体实现其实比较简单,例如当一个父进程要向子进程传递一个文件描述符时,首先会在fork产生子进程以前,调用socketpair建立一个套接字对,用于父子进程之间的通信。之后父进程打开文件f,获得文件描述符fd1。接着通过sendmsg将包含文件描述符fd1的消息发送出去,而在子进程通过recvmsg接收消息,从中获取出文件描述符fd2。最后,子进程就能通过操作fd2对文件f进行一系列的读写操作。

    最后,需要注意的是,当发送进程将文件描述符传送给接收进程以后,通常会关闭该描述符。不过,发送进程关闭该描述符并不会真的关闭该文件或设备,其原因是该文件描述符仍然视为由接收进程打开(即使接收进程尚未接收到该描述符,此时称该描述符在飞行中...in flight)。简单的代码示例,如下所示:

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/socket.h>

// CONTROLLEN 为cmsghdr加一个文件描述符的长度
#define CONTROLLEN CMSG_LEN(sizeof(int)) 

int send_fd(int fd, int fd_to_send) {
	struct iovec	iov[1];
	struct msghdr	msg;
	char		buf[1];			// buf用于表示传递的描述符是否合法,合法buf[0]=0, 否则buf[0]=1
	struct cmsghdr	*cmptr = NULL;

	iov[0].iov_base = buf;		
	iov[0].iov_len	= 1;		
	msg.msg_iov	= iov;		// array of IO buffers
	msg.msg_iovlen	= 1;		// number of elements in array
	msg.msg_name	= NULL;
	msg.msg_namelen	= 0;

	if (fd_to_send < 0) {
		msg.msg_control 	= NULL;
		msg.msg_controllen	= 0;
		buf[0] = 1;
	} else {
		// cmsghdr 包含了要传递的信息
		if ((cmptr = malloc(CONTROLLEN)) == NULL) {
			return -1;
		}
		cmptr->cmsg_level = SOL_SOCKET;
		cmptr->cmsg_type  = SCM_RIGHTS;
		cmptr->cmsg_len	  = CONTROLLEN;
		msg.msg_control   = cmptr;
		msg.msg_controllen= CONTROLLEN;
		*(int*)CMSG_DATA(cmptr) = fd_to_send;
		buf[0] = 0;
	}
	
	if (sendmsg(fd, &msg, 0) != 1) {
		return -1;
	}
	return 0;
}

int recv_fd(int fd, int *fd_to_recv) {
	int 		nr;
	char		buf[1];
	struct iovec	iov[1];
	struct msghdr	msg;
	struct cmsghdr	*cmptr = NULL;
	
	iov[0].iov_base = buf;
	iov[0].iov_len	= 1;
	msg.msg_iov	= iov;
	msg.msg_iovlen	= 1;
	msg.msg_name	= NULL;
	msg.msg_namelen	= 0;

	if ((cmptr = malloc(CONTROLLEN)) == NULL) {
		return -1;
	}
	msg.msg_control = cmptr;
	msg.msg_controllen = CONTROLLEN;

	if(recvmsg(fd, &msg, 0) < 0) {
		printf("recvmsg error\n");
		return -1;
	}

	if(msg.msg_controllen < CONTROLLEN) {
		printf("recv_fd get invalid fd\n");
		return -1;
	}
	
	*fd_to_recv = *(int*)CMSG_DATA(cmptr);
	return 0;
}

int main() {
	int	fd;
	pid_t	pid;
	int	sockpair[2];
	int	status;
	char	fname[256];

	status = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair);
	if (status < 0) {
		printf("Call socketpair error, errno is %d\n", errno);
		return errno;
	}

	pid = fork();
	if (pid == 0) {
		close(sockpair[1]);
		
		status = recv_fd(sockpair[0], &fd);
		if (status != 0) {
			printf("[CHILD]: recv error, errno is %d\n", status);
			return status;
		}

		status = write(fd, "Yao DengDeng", strlen("Yao Dengdeng"));
		if (status < 0) {
			printf("[CHILD]: write error, errno is %d\n", status);
			return -1;
		} else {
			printf("[CHILD]: append logo successfully\n");
		}
		close(fd);

		exit(0);
	}
	
	printf("[PARENT]: enter the filename:\n");
	scanf("%s", fname);

	fd = open(fname, O_RDWR | O_APPEND);
	if (fd < 0) {
		printf("[PARENT]: open file error, errno is %d\n", errno);
		return -1;
	}

	status = send_fd(sockpair[1], fd);
	if (status != 0) {
		printf("[PARENT]: send_fd error, errno is %d\n", status);
		return -1;
	}
	close(fd);

	wait(NULL);
	return 0;
}

  

[email protected]:~/TEST/c$ touch my.log
[email protected]:~/TEST/c$ gcc -o fdpass main.c
[email protected]:~/TEST/c$ ./fdpass 
[PARENT]: enter the filename:
my.log
[CHILD]: append logo successfully
[email protected]:~/TEST/c$ cat my.log 
Yao DengDeng

  

以上是关于传送文件描述符的简单示例的主要内容,如果未能解决你的问题,请参考以下文章

寻找有关如何使用 socket.io 或 IPC 传递文件描述符的示例

监控文件描述符的六种方式(进程监控selectpoll非阻塞轮询I/O异步I/O线程监控)

处理屏幕旋转上的片段重复(带有示例代码)

为啥这段代码会泄露? (简单的代码片段)

在android中使用带有片段的roboguice的任何简单示例?

在android中显示隐藏片段