msghdr结构通过UNIX域套接字实现文件描述符的本地传递

        本文介绍msghdr结构体借助UNIX域套接字和sendmsg()函数和recvmsg()函数来传递文件描述符,sendmsg()函数和recvfmsg()函数还可以传递其他控制信息,这里不作介绍。
函数声明:
ssize_t sendmsg(int sockfd,const struct msghdr *msg,int flags);
ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags);

struct msghdr {
    void                *msg_name;         /* optional address */
    socklen_t        msg_namelen;      /* address size in bytes */
    struct iovec    *msg_iov;              /* array of I/O buffers */
    int                  msg_iovlen;          /* number of elements in array */
    void                *msg_control;       /* ancillary data */
    socklen_t        msg_controllen;    /* number of ancillary bytes */
    int                  msg_flags;            /* flags for received message */
};

struct iovec {
    void *iov_base;       /* Starting address */
    size_t iov_len;         /* Number of bytes */
};

msghdr结构第五个成员void *msg_contrl指向这样一个结构体:cmsghdr:
struct cmsghdr {
    socklen_t        cmsg_len;      /* data byte count, including header */
    int                  cmsg_level;    /* originating protocol */
    int                  cmsg_type;    /* protocol-specific type */
    /* unsigned char *newfd;    要发送的fd数据保存在这里*/

};

  //注意区分cmsghdr和msghddr

        需要注意的是,为了发送文件描述符,将cmsg_len设置为cmsghdr结构的长度加一个整形(描述符)的长度,cmsg_level字段设置为SOL_SOCKET,cmsg_type字段设置为SCM_RIGHTS,用以指明我们在传送访问权限(SCM指的是套接字级控制消息,socket_level control message),访问权限能通过UNIX套接字传送,描述符紧随cmsg_type字段之后存放,用CMSG_DATA宏获得该整形变量的指针。
        三个宏用于访问控制数据,一个宏用于帮助计算cmsg_len所使用的值。
  #include<sys/socket.h>
  unsigned char *CMSG_DATA(struct cmsghdr *cp);
  返回值:指向与cmsghdr结构体相关联的数据的指针
  struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
  返回值:指向与msghdr结构体相关联的第一个cmsghdr结构的指针,若无这样的结构则返回NULL
  struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp,struct cmsghdr *cp);
  返回值:指向与msghdr结构相关联的下一个cmsghdr结构的指针,
       该msghdr结构给出了当前cmsghdr结构,若当前cmsghdr结构已是最后一个则返回NULL
  unsigned int CMSG_LEN(unsigned int nbytes);

  返回值:为nbytes大小的数据对象分配的长度


发送描述符端的使用示例:

struct cmsghdr *cmsg;//描述符控制信息cmsg

//申请cmsghdr空间,大小为cmsghdr + fd大小
cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd_to_send));

//设置cmsg结构的cmsg_len、cmsg_level、cmsg_type成员 和额外的那个空间,额外的那个fd大小的空间保存着待发送的fd。
cmsg->cmsg_len   = sizeof(struct cmsghdr) + sizeof(fd_to_send);//控制消息长度
cmsg->cmsg_level = SOL_SOCKET;//控制消息级别
cmsg->cmsg_type = SCM_RIGHTS;//控制消息类型

//把待发送的fd_to_send保存到cmsg结构的fd空间中,重要的是CMSG_DATA宏能直接返回cmsg结构中那个额外数据的指针。
memcpy(CMSG_DATA(cmsg),&fd_to_send,sizeof(fd_to_send));

//设置msg结构的msg_control、msg_controllen、msg_name、msg_namelen、msg_iov、msg_iovlen成员
//其中msg_control、msg_controllen成员与发送控制命令或文件描述相关,msg_iov、msg_iovlen与发送普通消息相关。

struct msghdr msg; //整个消息
msg.msg_control = cmsg;//初始化控制消息
msg.msg_controllen = cmsg->cmsg_len;

msg.msg_name = NULL;
msg.msg_namelen = 0;
struct iovec iov[3]; //普通消息空间,每个普通消息包含iov_base和iov_len。
iov[0].iov_base = "hello,"; //普通消息1
iov[0].iov_len = strlen("hello");
iov[1].iov_base = "this is yangzd,";    //普通消息2
iov[1].iov_len = strlen("this is yangzd,");
iov[2].iov_base = "and you?";    //普通消息2
iov[2].iov_len = strlen("and you?");

msg.msg_iov = iov;
msg.msg_iovlen = 3;

//发送msg结构体给客户端,msg结构已经包含了要发送的文件描述符和普通消息内容。文件描述符存于msg结构的
//msg_control成员中,普通消息存于msg结构的msg_iov成员中。

sendmsg(sock_fd,&msg,0) < 0)

接收进程也做相应的设置:

struct cmsghdr *cmsg;
cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd_to_recv));//分配控制消息存储空间
cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd_to_recv);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;

struct msghdr msg;
msg.msg_control = cmsg;
msg.msg_controllen = cmsg->cmsg_len;
msg.msg_name = NULL;
msg.msg_namelen = 0;

struct iovec iov[3]; //普通消息接收空间
iov[0].iov_base = buf;
iov[0].iov_len = len;

msg.msg_iov = iov;
msg.msg_iovlen = 3;

//接收消息,从服务器端获得的msg结构体的信息保存在recvmsg()函数的第二个参数msg中。
recvmsg(sock_fd,&msg,0)

//提取文件描述符,后面就可以直接读写fd_to_recv了

memcpy(fd_to_recv,CMSG_DATA(cmsg),sizeof(fd_to_recv));

        需要注意的是,传送文件描述符,实际上是发送进程向接收进程传送一个指向一个打开文件表项的指针,该指针被分配存放在接收进程的第一个可用描述符表项中,但发送进程和接收进程中的描述符编号通常是不同的。两个进程共享同一个文件表项,共享读写位置,和父子进程共享打开的文件表项情况一样。
        当发送进程将描述符传送给接收进程后,通常它关闭该描述符,发送进程关闭该文件描述符并不造成关闭该文件或设备,其原因是该描述符对应的文件仍被视为由接收进程打开(即使接收进程尚未接收到该描述符)。

        另外,有关socket方面,和UNIX域套接字编程是一样的,其socket地址是sockaddr_un结构。

完整的例子代码:

服务器端代码:

send_fd_server.c:

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

#define SOCK_FILE "chenxh"	//本地通信时Socket文件的文件名
int create_local_sock(char *sockfile)//次函数用于创建本地Socket
{
	int local_fd;
	struct sockaddr_un myaddr;
	if(-1 == (local_fd = socket(AF_LOCAL,SOCK_STREAM,0)))//创建Socket
	{
		perror("socket");
		exit(EXIT_FAILURE);	
	}	
	
	bzero(&myaddr,sizeof(myaddr));
	myaddr.sun_family = AF_LOCAL;//本地Socket
	strncpy(myaddr.sun_path,sockfile,strlen(sockfile));
	if(-1 == bind(local_fd,(struct sockaddr *)&myaddr,sizeof(myaddr)))//绑定
	{
		perror("bind");
		exit(EXIT_FAILURE);	
	}
	
	if(-1 == listen(local_fd,5))//监听
	{
		perror("listen");
		exit(EXIT_FAILURE);	
	}
	
	int new_fd;
	struct sockaddr_un peeraddr;
	int len = sizeof(peeraddr);
	new_fd = accept(local_fd,(struct sockaddr *)&peeraddr,&len);//阻塞等待
	
	if (-1 == new_fd)
	{
		perror("accept");
		exit(EXIT_FAILURE);	
	}
	return new_fd;
}

send_fd(int sock_fd,char *file)	//发送文件描述符,期望对方写此文件描述符
{
	int fd_to_send;
	if(-1 == (fd_to_send = open(file,O_RDWR|O_APPEND)))//打开文件
	{
		perror("open");
		exit(EXIT_FAILURE);	
	}
	struct cmsghdr *cmsg;//描述符控制信息
	
	cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd_to_send));//申请控制空间
	cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd_to_send);//控制消息长度
	cmsg->cmsg_level = SOL_SOCKET;//控制消息级别
	cmsg->cmsg_type = SCM_RIGHTS;//控制消息类型
	memcpy(CMSG_DATA(cmsg),&fd_to_send,sizeof(fd_to_send));//控制消息内容
	
	struct msghdr msg;	//整个消息
	msg.msg_control = cmsg;//初始化控制消息
	msg.msg_controllen = cmsg->cmsg_len;
	
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	struct iovec iov[3];	//普通消息空间
	
	iov[0].iov_base = "hello,";	//普通消息1
	iov[0].iov_len = strlen("hello,");
	iov[1].iov_base = "this is chenxh,";//普通消息2
	iov[1].iov_len = strlen("this is chenxh,");
	iov[2].iov_base = "and you?";//普通消息3
	iov[2].iov_len = strlen("and you?");
	
	msg.msg_iov = iov;
	msg.msg_iovlen = 3;
	
	if(sendmsg(sock_fd,&msg,0) < 0)	//将消息一起发送给对方
	{
		printf("sendmsg error,errno is %d\n",errno);
		fprintf(stderr,"sendmsg failed.errno:%s\n",strerror(errno));
		return errno;	
	}
	return 1;
	
}

int main(int argc,char *argv[])
{
	int sock_fd = 0;
	unlink(SOCK_FILE);
	if(argc != 2)	//argv[1]为期望对方内容的文件
	{
		printf("pls usage %s file_send\n",argv[0]);
		exit(EXIT_FAILURE);	
	}	
	sock_fd = create_local_sock(SOCK_FILE);	//创建socket
	if(send_fd(sock_fd,argv[1]) != 1)
	{
		printf("send error");
		exit(EXIT_FAILURE);	
	}
}
客户端代码:

send_fd_client.c:

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


#define SOCK_FILE "chenxh"	//通信的socket文件
int create_local_sock(char *sockfile)//创建socket
{
	int local_fd = 0;
	struct sockaddr_un serveraddr;
	if(-1 == (local_fd=socket(AF_LOCAL,SOCK_STREAM,0)))//TCP方式
	{
		perror("socket");
		exit(EXIT_FAILURE);	
	}	
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sun_family = AF_LOCAL;
	strncpy(serveraddr.sun_path,sockfile,strlen(sockfile));
	
	if(-1 == connect(local_fd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)))
	{
		perror("connect");
		exit(EXIT_FAILURE);	
	}
	return local_fd;
}

static int recv_fd(int sock_fd,int *fd_to_recv,char *buf,int len)//接收消息
{
	struct cmsghdr *cmsg;
	cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd_to_recv));//分配控制消息存储空间
	cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd_to_recv);
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	
	struct msghdr msg;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsg->cmsg_len;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	
	struct iovec iov[3];	//普通消息接收空间
	iov[0].iov_base = buf;
	iov[0].iov_len = len;
	
	msg.msg_iov = iov;
	msg.msg_iovlen = 3;
	if(recvmsg(sock_fd,&msg,0) < 0)	//接收消息,从服务器端获得的msg结构体的信息保存在recvmsg()函数的第二个参数msg中。
	{
		printf("recvmsg error,errno is %d\n",errno);
		fprintf(stderr,"recvmsg failed.errno:%s\n",strerror(errno));
		return errno;
	}	
	memcpy(fd_to_recv,CMSG_DATA(cmsg),sizeof(fd_to_recv));	//获取文件描述符
	if(msg.msg_controllen != cmsg->cmsg_len)
	{
		*fd_to_recv = -1;
			
	}
	return 0;
}

int main(int argc,char *argv[])
{
	int sock_fd = 0;
	int file_fd;
	char *ptr = "now write data to file \n"	;	//写入到文件的内容
	char buf[129];
	memset(buf,'\0',128);
	sock_fd = create_local_sock(SOCK_FILE);		//创建socket
	recv_fd(sock_fd,&file_fd,buf,128);	//接收消息
	write(file_fd,ptr,strlen(ptr));		//写内容到对方发送过来的fd对应文件
	printf("recv message:%s\n",buf);	//接收到的消息,这个普通消息为什么全部保存在buf?
	unlink(SOCK_FILE);
}

运行结果(待打开及传送描述符的文件为haha):

book@book-desktop:~/workspace/zongde/chapter17$ cat haha
haha,
book@book-desktop:~/workspace/zongde/chapter17$ ./send_fd_server haha


book@book-desktop:~/workspace/zongde/chapter17$ ./send_fd_client 
recv message:hello,this is chenxh,and you?
book@book-desktop:~/workspace/zongde/chapter17$ cat haha
haha,
now write data to file 
book@book-desktop:~/workspace/zongde/chapter17$ 

猜你喜欢

转载自blog.csdn.net/qq_22863733/article/details/80636757