当前位置:   article > 正文

【Linux 内核网络协议栈源码剖析】recvfrom 函数剖析

recvfrom

继前篇介绍完sendto 数据发送函数 后,这里介绍数据接收函数 recvfrom。

一、应用层——recvfrom 函数

对于这个函数有必要分析一下,先看看这个dup例子。服务器端中调用recvfrom函数,并未指定发送端(客户端)的地址,换句话说这个函数是一个被动函数,有点类似于tcp协议中服务器listen 之后阻塞,等待客户端connect。这里则是服务器端recvfrom后,等待客户端sendto,服务器端recvfrom接收到客户端的数据包,也顺便知道了发送端的地址,于是将其填充到recvfrom的最后两个参数中,这样服务器端就获得了客户端的地址,然后服务器端就可sendto数据给客户端。(TCP同理)

想想也是,服务器怎么可能实现知道全球这么多客户的地址呢?但服务器采用的是大家广为人知的地址,比如你访问谷歌搜索,你知道谷歌的网址,但谷歌事先肯定不知道它众多访问者的地址,所以是客户端先主动访问,发送数据之后,谷歌才知道该客户端的地址,然后返回访问信息。

  1. #include <sys/socket.h>
  2. ssize_t recvfrom(int sockfd, const void *buff, size_t nbytes, int flags,
  3. const struct sockaddr *from, socklen_t *addrlen);
  4. //若成功返回读到的字节数,否则返回-1
  5. /*参数解析。这里sockfd是接收,from那边是发送
  6. 前面三个参数分别表示:套接字描述符,指向写出缓冲区的指针和写字节数。
  7. 与sendto不同是后面的参数,recvfrom的最后两个参数类似于accept的最后两个参数,返回时其中套接字地址结构的内容告诉我们是谁发送了数据报
  8. */
二、BSD Socket 层——sock_recvfrom 函数
  1. /*
  2. * Receive a frame from the socket and optionally record the address of the
  3. * sender. We verify the buffers are writable and if needed move the
  4. * sender address from kernel to user space.
  5. */
  6. //从指定的远端地址接收数据,主要用于UDP协议
  7. //从addr指定的源端接收len大小的数据,然后缓存到buff缓冲区
  8. //该函数还要返回远端地址信息,存放在addr指定的地址结构中
  9. static int sock_recvfrom(int fd, void * buff, int len, unsigned flags,
  10. struct sockaddr *addr, int *addr_len)
  11. {
  12. struct socket *sock;
  13. struct file *file;
  14. char address[MAX_SOCK_ADDR];
  15. int err;
  16. int alen;
  17. //参数有效性检查
  18. if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))
  19. return(-EBADF);
  20. //通过文件描述符找到对应socket结构
  21. if (!(sock = sockfd_lookup(fd, NULL)))
  22. return(-ENOTSOCK);
  23. if(len<0)
  24. return -EINVAL;
  25. if(len==0)
  26. return 0;
  27. //检查缓冲区域是否可写
  28. err=verify_area(VERIFY_WRITE,buff,len);
  29. if(err)
  30. return err;
  31. //调用下层函数inet_recvfrom
  32. len=sock->ops->recvfrom(sock, buff, len, (file->f_flags & O_NONBLOCK),
  33. flags, (struct sockaddr *)address, &alen);
  34. if(len<0)
  35. return len;
  36. //对比可知,这里是sock_recvfrom相比sock_sendto多出来的一部分
  37. //它的作用便是将发送端(客户端)的地址信息填充到addr中,就是获取客户端的地址信息
  38. if(addr!=NULL && (err=move_addr_to_user(address,alen, addr, addr_len))<0)
  39. return err;
  40. return len;
  41. }
三、INET Socket 层——inet_recvfrom 函数
  1. /*
  2. * The assorted BSD I/O operations
  3. */
  4. //其功能与inet_sendto函数类似
  5. static int inet_recvfrom(struct socket *sock, void *ubuf, int size, int noblock,
  6. unsigned flags, struct sockaddr *sin, int *addr_len )
  7. {
  8. //获取对应sock结构
  9. struct sock *sk = (struct sock *) sock->data;
  10. if (sk->prot->recvfrom == NULL)
  11. return(-EOPNOTSUPP);
  12. if(sk->err)
  13. return inet_error(sk);
  14. /* We may need to bind the socket. */
  15. //检查是否绑定了端口,没有的话就自动绑定一个,就服务器端而言,肯定是有的
  16. if(inet_autobind(sk)!=0)
  17. return(-EAGAIN);
  18. //调用下层udp_recvfrom函数
  19. return(sk->prot->recvfrom(sk, (unsigned char *) ubuf, size, noblock, flags,
  20. (struct sockaddr_in*)sin, addr_len));
  21. }

四、传输层——udp_recvfrom 函数

  1. /*
  2. * This should be easy, if there is something there we\
  3. * return it, otherwise we block.
  4. */
  5. //接收数据包,并返回对端地址(如果需要的话)
  6. int udp_recvfrom(struct sock *sk, unsigned char *to, int len,
  7. int noblock, unsigned flags, struct sockaddr_in *sin,
  8. int *addr_len)
  9. {
  10. int copied = 0;
  11. int truesize;
  12. struct sk_buff *skb;
  13. int er;
  14. /*
  15. * Check any passed addresses
  16. */
  17. if (addr_len)
  18. *addr_len=sizeof(*sin);
  19. /*
  20. * From here the generic datagram does a lot of the work. Come
  21. * the finished NET3, it will do _ALL_ the work!
  22. */
  23. //从接收队列中获取数据包
  24. skb=skb_recv_datagram(sk,flags,noblock,&er);
  25. if(skb==NULL)
  26. return er;
  27. //数据包数据部分(数据报)长度
  28. truesize = skb->len;
  29. //读取长度检查设置,udp是面向报文的,其接收到的每个数据包都是独立的
  30. //如果用户要求读取的小于可读取的,那么剩下的将被丢弃(本版本协议栈就是这么干的)
  31. copied = min(len, truesize);
  32. /*
  33. * FIXME : should use udp header size info value
  34. */
  35. //拷贝skb数据包中的数据负载到to缓冲区中
  36. //这里就是数据转移的地方,将数据从数据包中转移出来到缓存区
  37. skb_copy_datagram(skb,sizeof(struct udphdr),to,copied);
  38. sk->stamp=skb->stamp;//记录时间
  39. /* Copy the address. */
  40. //如果要求返回远端地址的话,这里就是拷贝远端地址信息了,含端口号和ip地址
  41. if (sin)
  42. {
  43. sin->sin_family = AF_INET;//地址族
  44. sin->sin_port = skb->h.uh->source;//端口号
  45. sin->sin_addr.s_addr = skb->daddr;//ip地址,这里是目的ip地址,有点困惑?
  46. }
  47. //释放该数据包
  48. skb_free_datagram(skb);
  49. release_sock(sk);
  50. return(truesize);//返回读取(接收)到的数据的大小
  51. }
上面在数据处理方面,调用了三个数据报文处理函数(net\inet\Datagram.c):skb_recv_datagram()、skb_copy_datagram()、skb_free_datagram() 

skb_recv_datagram()

  1. /*
  2. * Get a datagram skbuff, understands the peeking, nonblocking wakeups and possible
  3. * races. This replaces identical code in packet,raw and udp, as well as the yet to
  4. * be released IPX support. It also finally fixes the long standing peek and read
  5. * race for datagram sockets. If you alter this routine remember it must be
  6. * re-entrant.
  7. */
  8. //从接收队列中获取数据包
  9. //需要注意的是,这些函数(非udp.c文件下)或没有明确指明只与udp协议相关的函数则都是通用的
  10. //在tcp和udp协议下都可被调用
  11. struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err)
  12. {
  13. struct sk_buff *skb;
  14. unsigned long intflags;
  15. /* Socket is inuse - so the timer doesn't attack it */
  16. save_flags(intflags);
  17. restart:
  18. sk->inuse = 1;//加锁
  19. //检查套接字接收队列中是否有数据包
  20. //如果没有,则睡眠等待,在睡眠等待之前必须检查等待的必要性
  21. while(skb_peek(&sk->receive_queue) == NULL) /* No data */
  22. {
  23. /* If we are shutdown then no more data is going to appear. We are done */
  24. //检查套接字是否已经被关闭接收通道,已经关闭通道了就没必要盲目等待了
  25. if (sk->shutdown & RCV_SHUTDOWN)
  26. {
  27. release_sock(sk);//对于udp无用,因为udp没有采用back_log暂存队列
  28. *err=0;
  29. return NULL;
  30. }
  31. //发生错误,则需要首先处理错误,返回
  32. if(sk->err)
  33. {
  34. release_sock(sk);
  35. *err=-sk->err;
  36. sk->err=0;
  37. return NULL;
  38. }
  39. /* Sequenced packets can come disconnected. If so we report the problem */
  40. //状态检查,如果不符合则置错误标志并返回
  41. if(sk->type==SOCK_SEQPACKET && sk->state!=TCP_ESTABLISHED)
  42. {
  43. release_sock(sk);
  44. *err=-ENOTCONN;
  45. return NULL;
  46. }
  47. /* User doesn't want to wait */
  48. //不阻塞,即调用者要求不进行睡眠等待,则直接返回
  49. if (noblock)
  50. {
  51. release_sock(sk);
  52. *err=-EAGAIN;
  53. return NULL;
  54. }
  55. //系列篇前面介绍过该函数的一个主要功能是重新接收back_log缓存队列中的数据包
  56. //由于udp协议不会使用back_log队列(用于tcp超时重发),所以该函数不会对套接字接收队列造成影响
  57. release_sock(sk);
  58. /* Interrupts off so that no packet arrives before we begin sleeping.
  59. Otherwise we might miss our wake up */
  60. cli();
  61. //经过前面的一系列检查,这里再次判断是否队列中没有数据包
  62. //因为很有可能在上面检查过程中,有数据包到达
  63. if (skb_peek(&sk->receive_queue) == NULL)
  64. {
  65. interruptible_sleep_on(sk->sleep);//进入睡眠等待
  66. /* Signals may need a restart of the syscall */
  67. if (current->signal & ~current->blocked)
  68. {
  69. restore_flags(intflags);;
  70. *err=-ERESTARTSYS;
  71. return(NULL);
  72. }
  73. if(sk->err != 0) /* Error while waiting for packet
  74. eg an icmp sent earlier by the
  75. peer has finally turned up now */
  76. {
  77. *err = -sk->err;
  78. sk->err=0;
  79. restore_flags(intflags);
  80. return NULL;
  81. }
  82. }
  83. sk->inuse = 1;//该套接字目前正在被本进程使用,不能被其余场所使用
  84. restore_flags(intflags);//恢复现场
  85. }//end while
  86. /* Again only user level code calls this function, so nothing interrupt level
  87. will suddenly eat the receive_queue */
  88. //如果接收队列中存在数据包
  89. //处理正常读取的情况
  90. if (!(flags & MSG_PEEK))
  91. {
  92. skb=skb_dequeue(&sk->receive_queue);//从队列中获取数据包
  93. if(skb!=NULL)
  94. skb->users++;//使用该数据包的模块数+1
  95. else
  96. goto restart; /* Avoid race if someone beats us to the data */
  97. }
  98. //如果设置了MSG_PEEK标志,允许查看已可读取的数据
  99. //处理预先读取的情况
  100. else
  101. {
  102. cli();
  103. skb=skb_peek(&sk->receive_queue);
  104. if(skb!=NULL)
  105. skb->users++;
  106. restore_flags(intflags);
  107. if(skb==NULL) /* shouldn't happen but .. */
  108. *err=-EAGAIN;
  109. }
  110. return skb;//返回该数据包
  111. }
skb_copy_datagram()

  1. //将内核缓冲区中数据复制到用户缓冲区
  2. //拷贝size大小skb数据包中的数据负载(由offset偏移定位)到to缓冲区中
  3. void skb_copy_datagram(struct sk_buff *skb, int offset, char *to, int size)
  4. {
  5. /* We will know all about the fraglist options to allow >4K receives
  6. but not this release */
  7. //函数原型:memcpy_tofs(to,from,n) :功能一目了然
  8. memcpy_tofs(to,skb->h.raw+offset,size);
  9. }
skb_free_datagram()

  1. //释放一个数据包
  2. //先判断该数据包是否还有其余模块使用,再判断该数据包是否还处于系统的某个队列中,
  3. //换句话说,这两个判断的目的就是看该数据包是否还有用,没有用了就释放
  4. void skb_free_datagram(struct sk_buff *skb)
  5. {
  6. unsigned long flags;
  7. save_flags(flags);//保存现场
  8. cli();
  9. skb->users--;//使用该数据包的模块数-1
  10. if(skb->users>0)//如果还有模块使用该数据包,则直接返回
  11. {
  12. restore_flags(flags);
  13. return;
  14. }
  15. /* See if it needs destroying */
  16. //如果没有其余模块使用该数据包,表示这是一个游离的数据包
  17. //下面检查数据包是否仍处于系统某个队列中,如果还处于某个队列中则不可进行释放
  18. if(!skb->next && !skb->prev) /* Been dequeued by someone - ie it's read */
  19. kfree_skb(skb,FREE_READ);//否则释放该数据包所占用的内存空间
  20. restore_flags(flags);//恢复现场
  21. }
对比数据包的发送与接收,发送过程就是把数据从缓冲区拷贝到数据包的数据部分,由于需要经过协议栈,所以对于数据部分区域还需要进行数据封装,添加各层的协议头。对于数据包的接收,由于本来已经处于传输层了,不需要进行数据包的解封装,直接获取套接字接收队列中的数据包(如果有),然后再将数据包中的数据部分拷贝到缓冲区。





声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/158790
推荐阅读
相关标签
  

闽ICP备14008679号