struct cmsghdr
1 是什么在 C 语言尤其是 Unix/Linux 网络编程中 struct cmsghdr 是 控制消息头Control Message Header 的标准结构体 主要用于在套接字通信中传递 辅助数据Ancillary Data 或 控制消息Control Messages什么是控制信息 在套接字通信中除了 sendmsg 和 recvmsg 传输的主要数据 有时还需要传递一些“带外”的元数据这就是控制信息Ancillary data。 常见的用途包括 传递文件描述符在 UNIX 域套接字进程间传递。 获取或设置 IP 选项如记录数据包到达接口。 获取扩展错误信息如 IP_RECVERR。 传递 UNIX 凭证如进程的用户 IDUID。2 定义该结构体定义在头文件 sys/socket.h 中structcmsghdr{socklen_tcmsg_len;// 控制消息的总长度包含头部本身 数据 对齐填充intcmsg_level;// 协议层标识如 SOL_SOCKET, IPPROTO_IP, IPPROTO_IPV6 等intcmsg_type;// 协议层内的具体类型如 SCM_RIGHTS, SCM_TIMESTAMP 等// 注意cmsg_type 之后紧跟的是实际数据但结构体定义中不直接包含数据字段};cmsg_len: 整个控制消息的长度包含自身类型在 POSIX 标准中为 socklen_t。 它必须通过 CMSG_LEN() 宏计算以确保内存对齐。 cmsg_level: 创建此消息的协议层如 SOL_SOCKET, IPPROTO_IP 决定了 cmsg_type 如何解释。 cmsg_type: 协议层内具体的消息类型 例如在 SOL_SOCKET 级别下有 SCM_RIGHTS传文件描述符等。 cmsg_data: 一个柔性数组不占结构体本身的空间。 实际控制消息数据紧跟结构体存放总长度由 cmsg_len 指定。 关键安全规则 不要手动偏移或直接访问 cmsghdr 序列应始终使用专门的宏来操作以防止对齐和可移植性问题。cmsg_level 和 cmsg_type 正是接收方判断“这条控制数据是用来干什么的”唯一依据 让接收方在运行时动态识别控制消息的“指令”和“数据格式” 从而安全、正确地使用这些辅助数据。 接收方的应用程序就是通过读取这两个字段 来决定如何安全地解析 cmsg_data并执行对应的逻辑3 宏由于内核要求控制消息 内存对齐 且不同架构对齐规则不同严禁手动计算偏移。 POSIX 提供了以下安全宏#1 CMSG_FIRSTHDR(struct msghdr *) 获取第一个 cmsghdr 指针#2 CMSG_NEXTHDR(struct msghdr *, struct cmsghdr *) 获取下一个 cmsghdr 指针#3 CMSG_DATA(struct cmsghdr *) 获取该控制消息的 实际数据起始地址 返回指向 cmsghdr 数据部分的指针#4 CMSG_LEN(size_t datalen) 计算应填入 cmsg_len 的值#5 CMSG_SPACE(size_t datalen) 计算分配控制缓冲区时需要的 总空间用于 malloc 或栈数组#6 CMSG_ALIGN(length) 对齐后长度 返回 length 经过内存对齐之后的值是 Linux 扩展。CMSG_LEN 用于设置单个消息的 cmsg_len 字段 CMSG_SPACE 用于申请缓冲区大小。两者通常相差几个字节的对齐填充。cmsg_len 包含头部 它不是纯数据长度而是 CMSG_LEN(data_size) 的返回值。 多条控制消息一次 recvmsg() 可能返回多个 cmsghdr需用 CMSG_NEXTHDR 遍历。 缓冲区大小msg_controllen 必须用 CMSG_SPACE() 计算否则可能截断或越界。 平台差异部分类型如 SCM_TIMESTAMPING、IPV6_PKTINFO依赖 Linux 内核版本或编译选项 编写跨平台代码需条件编译。 权限与安全传递 SCM_CREDENTIALS 或 SCM_RIGHTS 需使用 UNIX 域套接字且接收方需有相应权限。CMSG_LEN 和 CMSG_SPACE 的区别是 CMSG_LEN 计算的是实际使用的长度用于赋值给 cmsghdr 的 cmsg_len 成员。 CMSG_SPACE 计算的是需要分配的总空间大小用于确定缓冲区大小和赋值给 struct msghdr 的 msg_controllen。 简单来说CMSG_SPACE 总是 ≥ CMSG_LEN。 这是因为 CMSG_SPACE 在头部和数据的长度之外还会额外加上满足内存对齐要求的填充字节。4 传递文件描述符传递文件描述符是 struct cmsghdr 最经典的应用场景之一。 核心思路是借助 Unix 域套接字 和 SCM_RIGHTS 控制消息 在无关的进程之间复制一个描述符使其指向同一个打开文件。struct cmsghdr 能够跨进程传递文件描述符的原理是 1 发送进程通过 sendmsg 发出一个文件描述符一个整数比如5 2 内核根据这个整数5在发送进程的文件描述符表中找到它对应的内核文件结构struct file。 它记录了文件偏移量、访问模式、inode指针等。 3 内核在接收进程的文件描述符表中寻找一个空闲的整数位比如7 让它也指向同一个 struct file。同时内核会增加该 struct file 的引用计数。 4 接收进程在控制消息中收到的就是这个新分配的整数7。 所以 传递的内容表面上是一个整数文件描述符 实质上是在内核层面共享同一个 struct file 对象。内核真实工作流程6 步 假设进程 A 想通过 Unix 套接字把 fd5 传给进程 B步骤发生位置具体动作1️⃣用户态 A调用sendmsg()cmsghdr中填入整数52️⃣内核态内核拦截调用查 A 的 FD 表找到fd5对应的struct file *3️⃣内核态对该struct file增加引用计数get_file()防止被提前关闭4️⃣内核态将struct file *存入 Unix 套接字的内部接收队列不经过用户态5️⃣内核态B 调用recvmsg内核从队列取出struct file *在 B 的 FD 表中分配一个空闲编号如86️⃣内核态 → 用户态 B将新编号8写入 B 的CMSG_DATA缓冲区用户态只看到整数8结果 A 的 5 和 B 的 8 指向同一个 struct file引用计数为 2。 双方后续 read/lseek 会相互影响但 FD 编号各自独立。 struct cmsghdr 传递的只是 发送方进程的文件描述符整数值。 内核拦截该请求后在内部查表找到对应的 struct file增加其引用计数并将其安全存入套接字内核队列。 接收方调用 recvmsg 时内核在接收进程中分配新 FD 编号并将其关联到同一个 struct file 最后将新编号返回给用户态。