sendmsg 和 recvmsg 函数
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地址和端口号)。