文章目录
UDP数据报的接收过程要分两部分来看:
- 网络层将数据报递交给UDP后,UDP的处理过程。该过程中,UDP需要接收数据包并对其进行校验,校验成功后将其放入接收队列中等待用户空间程序来读取;
- 用户空间程序调用read()等系统调用读取已经放入接收队列中的数据。
这篇笔记记录了第二步。
系统调用: udp_recvmsg()
对于应用程序而言,读操作可以通过多个系统调用实现,如read()、recv()、recvfrom()等等,但是这些系统调用到了传输层协议,都调用到了同一接口,对于UDP就是udp_recvmsg()。
int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len, int noblock, int flags, int *addr_len)
{
struct inet_sock *inet = inet_sk(sk);
struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
struct sk_buff *skb;
unsigned int ulen, copied;
int peeked;
int err;
int is_udplite = IS_UDPLITE(sk);
// 需要返回源地址信息,设置源地址长度
if (addr_len)
*addr_len = sizeof(*sin);
// 如果设置了MSG_ERRQUEUE标记,那么只读取错误信息
if (flags & MSG_ERRQUEUE)
return ip_recv_error(sk, msg, len);
try_again:
// 根据是否需要阻塞,从接收队列中取出一个skb
skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0), &peeked, &err);
if (!skb)
goto out;
// ulen为该skb中包含的数据载荷长度
ulen = skb->len - sizeof(struct udphdr);
// len为应用程序指定的buffer大小,所以下面的逻辑含义为:
// 1. 如果应用提供的buffer超过了该数据包的数据长度,那么调整要拷贝的数据量为该skb的实际数据量
// 2. 如果应用提供的buffer不够大,那么需要截断数据包,并设置截断标记
copied = len;
if (copied > ulen)
copied = ulen;
else if (copied < ulen)
msg->msg_flags |= MSG_TRUNC;
/*
* If checksum is needed at all, try to do it while copying the
* data. If the data is truncated, or if we only want a partial
* coverage checksum (UDP-Lite), do it before the copy.
*/
// 对于截断的数据包和尚未完成校验的数据包,先进行校验,校验出错则尝试读取下一个数据包
// 条件二实际上只用于UDPLite,因为UDP协议的校验在接收过程的第一步就完成了
if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) {
if (udp_lib_checksum_complete(skb))
goto csum_copy_err;
}
// 根据是否需要校验,调用不同的数据拷贝函数
if (skb_csum_unnecessary(skb))
err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov, copied);</