UNP编程:45---IO管理(recvmsg、sendmsg函数:struct msghdr)

一、函数原型

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  •  这两个函数是最通用的I/O函数。实际上我们可以把所有read、readv、recv和recvfrom调用替换成recvmsg调用。类似地,各种输出函数调用也可以替换成sendmsg调用
  • recvmsg:用于通过sockfd接受数据
  • sendmsg:用于通过sockfd发送数据

recvmsg()返回值

  • 成功:返回接收到的字节数
  • 返回0:
    • 当一个流套接字对等节点有序地关闭时,返回值将为0(传统的“end-of-file”返回)
    • 不同域(例如UNIX和Internet域)中的数据报套接字允许零长度的数据报。当接收到这样的数据报时,返回值为0
    • 如果要从流套接字接收的请求字节数为0,也可以返回值0
  • 失败:返回-1,将errno设置为如下的值:
    • 这些是套接字层生成的一些标准错误。底层协议模块可能会产生和返回其他错误
    • EAGAIN或EWOULDBLOCK:套接字标记为非阻塞,接收操作将阻塞,或者设置了接收超时,并且在接收数据之前超时已经过期。在这种情况下,POSIX.1允许返回任何一个错误,并且不要求这些常量具有相同的值,因此可移植应用程序应该检查这两种可能性
    • EBADF:参数sockfd是一个无效的文件描述符
    • ECONNREFUSED:远程主机拒绝允许网络连接(通常是因为它没有运行所请求的服务)
    • EFAULT:将接收缓冲区指针指向进程地址空间之外
    • EINTR:在任何数据可用之前,接收被信号的发送打断
    • EINVAL:无效参数传递
    • ENOMEM:无法为recvmsg()分配内存
    • ENOTCONN:套接字与一个面向连接的协议相关联,并且还没有被连接(参阅connect()和accept())
    • ENOTSOCK:文件描述符sockfd不引用套接字

sendmsg()返回值

  • 成功:这些调用返回发送的字节数
  • 失败:返回-1,,将errno设置为如下的值:
    • 这些是套接字层生成的一些标准错误。底层协议模块可能会产生和返回其他错误
    • EACCES:
      • 在目标套接字文件上拒绝EACCES(对于UNIX域套接字,它是由路径名标识的)的写权限,或者拒绝路径前缀中的某个目录的搜索权限。(参阅path_resolution ())
      • (对于UDP套接字)尝试发送到网络/广播地址,就好像它是一个单播地址
    • EAGAIN或EWOULDBLOCK:套接字标记为非阻塞,请求的操作将阻塞。在这种情况下,POSIX.1-2001允许返回任何一个错误,并且不要求这些常量具有相同的值,因此可移植应用程序应该检查这两种可能性
    • EAGAIN:sockfd引用的套接字以前没有绑定到一个地址,当尝试将其绑定到临时端口时,确定临时端口范围内的所有端口号目前都在使用。参见ip(7)中的/proc/sys/net/ipv4/ip_local_port_range的讨论
    •  EALREADY:另一个快速开放正在进行中
    • EBADF:sockfd不是一个有效的打开文件描述符
    • ECONNRESET:通过对等点重置连接
    • EDESTADDRREQ:套接字不是连接模式,没有设置对等地址
    • EFAULT:为参数指定了无效的用户空间地址
    • EINTR:在任何数据传输之前发生的信号
    • EINVAL:无效参数传递
    • EISCONN:连接模式套接字已经连接,但是指定了一个接收方。(现在要么返回此错误,要么忽略收件人规范。)
    • EMSGSIZE:套接字类型要求以原子方式发送消息,而要发送的消息的大小使得这种情况不可能发生
    • ENOBUFS:网络接口的输出队列已满。这通常表示接口已停止发送,但可能是由瞬态拥塞引起的。(通常,这在Linux中不会发生。当设备队列溢出时,数据包会无声地丢弃。)
    • ENOMEM:没有可用的内存
    • ENOTCONN:套接字没有连接,并且没有给定目标
    • ENOTSOCK:文件描述符sockfd不引用套接字
    • EOPNOTSUPP:标志参数中的一些位不适合套接字类型
    • EPIPE:在一个面向连接的套接字上,本地端已经关闭。在本例中,除非设置了MSG_NOSIGNAL,否则进程还将收到一个SIGPIPE

二、struct  msghdr结构体

  • recvmsg、sendmsg用于接受和发送的数据都存储在这个结构体中
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;      /*标志*/
};

msg_name、msg_namelen成员:

如果套接字是在未连接的场合进行传输数据(例如未连接UDP套接字),这两个成员则如下使用:

  • 如果是sendmsg函数:msg_name成员设置的是数据的接受者的地址,msg_namelen代表地址的长度
  • 如果是recvmsg函数:msg_name成员存放的是数据的发送者的地址,msg_namelen代表地址的长度

如果套接字是在已连接的场合进行传输数据(例如TCP通信/已连接UDP套接字),这两个成员则如下使用:

  • recvmsg、sendmsg都不使用这两个成员。所以msg_name值为空指针,msg_namelen为0

msg_iov、msg_iovlen成员:

  • msg_iov:用于发送/接受数据的缓冲区数组,可以用于发送/接受多组数据。每个成员是一个struct iovec类型的结构体
  • msg_iovlen:缓冲区数组的大小,而不是单个缓冲区的大小
struct iovec {
    void * iov_base; /*缓冲区首地址*/
    size_t iov_len;  /*缓冲区的大小*/
};

msg_control、msg_controllen成员:

  • msg_control:用于设置/存放辅助数据。辅助数据的数据类型为struct cmsghdr。以struct cmsghdr结构体指针开头,可以为多组
  • msg_controllen:辅助数据的总大小/长度

msg_flags成员:

  • recvmsg函数:只有recvmsg函数才使用msg_flags成员。当调用recvmsg函数时,recvmsg函数的flags参数被复制到msg_flags上,并由内核使用其值驱动接受处理过程
  • sendmsg函数:不设置此参数。因为它直接使用sendmsg函数的flags参数驱动发送处理过程。(如果msg_flags设置了值,则被sendmsg函数忽略。例如flags设置了MSG_DONTWAIT,msg_flags也设置了该值,则msg_flags的被忽略)

 三、flags参数与msg_flags成员

  • 下图列出了sendmsg和recvmsg的flags参数值,以及recvmsg可以接受到的数据中的msg_flags成员值(下图没有sendmsg---msg_flags一栏,因为上面介绍了此组合无效)

  • 这些标志中,内核只检查而不返回前4个标志。即检查又返回中间2个标志。不检查而只返回后4个标志
  • recvmsg返回的7个标志解释如下
    • MSG_BCAST:本标志随BSD/OS引入,相对较新。它的返回条件是本数据报作为链路层广播收取或者其目的IP地址是一个广播地址。与IP_RECVDSTADDR套接字选项相比,本标志是用于判定一个UDP数据报是否发往某个广播地址的更好方法
    • MSG_MCAST:本标志随BSD/OS引入,相对较新。它的返回条件是本数据报作为链路层多播收取
    • MSG_TRUNC:本标志的返回条件是本数据报被截断,也就是说,内核预备返回的 数据超过进程事先分配的空间(所有iov_len成员之和)。我们将在22.3节详细讨论本问题
    • MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断,也就是说,内核 预备返回的辅助数据超过进程事先分配的空间(msg_controllen)
    • MSG_EOR:本标志的返回条件是返回数据结束一个逻辑记录。TCP不使用本标 志,因为它是一个字节流协议
    • MSG_OOB:本标志绝不为TCP带外数据返回。它用于其他协议族(例如OSI协 议族)
    • MSG_NOTIFICATION:本标志由SCTP接收者返回,指示读入的消息是一个事件通知,而不 是数据消息

四、辅助数据(struct  cmsghdr)

辅助数据的概念:

  • 辅助数据通过sendmsg、recvmsg这两个函数,使用msghdr结构中的msg_control和msg_controllen成员发送和接收
  • 辅助数据的另一个称谓是“控制信息”

struct  cmsghdr结构体

  • 每个辅助数据用此结构体标识
#include <sys/socket.h>
struct cmsghdr {
    socklen_t   cmsg_len;     /*此结构体总的大小,通常使用CMSG_LEN宏进行赋值*/
    int         cmsg_level;   /* originating protocol */
    int         cmsg_type;    /* protocol-specific type */
    /*char cmsg_data[];*/     /*数据*/
};

辅助数据的用途:

辅助数据案例一:

  • 下图展示了在一个控制缓冲区出现2个辅助数据对象的例子
  • msg_control指向第一个辅助数据对象。辅助数据对象的总长度由msg_controllen指定,每个辅助数据对象开头是一个struct cmsghdr结构体,在结构体的cmsg_type成员和实际数据之间可以有填充字节。从数据结尾到下一个辅助数据对象之前也可以有填充字节(不是所有实现都支持在单个控制缓冲区中存放多个辅助数据对象)

辅助数据案例二:

  • 下图演示了一个通过UNIX域套接字传递描述符所用的cmsghdr结构体的格式(假设只传递了一个描述符,描述符存放在cmsg_data数组中。且在cmsg_type和cmsg_data之间没有填充字节)

辅助数据案例三:

  • 下图演示了一个通过UNIX域套接字传递凭证时所用的cmsghdr结构体的格式

五、辅助数据测试宏

  • 由于recvmsg返回的辅助数据可含有任意数目的辅助数据对象,为了对应用程序屏蔽可能出现的填充字节。定义了下面5个宏,以简化对辅助数据的处理:
#include <sys/socket.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);

struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);

size_t CMSG_ALIGN(size_t length);

size_t CMSG_SPACE(size_t length);

size_t CMSG_LEN(size_t length);

unsigned char *CMSG_DATA(struct cmsghdr *cmsg);
  • CMSG_FIRSTHDR:返回辅助数据第一个cmsghdr结构的指针。若无辅助数据(或者msg_control为一个空指针,或者cmsg_len小于一个cmsghdr结构的大小)返回NULL
  • CMSG_NXTHDR:返回指向下一个cmsghdr结构的指针,若没有下一个辅助数据则返回NULL
  • CMSG_SPACE:用于设置下一个辅助数据总的大小(参数指定)
  • CMSG_LEN:参数为cmsghdr的cmsg_data成员的大小,然后将此大小+cmsghdr其他成员的大小作为cmsghdr结构体的总大小作为返回值返回,通常将返回值作为cmsg_len的值
  • CMSG_DATA:指向与cmsghdr结构关联的数据的第一个字节的指针
  • CMSG_ALIGN:

CMSG_LEN和CMSG_SPACE的区别在于:前者不计辅助数据对象中的数据部分之后可能的填充字节,因而返回的是用于存放在cmsg_len成员中的值,后者计上结尾处肯能的填充字节,因而返回的是为辅助数据对象动态分配空间的大小值

  • 这些宏能以如下伪代码形式使用
struct msghdr msg;
struct cmsghdr *cmsgptr;

/* fill in msg structure */
/* call recvmsg() */

for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
    cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
        if (cmsgptr->cmsg_level == ... &&
            cmsgptr->cmsg_type == ...) {
            u_char *ptr;

            ptr = CMSG_DATA(cmsgptr);
            /* process data pointed to by ptr */
    }
} 

五、图解演示案例

  • 下图展示了一个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结构中的所 有信息

图中被recvmsg修改过的字段标上了阴影。从图1到图2的变动包括以下几点:

  • 由msg_name成员指向的缓冲区被填以一个网际网套接字地址结构,其中有所收到数据报 的源IP地址和源UDP端口号
  • msg_namelen成员(一个值-结果参数)被更新为存放在msg_name所指缓冲区中的数据 量。本成员并无变化,因为recvmsg调用前和返回后其值均为16
  • 所收取数据报的前100字节数据存放在第一个缓冲区,中60字节数据存放在第二个缓冲 区,后10字节数据存放在第三个缓冲区。最后那个缓冲区的后70字节没有改动。recvmsg 函数的返回值(即170)就是该数据报的大小。
  • 由msg_control成员指向的缓冲区被填以一个cmsghdr结构。(我们将在14.6节详细讨论 辅助数据,在22.2节详细讨论IP_RECVDSTADDR套接字选项。)该cmsghdr结构中, cmsg_len成 员 值 为 16, cmsg_level成 员 值 为 IPPROTO_IP, cmsg_type成 员 值 为 IP_RECVDSTADDR,随后4个字节存放所收到UDP数据报的目的IP地址。这个20字节缓冲 区的后4个字节没有改动
  • msg_controllen成员被更新为所存放辅助数据的实际数据量。本成员也是一个值-结果参数,recvmsg返回时其结果为16。
  • msg_flags成员同样被recvmsg更新,不过没有标志返回给进程

六、汇总

  • 下图汇总了我们已讲述的5组I/O函数之间的差异

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

董哥的黑板报

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

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

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

打赏作者

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

抵扣说明:

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

余额充值