recv, recvfrom, recvmsg 从套接口接收一个消息

名字
recv, recvfrom, recvmsg - 从套接口接收一个消息
概要
#include <sys/types.h>

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
描述
recvfrom和recvmsg()用来从一个套接口接收消息,也可以用来在一个面向连接或非连接的套接口上接收数据。

if src_addr为NULL,并且底层协议提供了源地址,那么这个源地址将被填进去。如果为NULL,则不填任何东西,这种情况下,addrlen不被使用,也应该为NULL。addrlen是值-结果参数,就是说调用者应该在调用前将其初始化为与之关联的buffer src_addr的大小,返回后这个值被修改为源地址的实际大小。如果提供的buffer太小返回的地址是被截断的,此时,addrlen将返回(被设置为)一个比调用时初始化的更大的值。

recv调用通常只用于已建立连接(参见connect(2))的套接口,等效于参数src_addr为NULL的recvfrom调用。

成功完成时这三个例程都返回消息长度。如果消息长度超过提供的buffer,超出的字节可能被丢弃,这要看该消息是从声明类型的套接口上接收的。

如果套接口上没有消息可读,那么这几个receives调用就等待一个消息到达,除非这个套接口被设置为非阻塞的(参见fcntl(2)),在这种情况下调用返回-1并且外部变量errno被设置为EAGAIN或EWOULDBLOCK。这几个receives调用通常返回当前已有的数据(当然最多是你请求的量),而不是等待所有请求的数据量。

selec(2)和poll(2)调用可以用来决定什么时候有更多的数据到达。

recv()调用的flags参数是下面这些值中的一个或几个进行或运算的结果。
MSG_CMSG_CLOEXEC(仅recvmsg()有;linux2.6.23以及以上版本支持)
在文件描述符上设置close-on-exec标志,这个文件描述符是通过在unix域文件描述符中使用SCM_RIGHTS操作收到的(见unix(7)中的描述)。这个标志的作用和open(2)中的O_CLOEXEC标志一样。
MSG_DONTWAIT (Linux 2.2及以上版本支持)
设置操作为非阻塞;如果这个操作将会阻塞,那么这个调用失败,错误码设置为EAGAIN或EWOULDBLOCK(使用F_SETFL fcntl(2)F_SETFL 的O_NONBLOCK标志有同样的效果)。
MSG_ERRQUEUE (Linux 2.2及以上版本支持)
这个标志指定从套接口错误队列中接受排队的错误。这个错误通过辅助消息(数据)传过来,此时辅助消息的类型依赖于协议(ipv4中是IP_RECVERR)。用户应该提供足够大小的buffer。查看cmsg(3)和ip(7)获取更多的信息。引起错误的原始分组数据作为正常数据由msg_iovec传过来。引起错误的数据报的原始目的地址在msg_name中。
对于本地错误来说,不传送地址(可以用msghdr的cmsg_len成员检查)。为了接受错误,需要在msghdr中设置MSG_ERRQUEUE标志。一个错误传递过之后,后面的错误将基于下一个排队中的错误再生,并且在下一个socket操作(如recvmsg)中被传递。

错误封装在sock_extended_err结构体中:

#define SO_EE_ORIGIN_NONE 0
#define SO_EE_ORIGIN_LOCAL 1
#define SO_EE_ORIGIN_ICMP 2
#define SO_EE_ORIGIN_ICMP6 3

struct sock_extended_err
{
uint32_t ee_errno; /* error number */
uint8_t ee_origin; /* where the error originated */
uint8_t ee_type; /* type */
uint8_t ee_code; /* code */
uint8_t ee_pad; /* padding */
uint32_t ee_info; /* additional information */
uint32_t ee_data; /* other data */
/* More data may follow */
};

struct sockaddr *SO_EE_OFFENDER(struct sock_extended_err *);
ee_errno包含了队列中的错误的错误码errno,ee_origin是错误来源,其他域由协议指定。SOCK_EE_OFFENDER宏返回一个指针,指向一个网络对象(节点)的地址,这个错误就是产生于此网络对象(节点),该宏需要的参数是一个指向辅助数据的指针(将它转换为struct sock_extended_err*)。如果这个地址是未知的,sockaddr的sa_family成员为AF_UNSPEC,其他域是未定义的。引起错误的分组负载作为正常数据传送。
对于本地错误来说,不传送地址(可以用msghdr的cmsg_len成员检查)。为了接受错误,需要在msghdr中设置MSG_ERRQUEUE标志。一个错误传递过之后,后面的错误将基于下一个排队中的错误再生,并且在下一个socket操作(如recvmsg)中被传递。
MSG_OOB
这个标志请求接收不会在正常数据流中收到的带外数据(紧急数据)。有些协议将紧急数据放在正常数据队列的头上,该标志就不能用于这样的协议。
MSG_PEEK
这个标志使得接收操作从接收队列开始出返回数据,但并不从队列中删掉返回的数据。这样一来,后续的调用将返回同样的数据。
MSG_TRUNC (Linux 2.2开始支持)
对于raw(AF_PACKET标志创建)套接口,数据报(Linux 2.4.27/2.6.8开始支持)套接口,以及netlink套接口(Linux 2.6.22开始支持),使用该标志将返回分组或数据报的真正长度,即时它比传进去的缓冲大。UNIX域套接口(unix(7))中还未实现。

在流套接口中的使用请参考tcp(7)手册页。
MSG_WAITALL (Linux 2.2开始支持)
该标志要求操作阻塞直到所有的请求被满足。然而调用在以下情况中还是会返回少于请求读取的数据:捕获到一个信号;出错;连接断开;下一个将要接收的数据和即将返回的数据不是同一类型。

recvmsg()调用使用msghdr结构最小化了需要直接提供的参数个数。该结构在<sys/socket.h>中定义为如下格式:

struct iovec { /* Scatter/gather 数组项 */
void *iov_base; /* 开始地址 */
size_t iov_len; /* 需要传输的字节数 */
};

struct msghdr {
void *msg_name; /* 可选的地址 */
socklen_t msg_namelen; /* 地址大小 */
struct iovec *msg_iov; /* 分散或聚合的数组 */
size_t msg_iovlen; /* msg_iov 中元素的个数 */
void *msg_control; /* 附带数据,看下面 */
size_t msg_controllen; /* 附带数据缓存区大小 */
int msg_flags; /* 收到的消息上的标志 */
};

如果socket是未连接的,则msg_name 和 msg_namelen指定源地址;如果没有names(地址)需要或请求的话,msg_name可以为null指针。像在readv(2)中讨论的一样,msg_iov 和 msg_iovlen域描述了scatter-gather位置。msg_control域指向存取协议控制相关消息或者辅助数据的一个缓冲,长度为msg_controllen域指定。recvmsg()被调用时,msg_controllen 应该包含msg_control中可用数据长度;如果成果返回,它将设置为控制消息序列长度。

这些消息有如下形式:

struct cmsghdr {
socklen_t cmsg_len; /* 数据字节数,包括hdr头本身 */
int cmsg_level; /* 产生协议 */
int cmsg_type; /* 协议指定类型 */
/* 接下来是数据
unsigned char cmsg_data[]; */
};

辅助数据应该只使用cmsg(3)中定义的宏访问。

例如,Linux使用辅助数据机制传递扩展的错误,ip选项,以及通过UNIX域套接口传递文件描述符。

recvmsg()返回时msghdr中的msg_flags域被设置。它包含如下几个标志:
MSG_EOR
指示记录边界;收到的数据完成了一个记录(通常在SOCK_SEQPACKET类型套接口中使用)。
MSG_TRUNC
表明数据报的尾部被丢弃了,因为数据报比提供的缓冲大。
MSG_CTRUNC
表明一些控制数据被丢弃了,因为没有更多的空间存储辅助数据。
MSG_OOB
返回表明收到了紧急或带外数据。
MSG_ERRQUEUE
表明没有收到数据,但从套接口错误队列中收到了一个扩展的错误。
返回值
这几个调用都返回收到的字节数,或者在出错时返回-1。当对方执行了一个正确的关闭操作时将返回0.
错误
这些是套接口层产生的标志错误。底层协议可以产生并返回额外的错误;参考相关手册页。
EAGAIN 或 EWOULDBLOCK
套接口被设置为非阻塞但接收操作需要阻塞才能完成,或者设置了接收超时,超时时间已到,仍没有收到数据。POSIX.1-2001允许在这种情况下返回两个中的任何一个,不要求它们有同样的值,所有可移植的应用程序应该对两者都做检查。
EBADF
参数 sockfd 不是一个有效的描述符。
ECONNREFUSED
远程主机拒绝网络连接(典型的原因是它没有运行请求的服务)。
EFAULT
接收缓冲指针指向进程地址空间以外。
EINTR
在任何数据到达之前接收操作被信号中断,参看 signal(7)。
EINVAL
无效参数被传入。
ENOMEM
不能为recvmsg()分配内存。
ENOTCONN
套接口关联到一个面向连接的协议,但还没连接(参考connect(2) 和 accept(2))。
ENOTSOCK
参数 sockfd 不是一个套接口。
遵循于
4.4BSD (这几个函数调用最先出现在 4.2BSD), POSIX.1-2001。

POSIX.1-2001 只描述了 MSG_OOB, MSG_PEEK, 和 MSG_WAITALL 三个标志。
注意
上面给出的原型遵循gblc2。除了返回类型为ssize_t(4.x BSD, libc4 以及 libc5 都为 int)之外,单一UNIX规范和此一致。参数flags在4.x BSD中为int,但在libc4 和 libc5中为unsigned int。参数在4.x BSD中为int,但在libc4 和 libc5中为size_t。addrlen参数在4.x BSD,libc4以及libc5中为int *。POSIX当前将其定为socklen_t *,参见accept(2)。

依据 POSIX.1-2001,msghdr 结构里 msg_controllen 成员域应该有类型 socklen_t,但是 glibc 目前使用类型 size_t。
示例
使用recvfrom()的例子在getaddrinfo(3)。
参看
fcntl(2), getsockopt(2), read(2), select(2), shutdown(2), socket(2), cmsg(3), sockatmark(3), socket(7)

原文地址:https://www.cnblogs.com/hnrainll/p/2141456.html