linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg

时间:2021-02-24 11:05:37
6.2.2.2.5、报文接收:

recv/recvfrom/recvmsg和send族系统调用的道理是一样的,如下图:

linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg

sock_recvmsg函数最终调用套接字类型ops的recvmsg方法,对于数据报套接字为函数sock_common_recvmsg,它实际调用传输层协议ops的recvmsg方法,对于UDP协议为函数udp_recvmsg,注意传输层协议ops的三类recvmsg也是各异的;

注意,实际报文的接收参见4.2.2.2.2一节末尾的udp_rcv函数处理流程,它将传输层收到的报文,根据bind中绑定的接收地址在UDP协议绑定表中找到应该转送的传输通道sock的接收队列(sock->sk_receive_queue),所以这里recv族实际只是从传输通道sock的接收队列中获取报文;

另外,当传输通道sock的接收队列中暂时没有报文时,如果该socket文件读属性为阻塞,那么应用程序将被睡眠,直到超时或者有报文进入传输通道sock的接收队列,非阻塞的情况下将直接返回;

udp_recvmsg函数的主要流程如下:

linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg

1、调用函数__skb_recv_datagram获取报文,在获取到报文后根据获取长度和预先希望获取的长度调整返回长度,原则是“用户进程希望获取的长度如果大于实际长度则应置copied为实际长度,否则要加标志位MSG_TRUNC还要检查校验和”,然后把报文内容复制到msghdr描述符的数据内容字段中(msg_iov);

报文的实际接收函数方式,就是在传输通道sock的接收队列(sk->sk_receive_queue)不为空时,从中摘除最旧的一个报文并返回(skb_peek),若为空,则陷入睡眠直到有报文被接收或超时(见函数wait_for_packet,定时阻塞,超时或被信号触发均被唤醒),超时时间在初始化时指定为无限长,可由setsockopt系统调用修改;如下图:

linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg

2、服务器是如何获知客户端的地址的?正是在这里!在报文接收中,接收方不仅接收报文数据,同样记录了报文发送者的地址,如下图:

linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg

上图中sin是recvfrom系统调用的第五个参数,它用于应用程序记录发送给它报文的发送者地址,它的被赋值就是在这里,根据收到的报文的IP协议头的源IP、UDP协议头的源端口,获知了对端地址;

3、到这里,这个报文的使命也就正式结束了,该skb将被释放:

linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg

至此,报文接收流程结束,要明确:

1、传输通道sock的接收队列存在报文时,应用程序摘除一个最旧的报文,否则应用程序阻塞直至超时或者有报文可接收;超时时间可由setsockopt设置;

2、除接收数据外,还会记录发送者的地址到应用程序(由recvfrom系统调用的第五参数),这样应用程序即可尝试回复报文给该发送者