zoukankan      html  css  js  c++  java
  • 【Linux 内核网络协议栈源码剖析】bind 函数剖析


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


    socket 函数并没有为套接字绑定本地地址和端口号,对于服务器端则必须显性绑定地址和端口号。bind 函数主要是服务器端使用,把一个本地协议地址赋予套接字。

    1、应用层——bind 函数

    1. #include <sys/socket.h>  
    2. int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);  
    3. /*sockfd是由socket函数返回的套接口描述字,第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度*/  

    bind 函数的功能则是将socket 套接字绑定指定的地址。

    2、BSD Socket 层——sock_bind 函数

    同样是通过一个共同的入口函数 sys_socket(参见 socket 函数剖析
    1. /* 
    2.  *  Bind a name to a socket. Nothing much to do here since it's 
    3.  *  the protocol's responsibility to handle the local address. 
    4.  * 
    5.  *  We move the socket address to kernel space before we call 
    6.  *  the protocol layer (having also checked the address is ok). 
    7.  */  
    8.  //bind函数对应的BSD层函数,用于绑定一个本地地址,服务器端  
    9.  //umyaddr表示需要绑定的地址结构,addrlen表示改地址结构的长度  
    10.  //这里的fd,即为套接字描述符  
    11. static int sock_bind(int fd, struct sockaddr *umyaddr, int addrlen)  
    12. {  
    13.     struct socket *sock;  
    14.     int i;  
    15.     char address[MAX_SOCK_ADDR];  
    16.     int err;  
    17.     //套接字参数有效性检查  
    18.     if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL)  
    19.         return(-EBADF);  
    20.     //获取fd对应的socket结构  
    21.     if (!(sock = sockfd_lookup(fd, NULL)))   
    22.         return(-ENOTSOCK);  
    23.     //将地址从用户缓冲区复制到内核缓冲区,umyaddr->address  
    24.     if((err=move_addr_to_kernel(umyaddr,addrlen,address))<0)  
    25.         return err;  
    26.     //转调用bind指向的函数,下层函数(inet_bind)  
    27.     if ((i = sock->ops->bind(sock, (struct sockaddr *)address, addrlen)) < 0)   
    28.     {  
    29.         return(i);  
    30.     }  
    31.     return(0);  
    32. }  

    sock_bind 函数主要就是将用户缓冲区的地址结构复制到内核缓冲区,然后转调用下一层的bind函数。

    该函数内部的一个用户空间与内核数据空间数据拷贝的函数

    1. //从uaddr拷贝ulen大小的数据到kaddr,实现地址用户空间到内核地址空间的数据拷贝  
    2. static int move_addr_to_kernel(void *uaddr, int ulen, void *kaddr)  
    3. {  
    4.     int err;  
    5.     if(ulen<0||ulen>MAX_SOCK_ADDR)  
    6.         return -EINVAL;  
    7.     if(ulen==0)  
    8.         return 0;  
    9.     //检查用户空间的指针所指的指定大小存储块是否可读  
    10.     if((err=verify_area(VERIFY_READ,uaddr,ulen))<0)  
    11.         return err;  
    12.     memcpy_fromfs(kaddr,uaddr,ulen);//实质是memcpy函数  
    13.     return 0;  
    14. }  
    3、INET Socket 层——inet_bind 函数
    1. /* this needs to be changed to disallow 
    2.    the rebinding of sockets.   What error 
    3.    should it return? */  
    4. //完成本地地址绑定,本地地址绑定包括IP地址和端口号两个部分  
    5. static int inet_bind(struct socket *sock, struct sockaddr *uaddr,int addr_len)  
    6. {  
    7.     struct sockaddr_in *addr=(struct sockaddr_in *)uaddr;  
    8.     struct sock *sk=(struct sock *)sock->data, *sk2;  
    9.     unsigned short snum = 0 /* Stoopid compiler.. this IS ok */;  
    10.     int chk_addr_ret;  
    11.   
    12.     /* check this error. */  
    13.     //在进行地址绑定时,该套接字应该处于关闭状态  
    14.     if (sk->state != TCP_CLOSE)  
    15.         return(-EIO);  
    16.     //地址长度字段校验  
    17.     if(addr_len<sizeof(struct sockaddr_in))  
    18.         return -EINVAL;  
    19.   
    20.     //非原始套接字类型,绑定前,没有端口号,则绑定端口号  
    21.     if(sock->type != SOCK_RAW)  
    22.     {  
    23.         if (sk->num != 0)//从inet_create函数可以看出,非原始套接字类型,端口号是初始化为0的   
    24.             return(-EINVAL);  
    25.   
    26.         snum = ntohs(addr->sin_port);//将地址结构中的端口号转为主机字节顺序  
    27.   
    28.         /* 
    29.          * We can't just leave the socket bound wherever it is, it might 
    30.          * be bound to a privileged port. However, since there seems to 
    31.          * be a bug here, we will leave it if the port is not privileged. 
    32.          */  
    33.          //如果端口号为0,则自动分配一个  
    34.         if (snum == 0)   
    35.         {  
    36.             snum = get_new_socknum(sk->prot, 0);//得到一个新的端口号  
    37.         }  
    38.         //端口号有效性检验,1024以上,超级用户权限  
    39.         if (snum < PROT_SOCK && !suser())   
    40.             return(-EACCES);  
    41.     }  
    42.     //下面则进行ip地址绑定  
    43.     //检查地址是否是一个本地接口地址  
    44.     chk_addr_ret = ip_chk_addr(addr->sin_addr.s_addr);  
    45.     //如果指定的地址不是本地地址,并且也不是一个多播地址,则错误返回  
    46.     if (addr->sin_addr.s_addr != 0 && chk_addr_ret != IS_MYADDR && chk_addr_ret != IS_MULTICAST)  
    47.         return(-EADDRNOTAVAIL); /* Source address MUST be ours! */  
    48.     //如果没有指定地址,则系统自动分配一个本地地址      
    49.     if (chk_addr_ret || addr->sin_addr.s_addr == 0)  
    50.         sk->saddr = addr->sin_addr.s_addr;//本地地址绑定  
    51.       
    52.     if(sock->type != SOCK_RAW)  
    53.     {  
    54.         /* Make sure we are allowed to bind here. */  
    55.         cli();  
    56.       
    57.         //for循环主要是检查检查有无冲突的端口号以及本地地址,有冲突,但不允许地址复用,肯定错误退出  
    58.         //成功跳出for循环时,已经定位到了哈希表sock_array指定索引的链表的末端  
    59.         for(sk2 = sk->prot->sock_array[snum & (SOCK_ARRAY_SIZE -1)];  
    60.                     sk2 != NULL; sk2 = sk2->next)   
    61.         {  
    62.         /* should be below! */  
    63.             if (sk2->num != snum) //没有重复,继续搜索下一个  
    64.                 continue;//除非有重复,否则后面的代码将不会被执行  
    65.             if (!sk->reuse)//端口号重复,如果没有设置地址复用标志,退出  
    66.             {  
    67.                 sti();  
    68.                 return(-EADDRINUSE);  
    69.             }  
    70.               
    71.             if (sk2->num != snum)   
    72.                 continue;       /* more than one */  
    73.             if (sk2->saddr != sk->saddr) //地址和端口一个意思  
    74.                 continue;   /* socket per slot ! -FB */  
    75.             //如果状态是LISTEN表明该套接字是一个服务端,服务端不可使用地址复用选项  
    76.             if (!sk2->reuse || sk2->state==TCP_LISTEN)   
    77.             {  
    78.                 sti();  
    79.                 return(-EADDRINUSE);  
    80.             }  
    81.         }  
    82.         sti();  
    83.   
    84.         remove_sock(sk);//将sk sock结构从其之前的表中删除,inet_create中 put_sock,这里remove_sock  
    85.         put_sock(snum, sk);//然后根据新分配的端口号插入到新的表中。可以得知系统在维护许多这样的表  
    86.         sk->dummy_th.source = ntohs(sk->num);//tcp首部,源端口号绑定  
    87.         sk->daddr = 0;//sock结构所代表套接字的远端地址  
    88.         sk->dummy_th.dest = 0;//tcp首部,目的端口号  
    89.     }  
    90.     return(0);  
    91. }  
    inet_bind 函数即为bind函数的最底层实现,该函数实现了本地地址和端口号的绑定,其中还针对上层传过来的地址结构进行校验,检查是否冲突可用。需要清楚的是 sock_array数组,这其实是一个链式哈希表,里面保存的就是各个端口号的sock结构,数组大小小于端口号,所以采用链式哈希表存储。

    bind 函数的各层分工很明显,主要就是inet_bind函数了,在注释里说的很明确了,bind 是绑定本地地址,它不负责对端地址,一般用于服务器端,客户端是系统指定的。

    一般是服务器端调用这个函数,到了这一步,服务器端套接字绑定了本地地址信息(ip地址和端口号),但是不知道对端(客户端)的地址信息。


  • 相关阅读:
    CXF JaxWsDynamicClientFactory 错误:编码GBK的不可映射字符
    关于springboot配置DataSource
    Spring Boot2.0加载多个数据源
    Kettle配置发送邮件
    推荐几个不错的VUE UI框架
    vue基础语法一
    Maven在Eclipse下构建多模块项目过程
    利用eclipse把jar包安装到本地仓库
    设计模式之策略模式
    设计模式之观察者模式
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645498.html
Copyright © 2011-2022 走看看