1. 基础介绍
最通用的I/O函数,只要设置好参数,read、readv、recv、recvfrom和write、writev、send、sendto等函数都可以对应换成这两个函数来调用。同时,各种输出函数调用也可以替换成sendmsg调用。
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssizt_t sendmsg(int sockfd, struct msghdr *msg, int flags);
大部分参数都在 msghdr结构中
struct iovec
{ /* Scatter/gather array items */
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
struct msghdr
{
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
struct msghdr 结构体参数说明:
- msg_name : 指向一个套接字地址结构,用于存放接受者或者发送者的协议地址。无需指明时,置为空 。
- msg_iov,msg_iovlen : 指定输入或输出的缓冲区数组。
- msg_control,msg_controllen : 可选的辅助数据的位置和大小。
注意事项:
- 在
sendmsg中,会忽略msg_flags成员,他会按照参数flags直接处理。那么当我们去设置MSG_DONTWAIT(临时非阻塞)是就把flags设为MSG_DONTWAIT而把msg_flags设为不起作用。 - 在
recvmsg中,使用msg_flags参数,他会将flags复制到msg_flags中进行处理。另外内核还可能将msg_flags的值更改。(因为在调用这两个函数的时候,第二个参数都是通过指针去调用的)
2. 图解其结构

协议地址16字节,辅助数据20字节。然后 iovec() 是三个缓冲数据数组。

- msg_name :填充了一个套接字地址结构。包括:源ip和端口
- msg_namelen :因为调用前和调用后之没有发生改变,所以还是返回 16
- msg_control:填充了一个cmsghdr()结构
- msg_controllen:,返回实际填入的字节数—>16
- msg_flags:也会被内核更新,但是在这里没有标志返回给进程
5组I/O函数的比较

3. 辅助数据
辅助数据又叫作控制信息,通过msg_control和msg_controllen来实现发送和接受。辅助数据的用途主要有:

它由一个或者多个辅助对象构成。对象由头部和身体组成 。头部是struct cmsghdr结构,身体是实际数据。 类似于http报文的结构。可以在头部与身体之间有填充字节,也可以在身体与下一个对象之间有填充字节。见下图 :
struct cmsghdr {
size_t cmsg_len; /* Data byte count, including header
(type is socklen_t in POSIX) */
int cmsg_level; /* Originating protocol */
int cmsg_type; /* Protocol-specific type */
/* followed by
unsigned char cmsg_data[]; */
};
注意事项: 由msg_control指向的辅助数据必须为cmsghdr结构适当的对齐。
以下是在一个控制缓冲区中出现2个辅助数据对象的例子:

以下是通过一个unix域套接字传递描述符 或者传递凭证时所用的cmsghdr结构:

疑问:那么假如对端传递过来了一个描述符的话,那我如何能够获取到该辅助数据呐?自己手动分配空间给cmsg_data[]吗?当然不是,这些系统已经帮我们想好了。系统提供了以下的5个宏来实现。
#include <sys/socket.h>
#include <sys/param.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdrptr);
//返回:指向第一个cmsghdr结构的指针,若无辅助数据则为NULL
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdrptr, struct cmsghdr *cmsghdr);
//返回:指向下一个cmsghdr结构的指针,若不再有辅助数据对象则为NULL
unsigned char *CMSG_DATA(struct cmsghdr *cmsgptr);
//返回:指向与cmsghdr结构关联的数据的第一个字节的指针
unsigned char *CMSG_LEN(unsigned int length);
//返回:给定数据量下存放到cmsg_len中的值
unsigned char *CMSG_SPACE(unsigned int length);
//返回:给定数据量下一个辅助数据对象总的大小。
那么就可以进行如下调用啦!!
struct msghdr msg;
struct cmsghdr *cmsgptr;
for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
cmsgptr = CMSG_NXTHDR(&msg, cmsgptr))
{
/* 判断是否自己需要 msg_level和msg_type */
u_char *ptr;
ptr = CMSG_DATA(cmsgptr); /* 获取辅助数据 */
/*通过ptr处理身体部分*/
}
注意事项: CMSG_LEN不计身体与下一个对象之间的填充字节,CMSG_SPACE反之。
实例1 :使用sendmsg和recvmsg在进程之间传递描述符,见下一篇博客.
附录:(1)recvfrom && sendto 函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
通常在UDP中使用,当然也可以用于TCP!因为UDP是无连接的,所以每次发送和接受时需要指明(IP地址和端口号)。
本文详细介绍了sendmsg和recvmsg两个通用I/O函数的使用方法及内部结构,包括msghdr和iovec结构体的参数解析,辅助数据的传输方式,并通过实例展示了如何在进程间传递描述符。
3589





