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
{
void *iov_base;
size_t iov_len;
};
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;
};
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;
int cmsg_level;
int cmsg_type;
};
注意事项: 由msg_control指向的辅助数据必须为cmsghdr结构适当的对齐。
以下是在一个控制缓冲区中出现2个辅助数据对象的例子:

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

疑问:那么假如对端传递过来了一个描述符的话,那我如何能够获取到该辅助数据呐?自己手动分配空间给cmsg_data[]吗?当然不是,这些系统已经帮我们想好了。系统提供了以下的5个宏来实现。
#include <sys/socket.h>
#include <sys/param.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdrptr);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdrptr, struct cmsghdr *cmsghdr);
unsigned char *CMSG_DATA(struct cmsghdr *cmsgptr);
unsigned char *CMSG_LEN(unsigned int length);
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))
{
u_char *ptr;
ptr = CMSG_DATA(cmsgptr);
}
注意事项: 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地址和端口号)。