程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 網絡編程常用接口的內核實現----sys_bind()

網絡編程常用接口的內核實現----sys_bind()

編輯:C++入門知識

  bind()系統調用是給套接字分配一個本地協議地址,對於網際協議,協議地址是32位IPv4地址或128位IPv6地址與16位的TCP或UDP端口號的組合。如果沒有通過bind()來指定本地的協議地址,在和遠端通信時,內核會隨機給套接字分配一個IP地址和端口號。bind()系統調用通常是在網絡程序的服務器端調用,而且是必須的。如果TCP服務器不這麼做,讓內核來選擇臨時端口號而不是捆綁眾所周知的端口,客戶端如何發起與服務器的連接? 一、sys_bind()   bind()系統調用對應的內核實現是sys_bind(),其源碼及分析如下: [cpp]   /*   *  Bind a name to a socket. Nothing much to do here since it's   *  the protocol's responsibility to handle the local address.   *   *  We move the socket address to kernel space before we call   *  the protocol layer (having also checked the address is ok).   */      SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)   {       struct socket *sock;       struct sockaddr_storage address;       int err, fput_needed;          /*       * 以fd為索引從當前進程的文件描述符表中       * 找到對應的file實例,然後從file實例的private_data中       * 獲取socket實例。       */   www.2cto.com     sock = sockfd_lookup_light(fd, &err, &fput_needed);       if (sock) {           /*           * 將用戶空間的地址拷貝到內核空間的緩沖區中。           */           err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);           if (err >= 0) {               /*               * SELinux相關,不需要關心。               */               err = security_socket_bind(sock,                              (struct sockaddr *)&address,                              addrlen);               /*               * 如果是TCP套接字,sock->ops指向的是inet_stream_ops,               * sock->ops是在inet_create()函數中初始化,所以bind接口               * 調用的是inet_bind()函數。               */               if (!err)                   err = sock->ops->bind(sock,                                 (struct sockaddr *)                                 &address, addrlen);           }           fput_light(sock->file, fput_needed);       }       return err;   }     sys_bind()的代碼流程如下圖所示:      sys_bind()首先調用sockfd_lookup_light()查找套接字對應的socket實例,如果沒有找到,則返回EBADF錯誤。在進行綁定操作之前,要先將用戶傳入的本地協議地址從用戶空間拷貝到內核緩沖區中,在拷貝過程中會檢查用戶傳入的地址是否正確。如果指定的長度參數小於0或者大於sockaddr_storage的大小,則返回EINVAL錯誤;如果在調用copy_from_user()執行拷貝操作過程中出現錯誤,則返回EFAULT錯誤。在上述的准備工作都完成後,調用inet_bind()函數(即sock->ops->bind指向的函數,參見注釋)來完成綁定操作。 二、inet_bind()   inet_bind()比較簡單,不做過多的分析,注釋的已經很清楚了。代碼及注釋如下所示: [cpp]   int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)   {       struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;       struct sock *sk = sock->sk;       struct inet_sock *inet = inet_sk(sk);       unsigned short snum;       int chk_addr_ret;       int err;          /* If the socket has its own bind function then use it. (RAW) */       /*       * 如果是TCP套接字,sk->sk_prot指向的是tcp_prot,在       * inet_create()中調用的sk_alloc()函數中初始化。由於       * tcp_prot中沒有設置bind接口,因此判斷條件不成立。       */       if (sk->sk_prot->bind) {           err = sk->sk_prot->bind(sk, uaddr, addr_len);           goto out;       }       err = -EINVAL;       if (addr_len < sizeof(struct sockaddr_in))           goto out;          /*       * 判斷傳入的地址類型。       */       chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);          /* Not specified by any standard per-se, however it breaks too       * many applications when removed.  It is unfortunate since       * allowing applications to make a non-local bind solves       * several problems with systems using dynamic addressing.       * (ie. your servers still start up even if your ISDN link       *  is temporarily down)       */       err = -EADDRNOTAVAIL;       /*       * 如果系統不支持綁定本地地址,或者       * 傳入的地址類型有誤,則返回EADDRNOTAVAIL       * 錯誤。       */       if (!sysctl_ip_nonlocal_bind &&           !(inet->freebind || inet->transparent) &&           addr->sin_addr.s_addr != htonl(INADDR_ANY) &&           chk_addr_ret != RTN_LOCAL &&           chk_addr_ret != RTN_MULTICAST &&           chk_addr_ret != RTN_BROADCAST)           goto out;          snum = ntohs(addr->sin_port);       err = -EACCES;       /*       * 如果綁定的端口號小於1024(保留端口號),但是       * 當前用戶沒有CAP_NET_BIND_SERVICE權限,則返回EACCESS錯誤。       */       if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))           goto out;          /*      We keep a pair of addresses. rcv_saddr is the one       *      used by hash lookups, and saddr is used for transmit.       *       *      In the BSD API these are the same except where it       *      would be illegal to use them (multicast/broadcast) in       *      which case the sending device address is used.       */       lock_sock(sk);          /* Check these errors (active socket, double bind). */       err = -EINVAL;       /*       * 如果套接字狀態不是TCP_CLOSE(套接字的初始狀態,參見       * sock_init_data()函數),或者已經綁定過,則返回EINVAL錯誤。       */       if (sk->sk_state != TCP_CLOSE || inet->num)           goto out_release_sock;          inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;       if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)           inet->saddr = 0;  /* Use device */          /* Make sure we are allowed to bind here. */       /*       * 這裡實際調用的是inet_csk_get_port()函數。       * 檢查要綁定的端口號是否已經使用,如果已經使用,       * 則檢查是否允許復用。如果檢查失敗,則返回       * EADDRINUSE錯誤。       */       if (sk->sk_prot->get_port(sk, snum)) {           inet->saddr = inet->rcv_saddr = 0;           err = -EADDRINUSE;           goto out_release_sock;       }          /*       * rcv_saddr存儲的是已綁定的本地地址,接收數據時使用。       * 如果已綁定的地址不為0,則設置SOCK_BINDADDR_LOCK標志,       * 表示已綁定本地地址。       */       if (inet->rcv_saddr)           sk->sk_userlocks |= SOCK_BINDADDR_LOCK;       /*       * 如果綁定的端口號不為0,則設置SOCK_BINDPORT_LOCK標志,       * 表示已綁定本地端口號。       */       if (snum)           sk->sk_userlocks |= SOCK_BINDPORT_LOCK;       inet->sport = htons(inet->num);       inet->daddr = 0;       inet->dport = 0;       /*       * 重新初始化目的路由緩存項,如果之前已設置,則       * 調用dst_release()釋放老的路由緩存項。       */       sk_dst_reset(sk);       err = 0;   out_release_sock:       release_sock(sk);   out:       return err;   }  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved