UNIX 域协议使用! 在进程间传递“文件描述符” 实例



你需要知道的:


注意:首先你要有一个测试文件存在!( 因为下面要打开一个文件,然后传递它的描述符 )

1 >:

此程序的整体结构是:

进程1中创建一个socketpair用于父子进程通信
            |
进程1中创建一个子进程
            |
子进程中使用execl启动另一个进程来打开此文件并将fd通过参数传入的socket传回子进程
注意:父进程在这个时候的动作是waitpid而已
            |
那么由于父子进程是的socket端口是全双工的,所以一旦子进程那里有信息就会发送过来
            |
那么父进程就可以在一个端口上获得信息,然后再在自己的环境下打开文件,写到stdout

请注意:其中最重要的就是怎么使用struct msghdr来封装数据传递!!!


2 >:

关于msghdr结构体:
struct msghdr
{
    void                 *msg_name;                //!> 对方主机的地址
    socklen_t        msg_namelen;            //!> 长度
    
    struct iovec     *msg_iov;                    //!> io vector 的内容
    size_t               msg_iovlen;                //!> 个数
    
    void                  *msg_control;            //!> 控制信息
    size_t               msg_controllen;            //!> 控制信息长度
    
    int                      msg_flags;                    //!> 标志
};

分成4组,具体的信息网上到处都是,此处不多说哦~

3 >:

关于:struct cmsghdr
>: 我们在接收msghdr的时候需要这样一个结构体的指针来遍历整个CMSG_DATA的内容!
>: 在发送的时候需要使用这个写入头信息:pCmsghdr = CMSG_FIRSTHD(&msghdr_send);
    pCmsghdr = CMSG_FIRSTHDR( &msghdr_send );    //!> the info of head
    pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int));       //!> the msg len
    pCmsghdr->cmsg_level = SOL_SOCKET;             //!> -> stream mode               
    pCmsghdr->cmsg_type = SCM_RIGHTS;              //!> -> file descriptor
    *((int *)CMSG_DATA( pCmsghdr )) = file_fd;            //!> data: the file fd

>: cmsg_len与CMSG_LEN()宏值所显示的长度相同。
   cmsg_level        这个值表明了原始的协议级别(例如,SOL_SOCKET)。
   cmsg_type        这个值表明了控制信息类型(例如,SCM_RIGHTS代表文件描述符)。
                       SCM_RIGHTS        附属数据对象是一个文件描述符
                    SCM_CREDENTIALS        附属数据对象是一个包含证书信息的结构

4 >:

关于一些宏:

>: CMSG_LEN:计算cmsghdr头结构加上所需要的填充字符的字节长度。
                     这个值用来设置cmsghdr对象的cmsg_len成员
                     上面的意思也就是说:输入的CMSG_DATA是什么类型的,对应的长度!
                     例如此处是fd,那么 就是CSMG_LEN(sizeof( int ))!!!
                    
>: CMSG_SPACE:计算附属数据以及其头部所需的总空白。尽管CMSG_LEN()宏计算了一
                       个相似的长度,CMSG_LEN()值并不包括可能的结尾的填充字符。
                        CMSG_SPACE(sizeof fd)
                        此宏调用来得到所需的总空间

>: CMSG_DATA:接受一个指向cmsghdr结构的指针。返回的指针值指向跟随在头部以及填
                      充字节之后的附属数据的第一个字节(如果存在)。
                      fd = *(int *)CMSG_DATA(ptr);
                      
>: CMSG_FIRSTHDR:返回一个指向附属数据缓冲区内的第一个附属对象的struct cmsghdr指
                           针。输入值为是指向struct msghdr结构的指针(不要与structcmsghdr相
                           混淆)。
                           这个宏会估计msghdr的成员msg_control与msg_controllen来确定在缓
                           冲区中是否存在附属对象。然后,他会计算返回的指针。如果不存在
                               附属数据对象则返回的指针值为NULL。否则,这个指针会指向存在
                               的第一个struct cmsghdr。这个宏用在一个for循环的开始处,来开始
                               在附属数据对象中遍历。
                           
>: CMSG_NXTHDR:返回下一个附属数据对象的struct cmsghdr指针。这个宏会接受两个输
                         入参数:指向struct msghdr结构的指针指向当前struct cmsghdr的指针
                         如果没有下一个附属数据对象,这个宏就会返回NULL。遍历附属数据
                         当接收到一个附属数据时,我们可以使用CMSG_FIRSTHDR()与
                         CMSG_NXTHDR()宏来在附属数据对象中进行遍历。

5 >:

注意的是:此处是采用socketpair来创建两个socket来处理的,本是是采用的“全双工”模式而已~


6 >:

注意: WIFEXITED, WEXITSTATUS

WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
( 当然status是由waitpid得到的!waitpid( pid, &status, 0 ) )

WEXITSTATUS:
解释:
    WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),    
    WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。


第一个进程代码:

/*
	这个进程需要做的就是创建一个子进程,然后
	在子进程中尝试打开一个文件,注意是调用另
	外一个进程处理,然后另外的进程将文件描述符
	返回给此进程,再有此进程的处理!
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define 	BUFLINE		1024

//!> when we recv from the socket,we should give the same data struct
int recv_file_fd( int fd, void * data, size_t len, int * recv_fd )
{
	struct msghdr 	msghdr_recv;		//!> the info struct
	struct iovec	iov[1];	                //!> io vector
	size_t		n;	                //!>
	
	union
	{
		struct cmsghdr	cm;			//!> control msg
		char 	ctl[CMSG_SPACE(sizeof( int ))];	//!> the pointer of char
	}ctl_un;
	
	struct cmsghdr * pCmsghdr = NULL;		//!> the pointer of control
	
	msghdr_recv.msg_control = ctl_un.ctl;	
	msghdr_recv.msg_controllen = sizeof( ctl_un.ctl );
	
	//!> these infos are nosignification
	msghdr_recv.msg_name = NULL;			//!> the name	
	msghdr_recv.msg_namelen = 0;		        //!> len of name
	
	iov[0].iov_base = data;			        //!> no data here
	iov[0].iov_len = len;				//!> the len of data
	
	msghdr_recv.msg_iov = iov;			//!> the io/vector info
	msghdr_recv.msg_iovlen = 1;			//!> the num of iov
	
	if( ( n = recvmsg( fd, &msghdr_recv, 0 ) ) < 0 )//!> recv msg
	{						//!> the msg is recv by msghdr_recv
		printf("recv error : %d\n", errno);
		exit(EXIT_FAILURE);
	}
	
	//!> now, we not use 'for' just because only one test_data_ 
	if(     ( pCmsghdr = CMSG_FIRSTHDR( &msghdr_recv ) ) != NULL 	//!> now we need only one,
	  && pCmsghdr->cmsg_len == CMSG_LEN( sizeof( int ) )		//!> we should use 'for' when 
	 )																				//!> there are many fds
	 {
	 	if( pCmsghdr->cmsg_level != SOL_SOCKET )
	 	{
	 		printf("Ctl level should be SOL_SOCKET\n");
	 		exit(EXIT_FAILURE);
	 	}
	 	
	 	if( pCmsghdr->cmsg_type != SCM_RIGHTS )
	 	{
	 		printf("Ctl type should be SCM_RIGHTS\n");
	 		exit(EXIT_FAILURE);
	 	}
	 	
	 	*recv_fd =*((int*)CMSG_DATA(pCmsghdr));	//!> get the data : the file des* 
	 }
	 else
	 {
	 	*recv_fd = -1;
	 }
	 
	 return n;
	
}

int open_file( char * path_name, int mode )
{
	int 		fd;
	int		sockfd[2];		//!> for sockpair()
	int 		status;			//!> the status of child process
	char 	c;
	char	arg_fd[10];			//> fd arg
	char	arg_mode[10]; 	//> mode arg
	pid_t	chi_pid;
	
	//!> create the socketpair
	if( socketpair( AF_LOCAL, SOCK_STREAM, 0, sockfd ) == -1 )
	{
		printf( "create socketpair error : %d\n", errno );
		exit( EXIT_FAILURE );
	}
	
	//!> the child process
	if( ( chi_pid = fork() ) == 0 )
	{
		close( sockfd[0] );		//!> child use the #1 port; so , close the #0
		
		snprintf( arg_fd, sizeof( arg_fd ), "%d", sockfd[1] );
		snprintf( arg_mode, sizeof( arg_mode ), "%d", mode );

		//!> call the new process
		execl( "./second", "second", arg_fd, path_name, arg_mode, (char*)NULL );
		printf("execl the second process error : %d\n", errno);	
		//!> you know : the usge of execl
		exit( EXIT_FAILURE );
	}
	
	//!> parent :
	
	close( sockfd[1] );				//!> yes
	
	waitpid( chi_pid, &status, 0 );			//!> wait the child process

	if( WIFEXITED( status ) != 0 )			//!> we should know the usge of WIFEXITED
	{
		if( ( status = WEXITSTATUS( status ) ) == 0 )	//!> end
		{
			recv_file_fd( sockfd[0], &c, 1, &fd );	//!> recv from sockfd[0]
		}
		else
		{
			errno = status;
			fd = -1;
		}		
		close( sockfd[0] );
		return fd;
	}
	
	return -1;

}

int main( int argc, char ** argv )
{
	int 		fd;		//!> get the file_descriptor
	int 		n_read;
	char	buf[BUFLINE];

	if( argc != 2 )			//!> input the file path
	{
		printf("Input : file path\n");
		exit( EXIT_FAILURE );
	}
	
	if( ( fd = open_file( argv[1], O_RDONLY ) ) < 0 ) //!> fork a child
	{						  //!> then: child -> execl a new process
		printf("Open file error : %d\n", errno);  //!>        parent -> wair and recv the fd from child
		exit( EXIT_FAILURE );
	}
	
	printf("Get the new fd == %d\n", fd);
	
	while( ( n_read = read( fd, buf, BUFLINE ) ) > 0 ) //!> read from fd
	{
		write( 1, buf, n_read );		   //!> write to std_out
	}
	
	return 0;
}




第二个个进程代码:

/*
	在这个文件中我们需要做的是:将打开的文件描述符,
	这个文件描述符是第二个进程进行fork的子进程中init的,
	所以也就是相当于在第二个进程的子进程中执行此程序,
	此程序传递打开的文件描述符再给第二个进程!
	这里其实是复杂化了程序,就是故意构造这样的一个状态!
	呵呵!~
	( 注意不仅仅是文件描述符,可以是所有的描述符 )
	例如open()、pipe()、mkfifo()、socket()或者accept() 都可以
	
	注意:此进程的是由第二个进程进行调用的!
	use UDP 
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>

int send_back( int fd_send_to, void * data, size_t len, int file_fd )
{
	struct msghdr 	msghdr_send;		//!> the info struct
	struct iovec		iov[1];		//!> io vector
	size_t				n;	//!>
	
	union
	{
		struct cmsghdr	cm;			//!> control msg
		char 	ctl[CMSG_SPACE(sizeof( int ))];	//!> the pointer of char
	}ctl_un;
	
	struct cmsghdr * pCmsghdr = NULL;		//!> the pointer of control
	
	msghdr_send.msg_control = ctl_un.ctl;	
	msghdr_send.msg_controllen = sizeof( ctl_un.ctl );
	
	//!> design : the first info
	pCmsghdr = CMSG_FIRSTHDR( &msghdr_send );	//!> the info of head
	pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int));     //!> the msg len
	pCmsghdr->cmsg_level = SOL_SOCKET; 		//!> -> stream mode           	
	pCmsghdr->cmsg_type = SCM_RIGHTS;  		//!> -> file descriptor
	*((int *)CMSG_DATA( pCmsghdr )) = file_fd;	//!> data: the file fd 
	
	//!> these infos are nosignification
	msghdr_send.msg_name = NULL;			//!> the name	
	msghdr_send.msg_namelen = 0;		        //!> len of name
	
	iov[0].iov_base = data;				//!> no data here
	iov[0].iov_len = len;				//!> the len of data
	
	msghdr_send.msg_iov = iov;			//!> the io/vector info
	msghdr_send.msg_iovlen = 1;			//!> the num of iov
	
	return ( sendmsg( fd_send_to, &msghdr_send, 0 ) );	//!> send msg now
}

int main( int argc, char ** argv )
{
	
	int 		fd;	//!> the fd of the open_file
	ssize_t		ret_n;	//!> the return of n
	
	if( argc != 4 )
	{
		printf("useg:<unix_domain socket> <file_name> <open_mode>\n");
		exit( 0 );
	}
	
	//!> open the file
	if( ( fd = open( argv[2], atoi( argv[3] ) ) ) < 0 )
	{
		printf("open file error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	printf("Open the file, the fd == %d\n", fd);
	
	if( ( ret_n = send_back( atoi( argv[1]) , "", 1, fd ) ) < 0 )	//!> send back by the socket
	{
		printf("send back error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	return 0;
}





在我的机子上输出的结果:

pt@ubuntu:~/桌面$ ./f file


Open the file, the fd == 3
Get the new fd == 4
//!> 注意,下面是我的文件中的内容
sdfvas
dv
asdf
sdfb
sfgn
d
sfynhfdgobfnadlfknjvbasdf
bsdf'bsmd
gfbka
dfk
ag
as
s



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值