recv, recvfrom, recvmsg - receive a message from a socket
recv()、recvfrom() 和 recvmsg() 调用用于从套接字接收消息。 它们可用于在UDP和TCP的套接字上接收数据。 本页首先介绍了所有三个系统调用的共同特点,然后介绍了调用之间的区别。
recv() 和 read(2) 之间的唯一区别是 flags 的存在。 使用零标志参数,recv() 通常等效于 read(2) (但请参阅 NOTES),且
recv(sockfd, buf, len, flags)
等价于
recvfrom(sockfd, buf, len, flags, NULL, NULL)
所有三个调用都在成功完成时返回消息的长度。 如果消息太长而无法放入提供的缓冲区,则 可能 会丢弃多余的字节,具体 取决于接收消息的套接字类型 ,显然TCP是不可能丢弃的。
如果套接字上没有可用消息,则接收调用将等待消息到达,除非套接字是非阻塞的(请参阅 fcntl(2)),在这种情况下,将返回值 -1 并将 errno 设置为 EAGAIN 或 EWOULDBLOCK。 recv_()调用通常会返回任何可用的数据,只要拿到数据就会立马返回,最多返回指定缓冲区大小的数据,但是并不会等待到让缓冲区满 ,除非设置了 MSG_WAITALL 标志,见下。
应用程序可以使用 select(2)、poll(2) 或 epoll(7) 来确定更多数据何时到达。
The flags argument is formed by ORing one or more of the following values:
ee_errno contains the errno number of the queued error. ee_origin is the origin code of where the error originated. The other fields are protocol-specific. The macro SOCK_EE_OFFENDER returns a pointer to the address of the network object where the error originated from given a pointer to the ancillary message. If this address is not known, the sa_family member of the sockaddr contains AF_UNSPEC and the other fields of the sockaddr are undefined. The payload of the packet that caused the error is passed as normal data.
For local errors, no address is passed (this can be checked with the cmsg_len member of the cmsghdr ). For error receives, the MSG_ERRQUEUE flag is set in the msghdr . After an error has been passed, the pending socket error is regenerated based on the next queued error and will be passed on the next socket operation.
recvfrom() 将接收到的消息放入缓冲区 buf 。 调用者必须在 len 中指定缓冲区的大小。
如果调用者希望拿到消息的原地址, 并且底层协议可以提供消息的源地址时,应将 src_addr 设置为指向用于接收消息原地址的缓冲区。 在这种情况下, addrlen 是一个 value-result 参数。 在调用之前,它应该被初始化为与 src_addr 关联的缓冲区的大小。 返回时,addrlen 被更新以包含源地址的实际大小。 如果提供的缓冲区太小,则截断返回的地址; 在这种情况下, addrlen 将返回一个大于提供给调用的值。
如果调用者对源地址不感兴趣,则应将 src_addr 和 addrlen 指定为 NULL。
ssize_t recv(int sockfd, void* buf, size_t len, int flags)
recv() 调用通常仅用于已连接的套接字(请参阅 connect(2))。 相当于调用:
recvfrom(fd, buf, len, flags, NULL, 0)
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags)
recvmsg() 调用使用 msghdr 结构来 最小化直接提供的参数数量 。 这个结构在 <sys/socket.h>中定义如下:
msg_name 字段指向调用者分配的缓冲区,如果套接字未连接( 特指UDP的服务端 ),则该缓冲区用于返回源地址。 调用者应在此调用之前将 msg_namelen 设置为此缓冲区的大小; 从成功调用返回后,msg_namelen 将包含返回地址的长度。 如果应用程序不需要知道源地址,可以将 msg_name 指定为 NULL。
The fields msg_iov and msg_iovlen describe scatter-gather locations, as discussed in readv(2).
需要注意的是 msg_iov 和 msg_iovlen 描述了一个 struct iovec 类型的数组, msg_iovlen 表示数组的元素个数,而struct iovec则是描述了一个缓冲区
字段 msg_control 指向用于其他协议控制相关消息或杂项辅助数据的缓冲区。 当recvmsg()被调用时, msg_controllen 为 msg_contro l中可用缓冲区的长度; 从成功调用返回时,它将被设置为控制消息序列的长度。
控制消息的格式为:
只能通过 cmsg(3) 中定义的宏访问辅助数据。
例如,Linux 使用这种辅助数据机制通过 UNIX 域套接字传递扩展错误、IP 选项或文件描述符。 有关在各种套接字域中使用辅助数据的更多信息,请参阅 unix(7) 和 ip(7)。
msghdr 中的 msg_flags 字段在 recvmsg() 返回时设置 。 它可以包含几个标志:
这些调用返回接收到的字节数,如果发生错误,则返回 -1。 如果发生错误,则设置 errno 以指示错误。
当流套接字对等端执行有序关闭(orderly shutdown)时,返回值将为 0(传统的“文件结束”返回)。
各种域(例如 UNIX 和 Internet 域)中的数据报套接字允许零长度数据报。 当收到这样的数据报时,返回值为 0。
如果从流套接字接收的请求字节数为 0,则也可能返回值 0。
这些是套接字层生成的一些标准错误。 底层协议模块可能会产生和返回额外的错误; 查看他们的手册页。
POSIX.1-2001, POSIX.1-2008, 4.4BSD (these interfaces first appeared in 4.2BSD).
POSIX.1 describes only the MSG_OOB, MSG_PEEK, and MSG_WAITALL flags.
如果零长度数据报未决,则带有零标志参数的 read(2) 和 recv() 提供不同的行为。 在这种情况下, read(2) 不起作用(数据报保持挂起),而 recv() 消耗挂起的数据报。
socklen_t 类型是由 POSIX 发明的。 另见 accept(2) 。
根据 POSIX.1,msghdr 结构的 msg_controllen 字段类型为 socklen_t,而 msg_iovlen 字段类型为 int,但 glibc 目前将两者设置为 size_t。
有关可用于在单个调用中接收多个数据报的 Linux 特定系统调用的信息,请参阅 recvmmsg(2)。
getaddrinfo(3) 中显示了使用 recv() 的示例。
非阻塞IO 和阻塞IO: 在网络编程中对于一个网络句柄会遇到阻塞IO 和非阻塞IO 的概念, 这里对于这两种socket 先做一下说明: 基本概念: 阻塞IO:: socket 的阻塞模式意味着必须要做完IO *** 作(包括错误)才会 返回。 非阻塞IO:: 非阻塞模式下无论 *** 作是否完成都会立刻返回,需要通过其他方 式来判断具体 *** 作是否成功。(对于connect,accpet *** 作,通过select判断, 对于recv,recvfrom,send,sendto通过返回值+错误码来判断)IO模式设置:SOCKET 对于一个socket 是阻塞模式还是非阻塞模式的处理方法:: 方法:: 用fcntl 设置用F_GETFL获取flags,用F_SETFL设置flags|O_NONBLOCK 同时,recv,send 时使用非阻塞的方式读取和发送消息,即flags设置为MSG_DONTWAIT 实现 fcntl 函数可以将一个socket 句柄设置成非阻塞模式: flags = fcntl(sockfd, F_GETFL, 0) //获取文件的flags值。 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) //设置成非阻塞模式; flags = fcntl(sockfd,F_GETFL,0) fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK) //设置成阻塞模式; 并在接收和发送数据时: 将recv, send 函数的最后有一个flag 参数设置成MSG_DONTWAIT recv(sockfd, buff, buff_size,MSG_DONTWAIT)//非阻塞模式的消息发送 send(scokfd, buff, buff_size, MSG_DONTWAIT) //非阻塞模式的消息接受普通文件对于文件的阻塞模式还是非阻塞模式::方法1、open时,使用O_NONBLOCK;方法2、fcntl设置,使用F_SETFL,flags|O_NONBLOCK;消息队列对于消息队列消息的发送与接受:://非阻塞 msgsnd(sockfd,msgbuf,msgsize(不包含类型大小),IPC_NOWAIT)//阻塞 msgrcv(scokfd,msgbuf,msgsize(**),msgtype,IPC_NOWAIT)读阻塞与非阻塞读的区别: //阻塞和非阻塞的区别在于没有数据到达的时候是否立刻返回.读(read/recv/msgrcv): 读的本质来说其实不能是读,在实际中, 具体的接收数据不是由这些调用来进行,是由于系统底层自动完成的。read 也好,recv 也好只负责把数据从底层缓冲copy 到我们指定的位置. 对于读来说(read, 或者recv) ::阻塞情况下:: 在阻塞条件下,read/recv/msgrcv的行为:: 1、如果没有发现数据在网络缓冲中会一直等待, 2、当发现有数据的时候会把数据读到用户指定的缓冲区,但是如果这个时候读到的数据量比较少,比参数中指定的长度要小,read 并不会一直等待下去,而是立刻返回。 read 的原则::是数据在不超过指定的长度的时候有多少读多少,没有数据就会一直等待。 所以一般情况下::我们读取数据都需要采用循环读的方式读取数据,因为一次read 完毕不能保证读到我们需要长度的数据, read 完一次需要判断读到的数据长度再决定是否还需要再次读取。非阻塞情况下:: 在非阻塞的情况下,read 的行为:: 1、如果发现没有数据就直接返回, 2、如果发现有数据那么也是采用有多少读多少的进行处理. 所以::read 完一次需要判断读到的数据长度再决定是否还需要再次读取。 对于读而言:: 阻塞和非阻塞的区别在于没有数据到达的时候是否立刻返回. recv 中有一个MSG_WAITALL 的参数:: recv(sockfd, buff, buff_size, MSG_WAITALL), 在正常情况下recv 是会等待直到读取到buff_size 长度的数据,但是这里的WAITALL 也只是尽量读全,在有中断的情况下recv 还是可能会被打断,造成没有读完指定的buff_size的长度。 所以即使是采用recv + WAITALL 参数还是要考虑是否需要循环读取的问题,在实验中对于多数情况下recv (使用了MSG_WAITALL)还是可以读完buff_size, 所以相应的性能会比直接read 进行循环读要好一些。 注意:: //使用MSG_WAITALL时,sockfd必须处于阻塞模式下,否则不起作用。 //所以MSG_WAITALL不能和MSG_NONBLOCK同时使用。 要注意的是使用MSG_WAITALL的时候,sockfd 必须是处于阻塞模式下,否则WAITALL不能起作用。写 阻塞与非阻塞写的区别: //写(send/write/msgsnd):: 写的本质也不是进行发送 *** 作,而是把用户态的数据copy 到系统底层去,然后再由系统进行发送 *** 作,send,write返回成功,只表示数据已经copy 到底层缓冲,而不表示数据已经发出,更不能表示对方端口已经接收到数据. 对于write(或者send)而言,阻塞情况下:: //阻塞情况下,write会将数据发送完。(不过可能被中断) 在阻塞的情况下,是会一直等待,直到write 完,全部的数据再返回.这点行为上与读 *** 作有所不同。原因:: 读,究其原因主要是读数据的时候我们并不知道对端到底有没有数据,数据是在什么时候结束发送的,如果一直等待就可能会造成死循环,所以并没有去进行这方面的处理; 写,而对于write, 由于需要写的长度是已知的,所以可以一直再写,直到写完.不过问题是write 是可能被打断吗,造成write 一次只write 一部分数据, 所以write 的过程还是需要考虑循环write, 只不过多数情况下一次write 调用就可能成功. 非阻塞写的情况下:: // 非阻塞写的情况下,是采用可以写多少就写多少的策略.与读不一样的地方在于,有多少读多少是由网络发送的那一端是否有数据传输到为标准,但是对于可以写多少是由本地的网络堵塞情况为标准的,在网络阻塞严重的时候,网络层没有足够的内存来进行写 *** 作,这时候就会出现写不成功的情况,阻塞情况下会尽可能(有可能被中断)等待到数据全部发送完毕, 对于非阻塞的情况就是一次写多少算多少,没有中断的情况下也还是会出现write 到一部分的情况.欢迎分享,转载请注明来源:内存溢出
评论列表(0条)