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



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


    前面介绍的函数基本上都是TCP协议的,如listen,connect,accept 等函数,这都是为可靠传输协议TCP定制的。对于另一个不可靠udp协议(通信系统其可靠性交由上层应用层负责),则主要由两个函数完成,sendto 和 recvfrom 函数。这里先介绍 sendto 函数。

    说明:sendto 和 recvfrom 函数不限于udp协议,这里只是udp协议当中是采用这两个函数实现的,所以就放在udp协议中介绍。

    对于 udp 协议的介绍和编程实现请参考下文:UDP 客户/服务器简单 Socket 程序

    简要介绍下UDP数据报格式,相比TCP数据报格式,实在是简洁不少。

                                      

    上面的各个字段含义一目了然(上面是16是表示该字段占16bit,udp头部占8字节),其中长度指的是此 UDP 数据报的长度(包括 UDP 数据报头部和 “数据” 部分)。

    一、应用层——sendto 函数

    1. #include <sys/socket.h>  
    2. ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,  
    3.                const struct sockaddr *to, socklen_t *addrlen);  
    4. //若成功则返回写的字节数,出错则返回-1  
    5. /*参数解析 
    6. 前面三个参数分别表示:套接字描述符,指向写出缓冲区的指针和写字节数。 
    7. to:指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen参数指定 
    8. */  
    该函数的作用是:向指定端口发送给定地址中的指定大小数据(如客户端sockfd,向 to 指定的远端套接字发送buff 缓冲区内nbytes 个字节数据)

    二、BSD Socket层——sock_sendto 函数

    1. /* 
    2.  *  Send a datagram to a given address. We move the address into kernel 
    3.  *  space and check the user space data area is readable before invoking 
    4.  *  the protocol. 
    5.  */  
    6. //发送数据给指定的远端地址,主要用于UDP协议  
    7. //前面三个参数分别表示套接口描述字、指向缓冲区的指针和读写字节数  
    8. //addr指向一个含有数据包接收者的协议地址(含ip地址和端口号)的套接口地址结构  
    9. //其大小由addr_len参数指定  
    10. //该函数的作用就是向指定地址的远端发送数据包:将buff缓冲区中len大小的数据发送给addr指定的远端套接字  
    11. static int sock_sendto(int fd, void * buff, int len, unsigned flags,  
    12.        struct sockaddr *addr, int addr_len)  
    13. {  
    14.     struct socket *sock;  
    15.     struct file *file;  
    16.     char address[MAX_SOCK_ADDR];  
    17.     int err;  
    18.     //参数有效性检查  
    19.     if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))  
    20.         return(-EBADF);  
    21.     //找到给定文件描述符对应的socket结构  
    22.     if (!(sock = sockfd_lookup(fd, NULL)))  
    23.         return(-ENOTSOCK);  
    24.   
    25.     if(len<0)  
    26.         return -EINVAL;  
    27.     //检查权限,buff中len个字节区域是否可读  
    28.     err=verify_area(VERIFY_READ,buff,len);  
    29.     if(err)  
    30.         return err;  
    31.     //从addr拷贝addr_len大小的数据到address  
    32.     if((err=move_addr_to_kernel(addr,addr_len,address))<0)  
    33.         return err;  
    34.     //调用下层函数sendto,inet域为inet_sendto函数  
    35.     return(sock->ops->sendto(sock, buff, len, (file->f_flags & O_NONBLOCK),  
    36.         flags, (struct sockaddr *)address, addr_len));  
    37. }  
    三、INET Socket层——inet_sendto 函数
    1. //INET socket层  
    2. tatic int inet_sendto(struct socket *sock, void *ubuf, int size, int noblock,   
    3.     unsigned flags, struct sockaddr *sin, int addr_len)  
    4.   
    5.    //得到socket对应的sock结构  
    6. struct sock *sk = (struct sock *) sock->data;  
    7. //判断该套接字的有效性,是否处于关闭状态(半关闭)  
    8. if (sk->shutdown & SEND_SHUTDOWN)   
    9. {  
    10.     send_sig(SIGPIPE, current, 1);  
    11.     return(-EPIPE);  
    12. }  
    13. if (sk->prot->sendto == NULL)   
    14.     return(-EOPNOTSUPP);  
    15. if(sk->err)  
    16.     return inet_error(sk);  
    17. /* We may need to bind the socket. */  
    18. //自动绑定一个本地端口号  
    19. if(inet_autobind(sk)!=0)  
    20.     return -EAGAIN;  
    21. //调用下层传输层函数udp_sendto函数  
    22. return(sk->prot->sendto(sk, (unsigned char *) ubuf, size, noblock, flags,   
    23.            (struct sockaddr_in *)sin, addr_len));  
    四、传输层

    udp_sento 函数

    1. static int udp_sendto(struct sock *sk, unsigned char *from, int len, int noblock,  
    2.        unsigned flags, struct sockaddr_in *usin, int addr_len)  
    3. {  
    4.     struct sockaddr_in sin;  
    5.     int tmp;  
    6.   
    7.     /*  
    8.      *  Check the flags. We support no flags for UDP sending 
    9.      */  
    10.      //udp除了MSG_DONTROUTE外,不支持任何其他标志位  
    11.     if (flags&~MSG_DONTROUTE)   
    12.         return(-EINVAL);  
    13.     /* 
    14.      *  Get and verify the address.  
    15.      */  
    16.     //对远端地址的合法性检查,由于不涉及网络数据传送,所以无法验证这个地址存在性  
    17.       
    18.     if (usin)   
    19.     {  
    20.     //如果明确指定远端地址,就直接检查该地址的有效性  
    21.         if (addr_len < sizeof(sin)) //大小  
    22.             return(-EINVAL);  
    23.         memcpy(&sin,usin,sizeof(sin));  
    24.         if (sin.sin_family && sin.sin_family != AF_INET) //本地地址有效性  
    25.             return(-EINVAL);  
    26.         if (sin.sin_port == 0) //端口号有效性  
    27.             return(-EINVAL);  
    28.     }   
    29.     else   
    30.     {  
    31.     //如果没有明确指定远端地址,则检查之前是否调用了connect函数进行了地址绑定  
    32.         if (sk->state != TCP_ESTABLISHED)   
    33.             return(-EINVAL);  
    34.         //如果进行了绑定,则将远端地址设置为这个绑定的地址  
    35.         sin.sin_family = AF_INET;  
    36.         sin.sin_port = sk->dummy_th.dest;  
    37.         sin.sin_addr.s_addr = sk->daddr;  
    38.     }  
    39.     
    40.     /* 
    41.      *  BSD socket semantics. You must set SO_BROADCAST to permit 
    42.      *  broadcasting of data. 
    43.      */  
    44.     //处理尚未指定本地地址的情况  
    45.     if(sin.sin_addr.s_addr==INADDR_ANY)  
    46.         sin.sin_addr.s_addr=ip_my_addr();  
    47.   
    48.     //处理广播的情况  
    49.     if(!sk->broadcast && ip_chk_addr(sin.sin_addr.s_addr)==IS_BROADCAST)  
    50.             return -EACCES;         /* Must turn broadcast on first */  
    51.   
    52.     sk->inuse = 1;//加锁  
    53.   
    54.     /* Send the packet. */  
    55.     //转调用udp_send函数  
    56.     tmp = udp_send(sk, &sin, from, len, flags);  
    57.   
    58.     /* The datagram has been sent off.  Release the socket. */  
    59.     //数据包以发送,释放该套接字,前面介绍到这个函数的两个功能  
    60.     //取决于sk_dead字段是否设置  
    61.     release_sock(sk);  
    62.     return(tmp);  
    63. }  
    udp_send 函数
    1.  //根据被调用出清楚参数情况  
    2. static int udp_send(struct sock *sk, struct sockaddr_in *sin,  
    3.      unsigned char *from, int len, int rt)  
    4. {  
    5.     struct sk_buff *skb;  
    6.     struct device *dev;  
    7.     struct udphdr *uh;  
    8.     unsigned char *buff;  
    9.     unsigned long saddr;  
    10.     int size, tmp;  
    11.     int ttl;  
    12.     
    13.     /*  
    14.      *  Allocate an sk_buff copy of the packet. 
    15.      */  
    16.     //计算所需要分配的封装数据的缓冲区大小   
    17.     size = sk->prot->max_header + len;  
    18.     //分配指定大小的sk_buff 结构用于封装数据  
    19.     skb = sock_alloc_send_skb(sk, size, 0, &tmp);  
    20.   
    21.   
    22.     if (skb == NULL)   
    23.         return tmp;  
    24.   
    25.     skb->sk       = NULL;    /* to avoid changing sk->saddr */  
    26.     skb->free     = 1;//发送完后数据包立即释放,udp不提供超时重传  
    27.     skb->localroute = sk->localroute|(rt&MSG_DONTROUTE);//指定路由类型  
    28.   
    29.     /* 
    30.      *  Now build the IP and MAC header.  
    31.      */  
    32.        
    33.     buff = skb->data;//udp首部和有效负载  
    34.     saddr = sk->saddr;//本地地址  
    35.     dev = NULL;  
    36.     ttl = sk->ip_ttl;  
    37. #ifdef CONFIG_IP_MULTICAST  
    38.     //如果目的地址是多播,则设置TTL值为1,表示局限于本地网络,不可跨越路由器  
    39.   
    40.     if (MULTICAST(sin->sin_addr.s_addr))  
    41.         ttl = sk->ip_mc_ttl;  
    42. #endif  
    43.     //创建MAC首部和IP首部  
    44.     tmp = sk->prot->build_header(skb, saddr, sin->sin_addr.s_addr,  
    45.             &dev, IPPROTO_UDP, sk->opt, skb->mem_len,sk->ip_tos,ttl);  
    46.   
    47.     skb->sk=sk;//关联  /* So memory is freed correctly */  
    48.       
    49.     /* 
    50.      *  Unable to put a header on the packet. 
    51.      */  
    52.                   
    53.     if (tmp < 0 ) //创建失败  
    54.     {  
    55.         sk->prot->wfree(sk, skb->mem_addr, skb->mem_len);  
    56.         return(tmp);  
    57.     }  
    58.       
    59.     buff += tmp;//定位到udp首部位置  
    60.     saddr = skb->saddr; /*dev->pa_addr;*/  
    61.     //数据报sk_buff中挂载的数据部分长度:下面注释,len是有效数据负载长度  
    62.     skb->len = tmp + sizeof(struct udphdr) + len;    /* len + UDP + IP + MAC */  
    63.     skb->dev = dev;//网络接口设备  
    64.       
    65.     /* 
    66.      *  Fill in the UDP header.  
    67.      */  
    68.     //udp首部字段的初始化  
    69.     uh = (struct udphdr *) buff;  
    70.     uh->len = htons(len + sizeof(struct udphdr));//长度字段  
    71.     uh->source = sk->dummy_th.source;//源端端口,sk中tcp首部字段  
    72.     uh->dest = sin->sin_port;//目的端口  
    73.     buff = (unsigned char *) (uh + 1);//定位到数据部分  
    74.     //MAC header | IP Header | UDP Header | Data  
    75.     //uh本身已经指向了udp首地址,uh+1,表示后移一个udp首部大小位置,定位到了数据负载  
    76.   
    77.     /* 
    78.      *  Copy the user data.  
    79.      */  
    80.     //从from拷贝len大小的数据到buff,即把应用层中待发送的缓冲区的数据拷贝到数据包的数据负载中  
    81.     //然后通过数据包整体打包发送出去。  
    82.     //就好比货物搭上了货轮开往目的地,为啥不是火车呢,因为火车线路已经固定好了,只能这么走。  
    83.     memcpy_fromfs(buff, from, len);  
    84.   
    85.     /* 
    86.      *  Set up the UDP checksum.  
    87.      */  
    88.     //同tcp,这里进行udp校验和检查   
    89.     udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);  
    90.   
    91.     /*  
    92.      *  Send the datagram to the interface.  
    93.      */  
    94.        
    95.     udp_statistics.UdpOutDatagrams++;  
    96.     //调用ip_queue_xmit函数将数据包发往网络层模块处理。以下处理就和TCP协议一样了,二者的差异只在于传输层  
    97.     //该函数以及更下层数据传送前面已经介绍,  
    98.     sk->prot->queue_xmit(sk, dev, skb, 1);  
    99.     return(len);  
    100. }  
    关于ip_queue_xmit 函数的介绍以及更下层的数据传送,参见博文:【Linux 内核网络协议栈源码剖析】数据包发送

    可以看出,udp是一种无连接传输层协议,不像tcp那样需要服务器监听,也不必等待客户端与服务器建立连接后才能通信,效率优于tcp协议,但udp则不能保证数据传输的可靠性。
    udp 的数据传输,实现并不像tcp那样要建立一条数据传输通道,而是直接创建套接字后,直接传送数据到给定的远端(提供远端地址),数据传送过程无超时重传和序列号校验工作,适用于数据传输的连续性比数据的完整性更重要的场合,允许数据在传输过程中有部分丢失,如IP电话、流媒体通信等。



  • 相关阅读:
    包建强的培训课程(6):Android App瘦身优化
    包建强的培训课程(14):Android与ReactNative
    包建强的培训课程(13):iOS与ReactNative
    包建强的培训课程(12):iOS深入学习(内存管理、Block和GCD等)
    包建强的培训课程(11):iOS Runtime实战
    Neo4j 第九篇:查询数据(Match)
    Neo4j 第八篇:投射和过滤
    Neo4j 第七篇:模式(Pattern)
    Neo4j 第六篇:Cypher语法
    Python 学习 第14篇:数据类型(元组和集合)
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645481.html
Copyright © 2020-2023  润新知