这两个函数是最通用的I/O函数。实际上我们可以把所有read、readv、recv和recvfrom调用替换成recvmsg调用。类似地,各种输出函数调用也可以替换成sendmsg调用。
#include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags); 返回:读入或写出字节数——成功;-1——出错
这两个函数把大部分参数封装到一个msghdr结构中:
struct msghdr { void *msg_name; /* protocol address */ socklen_t msg_namelen; /* size of protocol address */ struct iovec *msg_iov; /* scatter/gather array */ int msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data (cmsghdr struct) */ socklen_t msg_controllen; /* length of ancillary data */ int msg_flags; /* flags returned by recvmsg() */ };
这里给出的msghdr结构符合POSIX规范。有些系统仍然使用本结构源自4.2BSD的较旧版本。这个较旧的结构没有msg_flags成员,而且msg_control和msg_controllen成员分别被称为msg_accrights和msg_accrightslen。这个较旧结构唯一支持的辅助数据形式用于传递文件描述字(称为访问权限)。
msg_name和msg_namelen这两个成员用于套接口未连接的场合(譬如未连接UDP套接口)。它们类似reacvfrom和sendto的第5和第6个参数:msg_name指向一个套接口地址结构,调用者在其中存放接收者(对于sendmsg调用)或发送者(对于recvmsg调用)的协议地址。如果无需指明协议地址(例如对于TCP套接口或已连接UDP套接口),msg_name应置为空指针。msg_namelen对于sendmsg是一个值参数,对于recvmsg却是一个值-结果参数。
msg_iov和msg_iovlen这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似readv和writev的第2和第3个参数。
msg_control和msg_controllen这两个成员指定可选的辅助数据的位置和大小。
对于recvmsg和sendmsg,我们必须区别它们的两个标志变量:一个是传递值的flags参数,另一个是所传递msghdr结构的msg_flags成员,它传递的是引用,因为传递给函数的是该结构的地址。
只有recvmsg使用msg_flags成员。recvmsg被调用时,flags参数被拷贝到msg_flags成员,并由内核使用其值驱动接收处理过程。内核还依据recvmsg的结构更新msg_flags成员的值。
sendmsg忽略msg_flags成员,因为它直接使用flags参数驱动发送处理过程。这一点意味着如果想在某个sendmsg调用中设置MSG_DONTWAIT标志,那就把flags参数设置为该值;把msg_flags成员设置为该值不起作用。
如下图所示,汇总了内核为相关输入和输出函数检查的flags参数值以及recvmsg可能返回的msg_flags成员值。其中没有sendmsg msg_flags一栏,因为本组合无效。
标志 | 内核检查:
send flags sendto flags sendmsg flags |
内核检查:
recv flags recvfrom flags recvmsg flags |
内核返回:
recvmsg msg_flags |
MSG_DONTROUTE
MSG_DONTWAIT MSG_PEEK MSG_WAITALL |
|
|
|
MSG_EOR
MSG_OOB |
|
|
|
MSG_BCAST
MSG_MCAST MSG_TRUNC MSG_CTRUNC MSG_NOTIFICATION |
|
这些标志中,内核只检查而不返回前4个标志;既检查又返回下2个标志;不检查而只返回后5个标志。recvmsg返回的7个标志解释如下:
MSG_BCAST 本标志随BSD/OS引入,相对较新。它的返回条件是:本数据报作为链路层广播收取或者其宿IP地址是一个广播地址。
MSG_MCAST 本标志随BSD/OS引入,相对较新。它的返回条件是:本数据报作为链路层多播收取。
MSG_TRUNC 本标志的返回条件是:本数据报被截断;也就是说,内核预备返回的数据超过进程事先分配的空间(所有iov_len成员之和)。
MSG_CTRUNC 本标志的返回条件是:本数据报的辅助数据被截断;也就是说,内核预备返回的辅助数据超过进程事先分配的空间(msg_controllen)。
MSG_EOR 如果返回的数据不是一个逻辑记录的结尾所在,本标志将清零;否则本标志将设置。TCP不使用本标志,因为它是一个字节流协议。
MSG_OOB 本标志绝不为TCP带外数据返回。它用于其他协议族(例如OSI协议族)。
MSG_NOTIFICATION 本标志的返回条件是:SCTP接收端读入的本消息是一个事件通知,而不是一个数据消息。
如下图所示,展示了一个msghdr结构以及它指向的各种信息。图中假设进程即将对一个UDP套接口调用recvmsg。
图中给协议地址分配了16个字节,给辅助数据分配了20个字节。为缓冲数据初始化了3个iovec结构构成的数组:第一个指定一个100字节的缓冲区,第二个指定一个60字节的缓冲区,第三个指定一个80字节的缓冲区。我们还假设已为这个套接口设置了IP_RECVDSTADDR套接口选项,以接收所读取UDP数报的宿IP地址。
我们接着假设从192.6.38.100端口2000到达一个170字节的UDP数据报,它的目的地是我们的UDP套接口,宿IP地址为206.168.112.96。如下图所示,展示了recvmsg返回时msghdr结构中的所有信息。(图中被修改过的字段标了阴影)
如下图所示为5组I/O函数之间的差异: