打开一个文件或者一个套接口,如果想把这个已经打开的文件和套接口的描述字传给其他的进程(仅限于本机),可以通过发送unix域的辅助数据来完成。当然如果这2个进程是父子进程,则问题要简单很多,因为子进程继承了父进程打开的描述字。下面的问题是:如何在2个没有关系的进程之间传递描述字。
辅助数据又叫做控制信息,在之前介绍sendmsg和recvmsg中,对于数据结构struct msghd中有个字段void * msg_control;没有介绍。这就是辅助数据存储地方以及socklen_t msg_controllen;辅助数据的长度。基于字面意思也就是它可以用来传递一些比较特殊的信息。
辅助数据的结构:
struct cmsghdr {
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
/* followed by unsigned char cmsg_data[] */
}
如果是IPV4协议,则cmsg_level为IPPROTO_IP. cmsg_type值可以为:IP_RECVDSTADDR(接受UDP数据报的目的地址) 或者是IP_RECVIF (接受UDP数据报的接口索引)。
如果为IPV6协议,cmsg_level为IPPROTO_IPV6. cmsg_type值可以为:
IPV6_DSTOPTS(指定/接收目标选项)
IPV6_HOPLIMIT(指定/接收跳限)
IPV6_HOPOPTS(指定/接收步跳选项)
IPV6_NEXTHOP(指定下一跳地址)
IPV6_PKTINFO(指定/接收分组信息)
IPV6_RTHDR(指定/接收路由头部)
如果UNIX域协议,cmsg_level为SOL_SOCKET。cmsg_type值可以为:
SCM_RIGHTS(发送/接收描述字)
SCM_CREDS(发送/接收用户凭证)。
辅助数据有一个或者多个辅助数据对象组成,每个对象由cmsghdr开头,其后跟了数据,在cmsg_type和实际数据之间可能有填充字节,一个数据对象和下一个对象之间也可能有填充字节。之所以要有填充字节是为了使整个数据对象按照cmsg_type结构对齐。因此为了方便操作就定义了以下几种宏操作:
CMSG_FIRSTHDR(struct msghdr * msg);//返回第一个cmsghdr结构指针
CMSG_NXTHDR(struct msghdr * msg, struct cmsghdr * cmsgptr);//指向cmsgptr的下一个cmsghdr指针
CMSG_DATA(struct cmsghdr * cmsgptr);//指向cmsgptr相关联的数据的第一个字节指针
CMSG_LEN(unsigned int length); //给定数据量下,存储在cmsg_len中的位置
CMSG_SPACE(unsigned int length); //给定数据量下,一个辅助数据对象的大小
相对比较抽象,通过代码及注释来看下:
代码实现的是同机器上2个进程间通过unix域传递打开的文件描述字。
#include "/programe/net/head.h"
#include "sys/un.h"
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "string.h"
//打开监听套接字,等待从另一个进程传递过来打开的文件描述字信息
int main(int argc, char ** argv) {
char buf[100];
int sockfd;
struct sockaddr_un addr;
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);
unlink(argv[1]);
int flag = bind(sockfd, (struct sockaddr *)&addr, SUN_LEN(&addr));//unix域,之前见过
if(flag == -1) {
printf("bind error\n");
exit(1);
}
listen(sockfd, 10);
int connfd = accept(sockfd, NULL, NULL);
struct msghdr msg;
struct iovec io;
msg.msg_name = NULL;
msg.msg_namelen = 0;
io.iov_base = buf;
io.iov_len = 100;
msg.msg_iov = &io;
msg.msg_iovlen = 1; //msg普通数据的定义,在sendmsg与recvmsg里面见过.
union {
struct cmsghdr cmsg;
char control[CMSG_SPACE(sizeof(int))];
} control_un; //为了使整个数据结构对齐,所以定义了union对象,当然也可以使用malloc
struct cmsghdr * cmptr;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control); //因为只有一个数据对象传送
ssize_t size = recvmsg(connfd, &msg, 0);
buf[size] = '\0';
printf("get message:%s\n", buf);
cmptr = CMSG_FIRSTHDR(&msg);//在接受到返回结果后,得到第一个控制信息对象
if(!cmptr) {
printf("some thing error!!!\n");
} else {
int recvfd = *((int *)CMSG_DATA(cmptr));//获取文件描述字信息
char my[100];
int length = read(recvfd, my, 100);
my[length] = '\0';
printf("%s\n", my);
close(recvfd);
}
close(connfd);
close(sockfd);
exit(0);
}
#include "stdio.h"
#include "stdlib.h"
#include "/programe/net/head.h"
#include "string.h"
#include "sys/un.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
//打开一个文件,并将描述字传递给另一个进程
int main(int argc, char ** argv) {
int sockfd;
char buf[] = "hello world";
int file = open("/programe/net/temp.txt", O_RDONLY);//打开文件,file就是即将要传输的描述字
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
struct sockaddr_un addr;
bzero(&addr, sizeof(addr));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);
connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
struct msghdr msg;
struct iovec io;
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
} control_un; //同上
struct cmsghdr * cmptr;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;//说明要传递的是描述字
*((int *)CMSG_DATA(cmptr)) = file;//将描述字放入传输对象中
msg.msg_name = NULL;
msg.msg_namelen = 0;
io.iov_base = buf;
io.iov_len = sizeof(buf);
msg.msg_iov = &io;
msg.msg_iovlen = 1;
ssize_t flag = sendmsg(sockfd, &msg, 0);
printf("flag = %d\n", flag);
close(sockfd);
exit(0);
}