歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

進程間傳遞文件描述符

UNIX域套接字可以在同一台主機上各進程之間傳遞文件描述符。

下面先來看兩個函數:

#include <sys/types.h> 
#include <sys/socket.h> 

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); 
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
它們與sendto 和 recvfrom 函數相似,只不過可以傳輸更復雜的數據結構,不僅可以傳輸一般數據,還可以傳輸額外的數據,即文件描述符。下面來看結構體msghdr :

struct msghdr { 
	void         *msg_name;       /* optional address */ 
	socklen_t     msg_namelen;    /* size of address */ 
	struct iovec *msg_iov;        /* scatter/gather array */ 
	size_t        msg_iovlen;     /* # elements in msg_iov */ 
	void         *msg_control;    /* ancillary data, see below */ 
	size_t        msg_controllen; /* ancillary data buffer len */ 
	int           msg_flags;      /* flags on received message */ 
};
1、msg_name :即對等方的地址指針,不關心時設為NULL即可;

2、msg_namelen:地址長度,不關心時設置為0即可;

3、msg_iov:是結構體iovec 的指針。

struct iovec { 
               void  *iov_base;    /* Starting address */ 
               size_t iov_len;     /* Number of bytes to transfer */ 
           };
成員iov_base 可以認為是傳輸正常數據時的buf,iov_len 是buf 的大小。

4、msg_iovlen:當有n個iovec 結構體時,此值為n;

5、msg_control:是一個指向cmsghdr 結構體的指針

struct cmsghdr { 
           socklen_t cmsg_len;    /* data byte count, including header */ 
           int       cmsg_level;  /* originating protocol */ 
           int       cmsg_type;   /* protocol-specific type */ 
           /* followed by unsigned char cmsg_data[]; */ 
       };
6、msg_controllen :cmsghdr 結構體可能不止一個;

7、flags : 一般設置為0即可;

直接上代碼:

client.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define UNIXSTR_PATH "foo.socket"
#define OPEN_FILE  "test"

int main(int argc, char *argv[])
{
	int clifd;
	struct sockaddr_un servaddr;  //IPC
	int ret;
	struct msghdr msg;
	struct iovec iov[1];
	char buf[100];
	union {  //保證cmsghdr和msg_control對齊
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *pcmsg;
	int fd;

	clifd  = socket(AF_UNIX, SOCK_STREAM, 0) ;
	if   ( clifd  <  0 ) {
		printf ( "socket failed.\n" ) ;
		return  - 1 ;
	}

	fd  =  open(OPEN_FILE ,O_CREAT | O_RDWR, 0777);
	if( fd  <  0 ) {
		printf("open test failed.\n");
		return -1;
	}

	bzero (&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_UNIX;
	strcpy ( servaddr.sun_path, UNIXSTR_PATH);

	ret = connect(clifd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if(ret < 0) {
		printf ( "connect failed.\n" ) ;
		return 0;
	}
	//udp需要,tcp無視
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	iov[0].iov_base = buf;
	iov[0].iov_len = 100;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	//設置緩沖區和長度
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);
	//直接通過CMSG_FIRSTHDR取得附屬數據
	pcmsg = CMSG_FIRSTHDR(&msg);
	pcmsg->cmsg_len = CMSG_LEN(sizeof(int));
	pcmsg->cmsg_level = SOL_SOCKET;
	pcmsg->cmsg_type = SCM_RIGHTS;  //指明發送的是描述符
	*((int*)CMSG_DATA(pcmsg)) == fd;  //把描述符寫入輔助數據

	ret = sendmsg(clifd, &msg, 0);  //send filedescriptor
	printf ("ret = %d, filedescriptor = %d\n", ret, fd);
	return 0 ;
}
server.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define UNIXSTR_PATH "foo.socket"

int main(int argc, char *argv[])
{
	int clifd, listenfd;
	struct sockaddr_un servaddr, cliaddr;
	int ret;
	socklen_t clilen;
	struct msghdr msg;
	struct iovec iov[1];
	char buf [100];
	char  *testmsg = "test msg.\n";

	union {  //對齊
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	}  control_un;
	struct cmsghdr  * pcmsg;
	int  recvfd;

	listenfd  =  socket ( AF_UNIX ,  SOCK_STREAM ,  0 ) ;
	if(listenfd < 0) {
		printf ( "socket failed.\n" ) ;
		return  -1;
	}

	unlink(UNIXSTR_PATH) ;

	bzero (&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_UNIX;
	strcpy ( servaddr.sun_path ,  UNIXSTR_PATH ) ;

	ret  =  bind ( listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if(ret < 0) {
		printf ( "bind failed. errno = %d.\n" ,  errno ) ;
		close(listenfd);
		return  - 1 ;
	}

	listen(listenfd, 5);

	while(1) {
		clilen = sizeof( cliaddr );
		clifd = accept( listenfd, (struct sockaddr*)&cliaddr , &clilen);
		if ( clifd < 0 ) {
			printf ( "accept failed.\n" ) ;
			continue ;
		}

		msg.msg_name  = NULL;
		msg.msg_namelen  = 0;
		//設置數據緩沖區
		iov[0].iov_base = buf;
		iov[0].iov_len = 100;
		msg.msg_iov = iov;
		msg.msg_iovlen = 1;
		//設置輔助數據緩沖區和長度
		msg.msg_control = control_un.control;
		msg.msg_controllen  =  sizeof(control_un.control) ;
		//接收
		ret = recvmsg(clifd , &msg, 0);
		if( ret <= 0 ) {
			return ret;
		}
		//檢查是否收到了輔助數據,以及長度
		if((pcmsg = CMSG_FIRSTHDR(&msg) ) != NULL && ( pcmsg->cmsg_len == CMSG_LEN(sizeof(int)))) {
			if ( pcmsg->cmsg_level  !=  SOL_SOCKET ) {
				printf("cmsg_leval is not SOL_SOCKET\n");
				continue;
			}

			if ( pcmsg->cmsg_type != SCM_RIGHTS ) {
				printf ( "cmsg_type is not SCM_RIGHTS" );
				continue;
			}
			//這就是我們接收的描述符
			recvfd = *((int*)CMSG_DATA(pcmsg));
			printf ( "recv fd = %d\n", recvfd );

			write ( recvfd, testmsg, strlen(testmsg) + 1);
		}
	}

	return 0 ;
}

進程間傳輸文件描述符的fd是不一定相同的,在client裡面是4,到達server的時候4已經被占用了,所以分配了5給它。

Copyright © Linux教程網 All Rights Reserved