Linux系统编程:sendmsg与recvmsg的使用方法

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_namemsg_namelen: 用于指定消息的目的地址,主要在发送时使用。在 Unix 域套接字中,这两个字段通常为 NULL0,因为不需要指定远程地址。

  • msg_iovmsg_iovlen: 用于指定多个数据缓冲区。这些缓冲区通过 iovec 结构体指向,可以一次性传递多个数据块。msg_iovlen 表示 msg_iov 中缓冲区的数量。

  • msg_controlmsg_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_FIRSTHDRCMSG_NXTHDR 等宏操作的,用于遍历和构造控制消息。

三个结构体用法总结

简单来说,他们的作用如下:

  • iovec 用于传递实际的数据(即使是一个空的数据块,也需要它来启动消息传递)。

  • msghdr 结合了 ioveccmsghdr,用于传递数据和文件描述符(控制信息)。

  • 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 就是从另一个进程发送过来的文件描述符
}

欢迎评论区提问!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若亦_Royi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值