79-辅助数据

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{} 获取数据首地址
  • 遍历所有辅助数据对象

说明:本文转自blog.csdn.net,用于学习交流分享,仅代表原文作者观点。如有疑问,请联系我们删除~