bind()函数的使用方法很简单,但是它是怎么实现的呢?

笔者从应用层出发,沿着网络协议栈,分析了bind()的系统调用、Socket层实现,以及它的TCP层实现。

本文主要内容:bind()的系统调用、bind()的Socket层实现。

内核版本:3.6

Author:zhangskd @ csdn blog

应用层

int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

bind() gives the socket sockfd the local address my_addr.

给socket描述符绑定IP和端口,一般服务器才需要。

也可交给系统来选择:

my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口 */

my_addr.sin_addr.s_addr = INADDR_ANY; /* 自动填入本机的IP地址 */

#define INADDR_ANY ((unsigned long int) 0x00000000)

端口号的范围为0 ~ 65535。

调用bind()时,一般不要把端口号置为小于1024的值,因为1到1023是保留端口号。

系统调用

bind()是由glibc提供的,声明位于include/sys/socket.h中,实现位于sysdeps/mach/hurd/bind.c中,

主要是用来从用户空间进入名为sys_socketcall的系统调用,并传递参数。sys_scoketcall()实际上是

所有socket函数进入内核空间的共同入口。

在sys_socketcall()中会调用sys_bind()。

  1. SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
  2. {
  3. ...
  4. switch(call) {
  5. ...
  6. case SYS_BIND:
  7. err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
  8. break;
  9. ...
  10. }
  11. return err;
  12. }

经过了socket层的总入口sys_socketcall(),现在进入sys_bind()。

  1. /*
  2. * Bind a name to a socket. Nothing much to do here since it's the protocol's responsibility
  3. * to handle the local address.
  4. * We move the socket address to kernel space before we call the protocol layer (having also
  5. * checked the address is ok).
  6. */
  7.  
  8. SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
  9. {
  10. struct socket *sock;
  11. struct sockaddr_storage address;
  12. int err, fput_needed;
  13.  
  14. /* 通过文件描述符fd,找到对应的socket。
  15. * 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
  16. * 然后从file实例的private_data成员中获取socket实例。
  17. */
  18. sock = sockfd_lookup_light(fd, &err, &fput_needed);
  19.  
  20. if (sock) {
  21. /* 把用户空间的地址复制到内核空间,成功返回0 */
  22. err = move_addr_to_kernel(umyaddr, addrlen, &address);
  23.  
  24. if (err >= 0) {
  25. /* SELInux相关 */
  26. err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen);
  27. if (!err)
  28. /* socket层的操作函数集。如果是SOCK_STREAM的话,proto_ops是inet_stream_ops,
  29. * 接下来调用的是inet_bind()。
  30. */
  31. err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);
  32. }
  33. fput_light(sock->file, fput_needed);
  34. }
  35. return err;
  36. }

通过文件描述符,找到对应的file结构。

  1. static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
  2. {
  3. struct file *file;
  4. struct socket *sock;
  5.  
  6. *err = -EBADF; /* Bad file number */
  7.  
  8. /* 从当前进程的files_struct中找到网络文件系统中的file指针,并增加它的引用计数 */
  9. file = fget_light(fd, fput_needed);
  10.  
  11. if (file) {
  12. sock = sock_from_file(file, err); /* 通过file找到对应的socket */
  13. if (sock)
  14. return sock;
  15. fput_light(file, *fput_needed); /* 失败的话减少file的引用计数 */
  16. }
  17. return NULL;
  18. }

通过file结构,找到对应的socket结构。

  1. struct socket *sock_from_file(struct file *file, int *err)
  2. {
  3. if (file->f_op == &socket_file_ops) /* 说明此file对应一个socket */
  4. return file->private_data; /* set in sock_map_fd */
  5.  
  6. *err = -ENOTSOCK;
  7. return NULL;
  8. }

把用户空间的socket地址复制到内核空间,同时检查是否合法,成功返回0。

  1. int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
  2. {
  3. if (ulen < 0 || ulen > sizeof(struct sockaddr_storage)) /* socket地址长度是否合法 */
  4. return -EINVAL;
  5.  
  6. if (ulen == 0)
  7. return 0;
  8.  
  9. if (copy_from_user(kaddr, uaddr, ulen))
  10. return -EFAULT; /* socket地址是否合法 */
  11.  
  12. return audit_sockaddr(ulen, kaddr);
  13. }

socket层

SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中绑定函数为inet_bind()。

  1. const struct proto_ops inet_stream_ops = {
  2. .family = PF_INET,
  3. .owner = THIS_MODULE,
  4. ...
  5. .bind = inet_bind, /* socket层的bind实现 */
  6. ...
  7. }

socket层做的主要事情为合法性检查、绑定IP地址,而真正的端口绑定是在TCP层进行的。

  1. int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
  2. {
  3. struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
  4. struct sock *sk = sock->sk; /* 传输层实例 */
  5. struct inet_sock *inet = inet_sk(sk); /* INET实例 */
  6. unsigned short snum; /* 要绑定的端口 */
  7. int chk_addr_ret; /* IP地址类型 */
  8. int err;
  9.  
  10. /* If the socket has its own bind function then use it. (RAW)
  11. * 用于原始套接字,TCP协议实例tcp_prot不含此函数指针。
  12. */
  13. if (sk->sk_prot->bind) {
  14. err = sk->sk_prot->bind(sk, uaddr, addr_len);
  15. goto out;
  16. }
  17.  
  18. err = -EINVAL;
  19.  
  20. if (addr_len < sizeof(struct sockaddr_in)) /* socket地址长度错误 */
  21. goto out;
  22.  
  23. if (addr->sin_family != AF_INET) { /* 非INET协议族 */
  24. /* Compatibility games: accept AF_UNSPEC (mapped to AF_INET)
  25. * only if s_addr is INADDR_ANY.
  26. */
  27. err = -EAFNOSUPPORT;
  28. if (addr->sin_family != AF_UNSPEC || addr->sin_addr.s_addr != htonl(INADDR_ANY))
  29. goto out;
  30. }
  31.  
  32. /* 在路由中检查IP地址类型,单播、多播还是广播 */
  33. chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
  34.  
  35. /* Not specified by any standard per-se, however it breaks too many applications
  36. * when removed. It is unfortunate since allowing applications to make a non-local
  37. * bind solves several problems with systems using dynamic addressing.
  38. * (ie. your servers still start up even if your ISDN link is temporarily down)
  39. */
  40. /* sysctl_ip_nonlocal_bind表示是否允许绑定非本地的IP地址。
  41. * inet->freebind表示是否允许绑定非主机地址。
  42. * 这里需要允许绑定非本地地址,除非是发送给自己、多播或广播。
  43. */
  44. err = -EADDRNOTAVAIL; /* Cannot assign requested address */
  45.  
  46. if (! sysctl_ip_nonlocal_bind && ! (inet->freebind || inet->transparent) &&
  47. addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
  48. chk_addr_ret != RTN_LOCAL && chk_addr_ret != RTN_MULTICAST &&
  49. chk_addr_ret != RTN_BROADCAST)
  50. goto out;
  51.  
  52. snum = ntohs(addr->sin_port); /* 要绑定的端口 */
  53.  
  54. err = -EACCES; /* Permission denied */
  55. /* snum为0表示让系统随机选择一个未使用的端口,因此是合法的。
  56. * 如要需要绑定的端口为1 ~ 1023,则需要对应的特权。
  57. */
  58. if (snum && snum < PORT_SOCK && ! capable(CAP_NET_BIND_SERVICE))
  59. goto out;
  60.  
  61. lock_sock(sk);
  62.  
  63. /* Check these errors (active socket, double bind).
  64. * 如果套接字不在初始状态TCP_CLOSE,或者已经绑定端口了,则出错。
  65. * 一个socket最多可以绑定一个端口,而一个端口则可能被多个socket共用。
  66. */
  67. err = -EINVAL;
  68. if (sk->sk_state != TCP_CLOSE || inet->inet_num)
  69. goto out_release_sock;
  70.  
  71. /* We keep a pair of addresses. rcv_saddr is the one used by hash lookups,
  72. * and saddr is used for transmit.
  73. * In the BSD API these are the same except where it would be illegal to use them
  74. * (multicast/broadcast) in which case the sending device address is used.
  75. */
  76. inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; /* 绑定地址 */
  77.  
  78. if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
  79. inet->inet_saddr = 0; /* Use device */
  80.  
  81. /* Make sure we are allowed to bind here.
  82. * 如果使用的是TCP,则sk_prot为tcp_prot,get_port为inet_csk_get_port()
  83. * 端口可用的话返回0。
  84. */
  85. if (sk->sk_prot->get_port(sk, snum)) {
  86. inet->inet_saddr = inet->inet_rcv_saddr = 0;
  87. err = -EADDRINUSE;
  88. goto out_release_sock;
  89. }
  90.  
  91. /* inet_rcv_saddr表示绑定的地址,接收数据时用于查找socket */
  92. if (inet->inet_rcv_saddr)
  93. sk->sk_userlocks |= SOCK_BINDADDR_LOCK; /* 表示绑定了本地地址 */
  94.  
  95. if (snum)
  96. sk->sk_userlocks |= SOCK_BINDPORT_LOCK; /* 表示绑定了本地端口 */
  97.  
  98. inet->inet_sport = htons(inet->inet_num); /* 绑定端口 */
  99. inet->inet_daddr = 0;
  100. inet->inet_dport = 0;
  101. sk_dst_reset(sk);
  102. err = 0;
  103.  
  104. out_release_sock:
  105. release_sock(sk);
  106.  
  107. out:
  108. return err;
  109. }
  110.  
  111. /* Sockets 0 - 1023 can't be bound to unless you are superuser */
  112. #define PORT_SOCK 1024
  113. /* Allows binding to TCP/UDP sockets below 1024 */
  114. #define CAP_NET_BIND_SERVICE 10

Socket层实现系列 — bind()的实现(一)的更多相关文章

  1. Socket层实现系列 — bind()的实现(二)

    本文主要内容:bind()的TCP层实现.端口的冲突处理,以及不同内核版本的实现差异. 内核版本:3.6 Author:zhangskd @ csdn blog TCP层实现 SOCK_STREAM套 ...

  2. Socket层实现系列 — send()类发送函数的实现

    主要内容:socket发送函数的系统调用.Socket层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 发送流程图 以下是send().sendt ...

  3. Socket层实现系列 — connect()的实现

    主要内容:connect()的Socket层实现.期间进程的睡眠和唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 应用层 int connect( ...

  4. Socket层实现系列 — getsockname()和getpeername()的实现

    本文主要介绍了getsockname()和getpeername()的内核实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int getsockname(in ...

  5. Socket层实现系列 — listen()的实现

    本文主要分析listen()的内核实现,包括它的系统调用.Socket层实现.半连接队列,以及监听哈希表. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int l ...

  6. Socket层实现系列 — 信号驱动的异步等待

    主要内容:Socket的异步通知机制. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事 ...

  7. Socket层实现系列 — 睡眠驱动的同步等待

    主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了 ...

  8. Socket层实现系列 — accept()的实现(一)

    本文主要介绍了accept()的系统调用.Socket层实现,以及TCP层实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int accept(int soc ...

  9. Socket层实现系列 — I/O事件及其处理函数

    主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...

随机推荐

  1. Java并发框架——AQS中断的支持

    线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...

  2. JAVA面向对象-----多态

    多态的概述 1:什么是多态 一个对象的多种状态 (老师)(员工)(儿子) 教师 a =老钟; 员工 b= 老钟; 2:多态体现 1:Father类 1:非静态成员变量x 2:静态成员变量y 3:非静态 ...

  3. 2.Lucene3.6.2包介绍,第一个Lucene案例介绍,查看索引信息的工具lukeall介绍,Luke查看的索引库内容,索引查找过程

     1  Lucen目录介绍 2  lucene-core-3.6.2.jar是lucene开发核心jar包 contrib  目录存放,包含一些扩展jar包 3  案例 建立第一个Lucene项目 ...

  4. 【并发编程】AIDL关键字

    oneway Oneway interfaces In early betas, the Android IPC was strictly synchronous. This means that s ...

  5. linux下查看Memcached运行状态

    查看Memcached运行状态的命令是:echo stats | nc 127.0.0.1 11211 查看memcached状态的基本命令,通过这个命令可以看到如下信息: STAT pid 2245 ...

  6. 初探linux子系统集之i2c子系统(二)

    大概也是前年了,一直没有把那个i2c的子系统讲解完,这里偷个懒,把以前整理的i2c相关的知识再梳理一下,做个了结,然后再去学习timer子系统. 先看下i2c在内核中的代码分布: obj-$(CONF ...

  7. erMaster插件

    需求: 在做开源项目时,了解基本业务后.试图从数据库表设计来分析项目.通过visio时绘制操作繁琐,另外不能与数据库连动.于是想找一款快速绘制er图,并且能够和数据库连动的软件工具. eclipse插 ...

  8. studio安装插件

    Android Studio安装插件的方式其实和Eclipse大同小异.废话不多说,直接上图: 区域1:你当前已经安装了的插件 区域2:在线安装 区域3:从硬盘安装,即针对你已经下载好了的插件,可通过 ...

  9. mysql进阶(二十五)解决数据库NO CONNECTION问题

    解决数据库NO CONNECTION问题 前言 数据库版本类型:Mysql5.5 在应用程序连接数据库时,提示数据库连接失败.打开数据库查看,显示如下. 究其原因,是因为mysql服务出现了问题,重启 ...

  10. Android官方命令深入分析之etc1tool

    etc1tool是一个命令行工具,可以将PNG图像压缩为etc1标准,并且可以进行解压缩. 用法: etc1tool infile [--help | --encode | --encodeNoHea ...