进程间传递文件描述符--sendmsg,recvmsg

UNIX域套接字可以在同一台主机上各进程之间传递文件描述符。

下面先来看两个函数:

#include  
#include  

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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给它。


你可能感兴趣的:(网络编程)