sendmsg与recvmsg
文章目录
三个结构体
在 Unix 网络编程中,msghdr、iovec 和 cmsghdr 是三个关键的结构体,它们用于在系统调用 sendmsg 和 recvmsg 中处理复杂的消息传递,特别是涉及到多缓冲区数据和控制信息(例如,文件描述符的传递)。
在学习sendmsg 和 recvmsg之前,我们先来看一下这三个结构体的结构:
msghdr
定义
我们先来看一下他的英文含义,便于我们记忆:
msghdr
: Message Header
- Message:消息
- Header:头部
msghdr
结构体用于描述一个完整的消息,包括消息的头部信息、数据和控制信息。
下面是他本身的结构:
struct msghdr {
void *msg_name; // 指向目的地址的指针 (对于Unix域套接字,这通常为NULL)
socklen_t msg_namelen; // 目的地址的长度
struct iovec *msg_iov; // 指向数据缓冲区数组的指针
size_t msg_iovlen; // 数据缓冲区数组的元素个数
void *msg_control; // 指向辅助数据的缓冲区 (用于控制消息)
size_t msg_controllen; // 辅助数据缓冲区的大小
int msg_flags; // 接收消息时设置的标志
};
作用
-
msg_name
和msg_namelen
: 用于指定消息的目的地址,主要在发送时使用。在 Unix 域套接字中,这两个字段通常为NULL
和0
,因为不需要指定远程地址。 -
msg_iov
和msg_iovlen
: 用于指定多个数据缓冲区。这些缓冲区通过iovec
结构体指向,可以一次性传递多个数据块。msg_iovlen
表示msg_iov
中缓冲区的数量。 -
msg_control
和msg_controllen
: 用于指定控制信息(如文件描述符)缓冲区的位置和大小。控制信息是通过cmsghdr
结构体管理的。 -
msg_flags
: 用于接收时设置标志,表示消息的状态,如MSG_TRUNC
(消息被截断)或MSG_CTRUNC
(控制信息被截断)。
iovec 结构体
在 msghdr 中有一个指针指向这个类型的数组,传输时,信息存放在里面
定义
同样的,我们先来看一下英文释义:
iovec
: Input/Output Vector
- Input:输入
- Output:输出
- Vector:向量(在这里指一个数据数组)
iovec
结构体用于描述一个输入或输出数据缓冲区的向量,是分散/聚集 I/O 操作中使用的关键结构。
结构
struct iovec {
void *iov_base; // 指向数据缓冲区的指针
size_t iov_len; // 数据缓冲区的长度
};
作用
-
iov_base
: 指向实际数据的缓冲区。这可以是任何类型的数据块。 -
iov_len
: 指定数据缓冲区的长度。
用法
-
在发送时,
iovec
数组中的每个元素都表示要发送的一个数据块。sendmsg
会将这些数据块作为一个整体发送。 -
在接收时,
iovec
数组中的每个元素都表示接收的数据块的位置和大小。recvmsg
会将接收到的数据分散到这些缓冲区中。
cmsghdr 结构体
cmsghdr
结构体用于描述控制信息,这些控制信息通过 msg_control
字段传递。控制信息可以包含文件描述符、标志等辅助数据。
定义
英文释义:
cmsghdr
: Control Message Header
- Control:控制
- Message:消息
- Header:头部
cmsghdr
结构体用于描述一个控制消息的头部,控制消息通常包含额外的协议信息或文件描述符等辅助数据。
结构
struct cmsghdr {
size_t cmsg_len; // 控制信息的总长度(包含头部)
int cmsg_level; // 控制信息的协议级别(通常为 SOL_SOCKET)
int cmsg_type; // 控制信息的类型(例如 SCM_RIGHTS)
// 之后是控制数据
};
作用
-
cmsg_len
: 包含整个控制信息的长度,包括cmsghdr
结构体本身和后面的控制数据。 -
cmsg_level
: 表示控制信息的协议级别,通常为SOL_SOCKET
,表示这是一个与套接字相关的控制信息。 -
cmsg_type
: 表示控制信息的类型,如SCM_RIGHTS
,用于文件描述符的传递。
用法
cmsghdr
结构体是通过CMSG_FIRSTHDR
、CMSG_NXTHDR
等宏操作的,用于遍历和构造控制消息。
三个结构体用法总结
简单来说,他们的作用如下:
-
iovec
用于传递实际的数据(即使是一个空的数据块,也需要它来启动消息传递)。 -
msghdr
结合了iovec
和cmsghdr
,用于传递数据和文件描述符(控制信息)。 -
cmsghdr
实际上包含了文件描述符这一特殊的控制信息。
示例代码
我们以主从反应堆为例,假设我们要让父进程接收到的连接的套接字作为消息发送到子进程,子进程继续维护连接内容,那么我们应该进行如下代码设计:
sendmsg示例
在发送方进程中,下面讲述了sendmsg的用法:
int fd_to_send = 1; // 要发送的文件描述符,这里设置为标准输出文件描述符(1)
struct msghdr message; // 定义一个 msghdr 结构体,用于描述将要发送的消息
struct iovec iov[1]; // 定义一个 iovec 数组,用于描述要发送的数据缓冲区信息
struct cmsghdr *ctrl_msg = NULL; // 定义一个指向 cmsghdr 结构体的指针,用于处理控制信息(如文件描述符)
char buf[CMSG_SPACE(sizeof(int))]; // 分配一个缓冲区,用于存储控制信息(在这个例子中是文件描述符, CMSG_SPACE(sizeof(int)) 计算了存储一个文件描述符所需的空间大小
char dummy_data[1] = {0}; // 定义一个字符数组,并初始化为 0,用于发送的数据(这里是占位符,实际数据不重要)
int *fdptr; // 定义一个指向 int 的指针,用于指向控制消息中的文件描述符位置
// 设置 iovec 结构体,将要发送的数据存储在 dummy_data 缓冲区中
iov[0].iov_base = dummy_data; // 指定缓冲区的起始地址
iov[0].iov_len = sizeof(dummy_data); // 指定缓冲区的长度
// 设置 msghdr 结构体,用于描述发送消息的信息
message.msg_name = NULL; // 不需要指定目标地址,因此设置为 NULL
message.msg_namelen = 0; // 地址长度为 0,因为不使用地址信息
// 设置 iovec 数组和长度,指定数据发送缓冲区
message.msg_iov = iov; // 指向 iovec 数组
message.msg_iovlen = 1; // iovec 数组中元素的数量
// 设置用于发送控制信息的缓冲区
message.msg_control = buf; // 指向控制信息的缓冲区
message.msg_controllen = sizeof(buf); // 设置控制信息缓冲区的大小
// 获取指向第一个控制消息头部的指针,准备填充控制信息
ctrl_msg = CMSG_FIRSTHDR(&message);
// 设置控制消息的级别和类型
ctrl_msg->cmsg_level = SOL_SOCKET; // 设置为套接字级别的控制信息
ctrl_msg->cmsg_type = SCM_RIGHTS; // 设置控制信息的类型为传递文件描述符
ctrl_msg->cmsg_len = CMSG_LEN(sizeof(int)); // 设置控制消息的总长度,包括头部和数据
// 获取控制消息数据部分的指针,并将要发送的文件描述符赋值给它
fdptr = (int *) CMSG_DATA(ctrl_msg);
*fdptr = fd_to_send; // 将文件描述符存储在控制消息的相应位置
// 调用 sendmsg 函数,通过套接字发送消息
// sockfd 是套接字描述符,message 是要发送的消息结构体,flags 为 0 表示没有特殊标志
if (sendmsg(sockfd, &message, 0) < 0) {
perror("sendmsg failed");
exit(1);
}
// 如果没有错误发生,文件描述符 fd_to_send 就成功通过套接字发送给了接收方进程
}
recvmsg 用法
由于注释太多也影响阅读体验,所以减少了注释量
下面是在从进程中,recvmsg 的使用方法:
struct msghdr message;
struct iovec iov[1];
struct cmsghdr *ctrl_msg = NULL;
char buf[CMSG_SPACE(sizeof(int))];
char dummy_data[1];
int received_fd;
iov[0].iov_base = dummy_data;
iov[0].iov_len = sizeof(dummy_data);
message.msg_name = NULL;
message.msg_namelen = 0;
message.msg_iov = iov;
message.msg_iovlen = 1;
message.msg_control = buf;
message.msg_controllen = sizeof(buf);
if (recvmsg(sockfd, &message, 0) < 0) {
perror("recvmsg failed");
exit(1);
}
// 通过 CMSG_FIRSTHDR 宏获取控制信息的首个头部指针
// 如果消息中包含控制信息,它会返回指向 cmsghdr 结构体的指针
ctrl_msg = CMSG_FIRSTHDR(&message);
// 检查控制信息是否存在,并验证它的协议级别和类型
// SOL_SOCKET 表示这是一个套接字级别的控制信息
// SCM_RIGHTS 表示这是一个文件描述符传递的控制信息
if (ctrl_msg != NULL && ctrl_msg->cmsg_level == SOL_SOCKET && ctrl_msg->cmsg_type == SCM_RIGHTS) {
// 从控制信息中提取文件描述符
// CMSG_DATA 宏返回指向控制信息数据部分的指针,这里是文件描述符的地址
received_fd = *((int *) CMSG_DATA(ctrl_msg));
// 打印接收到的文件描述符
printf("Received file descriptor: %d\n", received_fd);
// 在这里,received_fd 就是从另一个进程发送过来的文件描述符
}
欢迎评论区提问!