Unix 域协议这一章的第一篇文章就是讨论如何在进程间传递描述符,可是后面似乎我们把这件事忘了。其实不然,我们一直在为这件事做铺垫,本文将进一步逼近“真相”。
辅助数据 (Ancillary) ,也叫控制数据,实际上在前面讲解 recvmsg 和 sendmsg 时提过一嘴,当时只是说先放一放。
1. 回忆 msghdr{}
还记得吧,这个结构体是 recvmsg 和 sendmsg 函数的第二个参数类型。
struct msghdr { void *msg_name; socklen_t msg_namelen; struct iovec *msg_iov; size_t msg_iovlen; void *msg_control; // 辅助数据 size_t msg_controllen; // 辅助数据大小 int msg_flags; };
当时我们落下了两个成员 msg_control 和 msg_controllen 没有讲,现在时机到了。要想在进程间传递描述符,使用普通方法直接传递描述符是行不通的,所以辅助数据在这里派上了用场。
2. 辅助数据类型
辅助数据的类型是 cmsghdr{},如下:
struct cmsghdr { socklen_t cmsg_len; int cmsg_level; int cmsg_type; /* followed by unsigned char cmsg_data[]; */ };
如果要想在进程间传递描述符,可通过适当的设置 cmsg_level 和 cmsg_type 的值,并把描述符的值保存在 cmsg_data 中,就可以通过 sendmsg 将描述符发送给其它进程了。
msghdr{} 的成员 msg_control 是一个指针,它指向一个 cmsghdr{} 类型的”数组”。准确的说,它指向第一个辅助数据对象。
图1 msghdr{} 与 cmsghdr{}
2.1 三个长度概念
观察图1 ,一共有 3 个辅助数据对象,每个对象有三部分构成:{cmsghdr, 数据, 填充字节},这里衍生了几个长度概念:
- CMSG_SPACE: 三部分长度的总和。
- CMSG_LEN:cmsghdr 的长度 + 数据长度。
- CMSG_ALIGN:数据长度 + 填充字节长度。(图中没有画)
实际上,这三个长度有着对应的宏,可以计算出来,分别是
size_t CMSG_SPACE(size_t length); size_t CMSG_LEN(size_t length); size_t CMSG_ALIGN(size_t length);
它们的参数,应该传递数据的长度。比如说数据长度是 4 字节,则根据三个宏算出来的 space, len 和 align 长度分别是:CMSG_SPACE(4), CMSG_LEN(4), CMSG_ALIGN(4)
.
2.2 根据 cmsghdr{} 地址获取数据地址
如果知道了 cmsghdr{} 的首地址,就可以取到数据的地址,使用宏 CMSG_DATA 就可以了,它的定义如下:
unsigned char *CMSG_DATA(struct cmsghdr *cmsg);
例如:
char* data = CMSG_DATA(&cm);
2.3 遍历所有 cmsghdr{}
有两个宏可以做到这一点:
// 根据 msghdr{} 获取第一个 cmsghdr{} 地址 struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh); // 根据 msghdr{} 和 cmsghdr{} 获取下一个 cmsghdr{} 地址 struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
例如:
struct msghdr msg; struct cmsghdr *cmptr; /* 填充 msg */ /* 调用 recvmsg */ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NEXTHDR(&msg, cmptr)); { char* data = CMSG_DATA(cmptr); }
3. 实验
程序路径:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/unixdomainprotocols/ancillary
.
程序 ancilla 使用方法如下:
./ancilla [--showsize size] [--showdata datacount]
--showsize
:根据数据大小 size 计算 space, len, align 长度--showdata
:填充辅助数据,并遍历辅助数据,参数 datacount 表示创建几个辅助数据对象
3.1 showdata 代码
void showdata(int count) { int i, controllen; struct msghdr msg; struct cmsghdr *cmptr; union control{ // 辅助数据头 struct cmsghdr cm; // 辅助数据对象大小是三部分的和。sizeof(int) 表示数据部分大小。 char control[CMSG_SPACE(sizeof(int))]; }; union control *control_un; // 辅助数据总长度 controllen = count * sizeof(union control); // 为辅助数据分配空间 control_un = (union control*)malloc(controllen);; // 填充辅助数据 for (i = 0; i < count; ++i) { control_un[i].cm.cmsg_len = CMSG_LEN(sizeof(int)); control_un[i].cm.cmsg_level = i; control_un[i].cm.cmsg_type = i; *((int*) CMSG_DATA(&control_un[i].cm)) = 2*i + 1; } printf("controllen = %d\n", controllen); msg.msg_control = control_un; msg.msg_controllen = controllen; // 遍历辅助数据 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NXTHDR(&msg, cmptr)) { printf("cmsg_len = %d, cmsg_level = %d, cmsg_type = %d, data = %d\n" , cmptr->cmsg_len, cmptr->cmsg_level, cmptr->cmsg_type, *((int*) CMSG_DATA(cmptr))); } // 16 进制形式打印所有辅助数据 printrawdata((char*)control_un, controllen); }
3.2 实验结果
图2 查看三个长度
图3 遍历辅助数据
图 3 中创建了 5 个辅助数据对象,并进行遍历。
4. 总结
- 掌握辅助数据的相关概念(几个长度概念)
- 根据 cmsghdr{} 获取数据首地址
- 遍历所有辅助数据对象