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()。

SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
{
...
switch(call) {
...
case SYS_BIND:
err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
break;
...
}
return err;
}

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

/*
* 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,找到对应的socket。
* 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
* 然后从file实例的private_data成员中获取socket实例。
*/
sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) {
/* 把用户空间的地址复制到内核空间,成功返回0 */
err = move_addr_to_kernel(umyaddr, addrlen, &address); if (err >= 0) {
/* SELInux相关 */
err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen);
if (!err)
/* socket层的操作函数集。如果是SOCK_STREAM的话,proto_ops是inet_stream_ops,
* 接下来调用的是inet_bind()。
*/
err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}

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

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
struct file *file;
struct socket *sock; *err = -EBADF; /* Bad file number */ /* 从当前进程的files_struct中找到网络文件系统中的file指针,并增加它的引用计数 */
file = fget_light(fd, fput_needed); if (file) {
sock = sock_from_file(file, err); /* 通过file找到对应的socket */
if (sock)
return sock;
fput_light(file, *fput_needed); /* 失败的话减少file的引用计数 */
}
return NULL;
}

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

struct socket *sock_from_file(struct file *file, int *err)
{
if (file->f_op == &socket_file_ops) /* 说明此file对应一个socket */
return file->private_data; /* set in sock_map_fd */ *err = -ENOTSOCK;
return NULL;
}

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

int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
{
if (ulen < 0 || ulen > sizeof(struct sockaddr_storage)) /* socket地址长度是否合法 */
return -EINVAL; if (ulen == 0)
return 0; if (copy_from_user(kaddr, uaddr, ulen))
return -EFAULT; /* socket地址是否合法 */ return audit_sockaddr(ulen, kaddr);
}

socket层

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

const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
...
.bind = inet_bind, /* socket层的bind实现 */
...
}

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

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); /* INET实例 */
unsigned short snum; /* 要绑定的端口 */
int chk_addr_ret; /* IP地址类型 */
int err; /* If the socket has its own bind function then use it. (RAW)
* 用于原始套接字,TCP协议实例tcp_prot不含此函数指针。
*/
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)) /* socket地址长度错误 */
goto out; if (addr->sin_family != AF_INET) { /* 非INET协议族 */
/* Compatibility games: accept AF_UNSPEC (mapped to AF_INET)
* only if s_addr is INADDR_ANY.
*/
err = -EAFNOSUPPORT;
if (addr->sin_family != AF_UNSPEC || addr->sin_addr.s_addr != htonl(INADDR_ANY))
goto out;
} /* 在路由中检查IP地址类型,单播、多播还是广播 */
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)
*/
/* sysctl_ip_nonlocal_bind表示是否允许绑定非本地的IP地址。
* inet->freebind表示是否允许绑定非主机地址。
* 这里需要允许绑定非本地地址,除非是发送给自己、多播或广播。
*/
err = -EADDRNOTAVAIL; /* Cannot assign requested address */ 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; /* Permission denied */
/* snum为0表示让系统随机选择一个未使用的端口,因此是合法的。
* 如要需要绑定的端口为1 ~ 1023,则需要对应的特权。
*/
if (snum && snum < PORT_SOCK && ! capable(CAP_NET_BIND_SERVICE))
goto out; lock_sock(sk); /* Check these errors (active socket, double bind).
* 如果套接字不在初始状态TCP_CLOSE,或者已经绑定端口了,则出错。
* 一个socket最多可以绑定一个端口,而一个端口则可能被多个socket共用。
*/
err = -EINVAL;
if (sk->sk_state != TCP_CLOSE || inet->inet_num)
goto out_release_sock; /* 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.
*/
inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; /* 绑定地址 */ if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
inet->inet_saddr = 0; /* Use device */ /* Make sure we are allowed to bind here.
* 如果使用的是TCP,则sk_prot为tcp_prot,get_port为inet_csk_get_port()
* 端口可用的话返回0。
*/
if (sk->sk_prot->get_port(sk, snum)) {
inet->inet_saddr = inet->inet_rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
} /* inet_rcv_saddr表示绑定的地址,接收数据时用于查找socket */
if (inet->inet_rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK; /* 表示绑定了本地地址 */ if (snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK; /* 表示绑定了本地端口 */ inet->inet_sport = htons(inet->inet_num); /* 绑定端口 */
inet->inet_daddr = 0;
inet->inet_dport = 0;
sk_dst_reset(sk);
err = 0; out_release_sock:
release_sock(sk); out:
return err;
} /* Sockets 0 - 1023 can't be bound to unless you are superuser */
#define PORT_SOCK 1024
/* Allows binding to TCP/UDP sockets below 1024 */
#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. 详解EBS接口开发之应收INVOICE导入

    (一)应收INVOICE常用标准表简介 1.1   常用标准表 如下表中列出了与应收INVOICE导入相关的表和说明: 表名 说明 其他信息 RA_BATCH_SOURCES_ALL AR事务处理来源 ...

  2. 基于BaseAdapter的Listview小Demo

    ListView是android开发中比较常用的控件, 其中适配器模式可以选择: ArrayAdapter:简单易用,通常用于将数组或者List集合的读个包值封装成多个列表项 SimpleAdapte ...

  3. JAVA面向对象-----抽象类

    1抽象类 为什么使用抽象类 1:定义Dog类 有颜色属性和叫的方法 2:定义Bird类 有颜色属性和叫的方法 3:定义其父类Animal 1:颜色的属性可以使用默认初始化值. 2:叫的方法在父类中如何 ...

  4. Dynamics CRM2016 新功能之Solution enhancements

    CRM2016中对解决方案的功能有了一定的加强,CRM自2011版本开始引入了solution的概念,但大家的共识是solution的导出导入以及发布都非常的慢,常常会出现发布超时的情况很是头疼. 以 ...

  5. 剑指Offer——网易笔试之解救小易——曼哈顿距离的典型应用

    剑指Offer--网易笔试之解救小易--曼哈顿距离的典型应用 前言 首先介绍一下曼哈顿,曼哈顿是一个极为繁华的街区,高楼林立,街道纵横,从A地点到达B地点没有直线路径,必须绕道,而且至少要经C地点,走 ...

  6. CSDN专访:大数据时代下的商业存储

    原文地址:http://www.csdn.net/article/2014-06-03/2820044-cloud-emc-hadoop 摘要:EMC公司作为全球信息存储及管理产品方面的领先公司,不久 ...

  7. 【Unity Shaders】使用Unity Render Textures实现画面特效——建立画面特效脚本系统

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  8. 自定义android 4.0以上的对话框风格

    做个笔记,这里是Dialog的风格,如果是用AlertDialog创建的,不能直接用.在styles.xml的写法: <style name="DialogWindowTitle&qu ...

  9. Java中怎么简单的使用正则表达式?

    对于正则表达式,我通常的认识就是通过一些陌生的奇怪的符号就可以完成很复杂事件的好帮手!实际上正则表达式确实是这方面的好助手,接下来让我们一起认识一下Java中怎么使用正则表达式吧. 初见Pattern ...

  10. 【原创】Eclipse vs. IDEA快捷键对比大全

    花了一天时间熟悉IDEA的各种操作,将各种快捷键都试了一下,感觉很是不错!于是就整理了一下我经常用的一些Eclipse快捷键与IDEA的对比,方便像我一样使用Eclipse多年但想尝试些改变的同学们. ...