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



    http://blog.csdn.net/wenqian1991/article/details/46907717


    继前篇介绍完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.   
    35.     if(len<0)  
    36.         return len;  
    37.     //对比可知,这里是sock_recvfrom相比sock_sendto多出来的一部分  
    38.     //它的作用便是将发送端(客户端)的地址信息填充到addr中,就是获取客户端的地址信息  
    39.     if(addr!=NULL && (err=move_addr_to_user(address,alen, addr, addr_len))<0)  
    40.         return err;  
    41.   
    42.     return len;  
    43. }  
    三、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.       
    11.     if (sk->prot->recvfrom == NULL)   
    12.         return(-EOPNOTSUPP);  
    13.     if(sk->err)  
    14.         return inet_error(sk);  
    15.     /* We may need to bind the socket. */  
    16.     //检查是否绑定了端口,没有的话就自动绑定一个,就服务器端而言,肯定是有的  
    17.     if(inet_autobind(sk)!=0)  
    18.         return(-EAGAIN);  
    19.     //调用下层udp_recvfrom函数  
    20.     return(sk->prot->recvfrom(sk, (unsigned char *) ubuf, size, noblock, flags,  
    21.                  (struct sockaddr_in*)sin, addr_len));  
    22. }  

    四、传输层——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.     /* 
    16.      *  Check any passed addresses 
    17.      */  
    18.        
    19.     if (addr_len)   
    20.         *addr_len=sizeof(*sin);  
    21.     
    22.     /* 
    23.      *  From here the generic datagram does a lot of the work. Come 
    24.      *  the finished NET3, it will do _ALL_ the work! 
    25.      */  
    26.     //从接收队列中获取数据包     
    27.     skb=skb_recv_datagram(sk,flags,noblock,&er);  
    28.     if(skb==NULL)  
    29.         return er;  
    30.       
    31.     //数据包数据部分(数据报)长度  
    32.     truesize = skb->len;  
    33.     //读取长度检查设置,udp是面向报文的,其接收到的每个数据包都是独立的  
    34.     //如果用户要求读取的小于可读取的,那么剩下的将被丢弃(本版本协议栈就是这么干的)  
    35.     copied = min(len, truesize);  
    36.   
    37.     /* 
    38.      *  FIXME : should use udp header size info value  
    39.      */  
    40.     //拷贝skb数据包中的数据负载到to缓冲区中   
    41.     //这里就是数据转移的地方,将数据从数据包中转移出来到缓存区  
    42.     skb_copy_datagram(skb,sizeof(struct udphdr),to,copied);  
    43.     sk->stamp=skb->stamp;//记录时间  
    44.   
    45.     /* Copy the address. */  
    46.     //如果要求返回远端地址的话,这里就是拷贝远端地址信息了,含端口号和ip地址  
    47.     if (sin)   
    48.     {  
    49.         sin->sin_family = AF_INET;//地址族  
    50.         sin->sin_port = skb->h.uh->source;//端口号  
    51.         sin->sin_addr.s_addr = skb->daddr;//ip地址,这里是目的ip地址,有点困惑?  
    52.     }  
    53.     //释放该数据包  
    54.     skb_free_datagram(skb);  
    55.     release_sock(sk);  
    56.     return(truesize);//返回读取(接收)到的数据的大小  
    57. }  
    上面在数据处理方面,调用了三个数据报文处理函数(netinetDatagram.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.   
    16.     /* Socket is inuse - so the timer doesn't attack it */  
    17.     save_flags(intflags);  
    18. restart:  
    19.     sk->inuse = 1;//加锁  
    20.     //检查套接字接收队列中是否有数据包  
    21.     //如果没有,则睡眠等待,在睡眠等待之前必须检查等待的必要性  
    22.     while(skb_peek(&sk->receive_queue) == NULL)  /* No data */  
    23.     {  
    24.         /* If we are shutdown then no more data is going to appear. We are done */  
    25.         //检查套接字是否已经被关闭接收通道,已经关闭通道了就没必要盲目等待了  
    26.         if (sk->shutdown & RCV_SHUTDOWN)  
    27.         {  
    28.             release_sock(sk);//对于udp无用,因为udp没有采用back_log暂存队列  
    29.             *err=0;  
    30.             return NULL;  
    31.         }  
    32.         //发生错误,则需要首先处理错误,返回  
    33.         if(sk->err)  
    34.         {  
    35.             release_sock(sk);  
    36.             *err=-sk->err;  
    37.             sk->err=0;  
    38.             return NULL;  
    39.         }  
    40.   
    41.         /* Sequenced packets can come disconnected. If so we report the problem */  
    42.         //状态检查,如果不符合则置错误标志并返回  
    43.         if(sk->type==SOCK_SEQPACKET && sk->state!=TCP_ESTABLISHED)  
    44.         {  
    45.             release_sock(sk);  
    46.             *err=-ENOTCONN;  
    47.             return NULL;  
    48.         }  
    49.   
    50.         /* User doesn't want to wait */  
    51.         //不阻塞,即调用者要求不进行睡眠等待,则直接返回  
    52.         if (noblock)  
    53.         {  
    54.             release_sock(sk);  
    55.             *err=-EAGAIN;  
    56.             return NULL;  
    57.         }  
    58.   
    59.         //系列篇前面介绍过该函数的一个主要功能是重新接收back_log缓存队列中的数据包  
    60.     //由于udp协议不会使用back_log队列(用于tcp超时重发),所以该函数不会对套接字接收队列造成影响  
    61.         release_sock(sk);  
    62.   
    63.         /* Interrupts off so that no packet arrives before we begin sleeping. 
    64.            Otherwise we might miss our wake up */  
    65.         cli();  
    66.         //经过前面的一系列检查,这里再次判断是否队列中没有数据包  
    67.         //因为很有可能在上面检查过程中,有数据包到达  
    68.         if (skb_peek(&sk->receive_queue) == NULL)  
    69.         {  
    70.             interruptible_sleep_on(sk->sleep);//进入睡眠等待  
    71.             /* Signals may need a restart of the syscall */  
    72.             if (current->signal & ~current->blocked)  
    73.             {  
    74.                 restore_flags(intflags);;  
    75.                 *err=-ERESTARTSYS;  
    76.                 return(NULL);  
    77.             }  
    78.             if(sk->err != 0) /* Error while waiting for packet 
    79.                            eg an icmp sent earlier by the 
    80.                            peer has finally turned up now */  
    81.             {  
    82.                 *err = -sk->err;  
    83.                 sk->err=0;  
    84.                 restore_flags(intflags);  
    85.                 return NULL;  
    86.             }  
    87.         }  
    88.         sk->inuse = 1;//该套接字目前正在被本进程使用,不能被其余场所使用  
    89.         restore_flags(intflags);//恢复现场  
    90.       }//end while  
    91.       /* Again only user level code calls this function, so nothing interrupt level 
    92.          will suddenly eat the receive_queue */  
    93.   
    94.      //如果接收队列中存在数据包  
    95.      //处理正常读取的情况  
    96.       if (!(flags & MSG_PEEK))  
    97.       {  
    98.         skb=skb_dequeue(&sk->receive_queue);//从队列中获取数据包  
    99.         if(skb!=NULL)  
    100.             skb->users++;//使用该数据包的模块数+1  
    101.         else  
    102.             goto restart;   /* Avoid race if someone beats us to the data */  
    103.       }  
    104.       //如果设置了MSG_PEEK标志,允许查看已可读取的数据  
    105.       //处理预先读取的情况  
    106.       else  
    107.       {  
    108.         cli();  
    109.         skb=skb_peek(&sk->receive_queue);  
    110.         if(skb!=NULL)  
    111.             skb->users++;  
    112.         restore_flags(intflags);  
    113.         if(skb==NULL)   /* shouldn't happen but .. */  
    114.             *err=-EAGAIN;  
    115.       }  
    116.       return skb;//返回该数据包  
    117. }  
    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.   
    8.     save_flags(flags);//保存现场  
    9.     cli();  
    10.     skb->users--;//使用该数据包的模块数-1  
    11.     if(skb->users>0)//如果还有模块使用该数据包,则直接返回  
    12.     {  
    13.         restore_flags(flags);  
    14.         return;  
    15.     }  
    16.     /* See if it needs destroying */  
    17.     //如果没有其余模块使用该数据包,表示这是一个游离的数据包  
    18.     //下面检查数据包是否仍处于系统某个队列中,如果还处于某个队列中则不可进行释放  
    19.     if(!skb->next && !skb->prev)  /* Been dequeued by someone - ie it's read */  
    20.         kfree_skb(skb,FREE_READ);//否则释放该数据包所占用的内存空间  
    21.     restore_flags(flags);//恢复现场  
    22. }  
    对比数据包的发送与接收,发送过程就是把数据从缓冲区拷贝到数据包的数据部分,由于需要经过协议栈,所以对于数据部分区域还需要进行数据封装,添加各层的协议头。对于数据包的接收,由于本来已经处于传输层了,不需要进行数据包的解封装,直接获取套接字接收队列中的数据包(如果有),然后再将数据包中的数据部分拷贝到缓冲区。

  • 相关阅读:
    读《程序是怎样跑起来的》第七章有感
    读《程序是怎样跑起来的》第六章有感
    读《程序是怎样跑起来的》第五章有感
    读《程序是怎样跑起来的》第四章有感
    读《程序是怎样跑起来的》第三章有感
    读《怎样成为一个高手 183》有感
    读《程序是怎样跑起来的》第二章有感
    《程序是怎样跑起来的》第一章读后感
    我与计算机
    师生关系
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645479.html
Copyright © 2020-2023  润新知