Socket层实现系列 — bind()的实现(一)
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()的实现(一)的更多相关文章
- Socket层实现系列 — bind()的实现(二)
本文主要内容:bind()的TCP层实现.端口的冲突处理,以及不同内核版本的实现差异. 内核版本:3.6 Author:zhangskd @ csdn blog TCP层实现 SOCK_STREAM套 ...
- Socket层实现系列 — send()类发送函数的实现
主要内容:socket发送函数的系统调用.Socket层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 发送流程图 以下是send().sendt ...
- Socket层实现系列 — connect()的实现
主要内容:connect()的Socket层实现.期间进程的睡眠和唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 应用层 int connect( ...
- Socket层实现系列 — getsockname()和getpeername()的实现
本文主要介绍了getsockname()和getpeername()的内核实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int getsockname(in ...
- Socket层实现系列 — listen()的实现
本文主要分析listen()的内核实现,包括它的系统调用.Socket层实现.半连接队列,以及监听哈希表. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int l ...
- Socket层实现系列 — 信号驱动的异步等待
主要内容:Socket的异步通知机制. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事 ...
- Socket层实现系列 — 睡眠驱动的同步等待
主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了 ...
- Socket层实现系列 — accept()的实现(一)
本文主要介绍了accept()的系统调用.Socket层实现,以及TCP层实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int accept(int soc ...
- Socket层实现系列 — I/O事件及其处理函数
主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...
随机推荐
- Java并发框架——AQS中断的支持
线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...
- JAVA面向对象-----多态
多态的概述 1:什么是多态 一个对象的多种状态 (老师)(员工)(儿子) 教师 a =老钟; 员工 b= 老钟; 2:多态体现 1:Father类 1:非静态成员变量x 2:静态成员变量y 3:非静态 ...
- 2.Lucene3.6.2包介绍,第一个Lucene案例介绍,查看索引信息的工具lukeall介绍,Luke查看的索引库内容,索引查找过程
1 Lucen目录介绍 2 lucene-core-3.6.2.jar是lucene开发核心jar包 contrib 目录存放,包含一些扩展jar包 3 案例 建立第一个Lucene项目 ...
- 【并发编程】AIDL关键字
oneway Oneway interfaces In early betas, the Android IPC was strictly synchronous. This means that s ...
- linux下查看Memcached运行状态
查看Memcached运行状态的命令是:echo stats | nc 127.0.0.1 11211 查看memcached状态的基本命令,通过这个命令可以看到如下信息: STAT pid 2245 ...
- 初探linux子系统集之i2c子系统(二)
大概也是前年了,一直没有把那个i2c的子系统讲解完,这里偷个懒,把以前整理的i2c相关的知识再梳理一下,做个了结,然后再去学习timer子系统. 先看下i2c在内核中的代码分布: obj-$(CONF ...
- erMaster插件
需求: 在做开源项目时,了解基本业务后.试图从数据库表设计来分析项目.通过visio时绘制操作繁琐,另外不能与数据库连动.于是想找一款快速绘制er图,并且能够和数据库连动的软件工具. eclipse插 ...
- studio安装插件
Android Studio安装插件的方式其实和Eclipse大同小异.废话不多说,直接上图: 区域1:你当前已经安装了的插件 区域2:在线安装 区域3:从硬盘安装,即针对你已经下载好了的插件,可通过 ...
- mysql进阶(二十五)解决数据库NO CONNECTION问题
解决数据库NO CONNECTION问题 前言 数据库版本类型:Mysql5.5 在应用程序连接数据库时,提示数据库连接失败.打开数据库查看,显示如下. 究其原因,是因为mysql服务出现了问题,重启 ...
- Android官方命令深入分析之etc1tool
etc1tool是一个命令行工具,可以将PNG图像压缩为etc1标准,并且可以进行解压缩. 用法: etc1tool infile [--help | --encode | --encodeNoHea ...