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

传送进程描述符,简单的来说,就是进程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

  

时间: 2024-08-19 11:17:48

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

通过UNIX域套接字传递文件描述符

传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程序设计范式 ??开发一个服务器程序,有较多的的程序设计范式可供选择,不同范式有其自身的特点和实用范围,明了不同范式的特性有助于我们服务器程序的开发.常见的TCP服务器程序设计范式有以下几种: 迭代服务器 并发服务器,每个客户请求fork一个子进程 预先派生子进程,每个子进程无保护地调用accept 预先

五、基于文件描述符的文件操作(非缓冲)

1文件描述符 内核为每个进程维护一个已打开文件的记录表,文件描述符是一个较小的正整数(0—1023),它代表记录表的一项,通过文件描述符和一组基于文件描述符的文件操作函数,就可以实现对文件的读.写.创建.删除等操作. 常用基于文件描述符的函数有open(打开).creat(创建).close(关闭).read(读取).write(写入).ftruncate(改变文件大小).lseek(定位).fsync(同步).fstat(获取文件状态).fchmod(权限).flock(加锁).fcntl(控

IO重定向与文件描述符

1.介绍 IO重定向用于捕捉一个文件,命令,程序,脚本或者代码块的输出,然后把捕捉到的输出作为输入发送给另外一个文件,命令,程序或脚本. 终端程序一般从单一源以流的形式聚集输入和显示输出,script执行时(进程), 系统会默认开启3个标准文件,stdin, stdout,stderr . script默认会由stdin读取数据,默认指键盘,由stdout输出执行结果,默认指屏幕; 若有错误发生,则由stderr显示信息,默认也指向屏幕.系统开启这3个文件时,以文件代码(分别为0,1,2)作为连

[转] linux系统文件流、文件描述符与进程间关系详解

http://blog.sina.com.cn/s/blog_67b74aea01018ycx.html linux(unix)进程与文件的关系错综复杂,本教程试图详细的阐述这个问题. 包括: 1.linux多/单进程与多/单文件对于文件流和描述符在使用时的关联情况及一些需要注意的问题. 2.fork,vfork流缓冲等对文件操作的影响. 1.linux文件系统结构 首先补充一点基础知识,了解一下linux文件系统.如下图所示: 图1 磁盘,分区和文件系统 应该明白,上图所示结构是硬盘中文件存放

Linux 套接字与文件描述符

端口和套接字,用于确定指定主机上的哪个本地进程使用了哪个协议和哪台远程主机上的哪个进程进行了通信.端口和套接字的使用可以基于以下几点: ①为每个应用过程分配一个过程标识符(Process ID),每次启动一个进程时,这个ID都可能是不同的. ②进程ID因操作系统平台不同而不同,因而它们是不统一的. ③一个服务器过程能够同时与多个客户连接,因而简单的连接标识符不可能是唯一的. 端口和套接字概念提供了一种以统一的方式唯一地标识连接以及参与连接的程序和主机的方法,而不管特定的过程ID. (1)端口 

Linux中文件描述符fd和文件指针flip的理解

转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp. open:文件描述符的操作(如: open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用(fd1,fd2,fd3...); fopen:而流(如: f

关于Linux文件描述符的笔记

当某个程序打开文件时,操作系统返回相应的文件描述符,程序为了处理该文件必须引用此描述符.所谓的文件描述符是一个低级的正整数.最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应.因此,函数 scanf() 使用 stdin,而函数 printf() 使用 stdout.你可以用不同的文件描述符改写默认的设置并重定向进程的 I/O 到不同的文件. 首先说什么是文件描述符,它有什么作用? 文件描述符是一个简单的整数,用以标明每一个被

unix文件描述符——socket

在unix系统中,socket和普通文件一样对待,因为它可以像普通文件一样被读和写,但是它还有一些自己独特的特点,例如,文件的读写位置可以设置,但是socket只能被顺序的读写等等,那么在unix系统中,是如何实现这种方式的呢? 如下图,其中有以下重要数据结构:proc.filedesc.file等,对这些重要数据结构及其之间的关系弄清楚之后,上面的问题自然就有答案了.在本文介绍中,使用的操作系统源码为:4.4bsd-lite版本,该版本是<TCP/IP协议卷2--实现>一书使用的源码,同时该

[Android]文件描述符透过Binder传输的原理

在Linux中,文件描述符都是属于进程的,用整数来表示.通过fork,虽然子进程和父进程都是打开同样的文件,但文件描述符却是不同的. 同样的文件描述符值在不同进程对应不同的文件描述符值数组. 所以文件描述符透过Binder来进行传输时,不能是简单的拷贝文件描述符值. 关键是要把对应的文件结构与一个新的文件描述符对应起来,这样另一个进程和原来的进程透过不同的文件描述符对应同一个文件. 幸好,打开文件的结构struct file是可以在进程间共享的,透过进程a的文件描述符来获取struct file