Socket bind系统调用简要分析
主要查看linux kernel 源码:Socket.c 以及af_inet.c文件
1.1 bind分析
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其中的参数解释如下。
·sockfd :表示要绑定地址的套接字描述符。
·addr :表示绑定到套接字的地址。
·addrlen :表示绑定的地址长度。
返回值 0 表示成功, -1 则表示错误
因为 Linux 的套接字是针对多种协议族的,而每个协议族都可以有不同的地址类型。所以 Linux 套接字关于地址的系统调用,统一使用了一个公共结构体,并要求
调用者将实际地址参数进行强制类型转换,以此来避免编译警告
/*
* 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;
//通过socket文件符fd找到socket
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) { //将地址信息由用户态copy到内核
err = move_addr_to_kernel(umyaddr, addrlen, &address);
if (err >= 0) {
err = security_socket_bind(sock,
(struct sockaddr *)&address,
addrlen);
if (!err) //调用TCP对应的插口函数执行绑定功能
err = sock->ops->bind(sock,
(struct sockaddr *)
&address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}
如果sock->ops指向inet_stream_ops,那么sock->ops->bind就指向inet_bind:
sock->ops指向inet_dgram_ops,那么sock->ops->bind就指向inet_bind:
//SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中绑定函数为inet_bind()。
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;/* BSD socket 实例 */
struct inet_sock *inet = inet_sk(sk);/* INET实例 */
unsigned short snum;
int chk_addr_ret;
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;//地址类型必须是struct sockaddr_in
if (addr_len < sizeof(struct sockaddr_in))
goto out; if (addr->sin_family != AF_INET) {//地址族必须是AF_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;
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;
/* snum为0表示让系统随机选择一个未使用的端口,因此是合法的。
* 如要需要绑定的端口为1 ~ 1023,则需要对应的特权。
*/
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).
* 如果套接字不在初始状态TCP_CLOSE,或者已经绑定端口了,则出错。
* 一个socket最多可以绑定一个端口,而一个端口则可能被多个socket共用。
*/
err = -EINVAL;
if (sk->sk_state != TCP_CLOSE || inet->inet_num)
goto out_release_sock;
/* 绑定地址 */
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;
}
SOCK_STREAM套接口的TCP层操作函数集实例为tcp_prot,其中端口绑定函数为inet_csk_get_port()
/* Obtain a reference to a local port for the given sock,
* if snum is zero it means select any available local port.
*/
int inet_csk_get_port(struct sock *sk, unsigned short snum)
{
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
struct inet_bind_hashbucket *head;
struct hlist_node *node;
struct inet_bind_bucket *tb;
int ret, attempts = 5;
struct net *net = sock_net(sk);
int smallest_size = -1, smallest_rover; local_bh_disable();/* 禁止下半部,防止 进程 软中断抢占 */ /* 如果snum为0,系统自动为sock选择一个端口号 */
if (!snum) {
int remaining, rover, low, high; again:
inet_get_local_port_range(&low, &high); /* 获取端口号的取值范围 */
remaining = (high - low) + 1;/* 取值范围内端口号的个数 */
smallest_rover = rover = net_random() % remaining + low;/* 随机选取范围内的一个端口 */ smallest_size = -1;
do { /* 查看端口是否属于保留的 */
if (inet_is_reserved_local_port(rover))
goto next_nolock;/* rover加1,继续 */
/* 根据端口号,确定所在的哈希桶 */
head = &hashinfo->bhash[inet_bhashfn(net, rover,
hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)/* 从头遍历哈希桶 */
if (net_eq(ib_net(tb), net) && tb->port == rover) {/* 如果端口被使用了 */
if (tb->fastreuse > 0 &&
sk->sk_reuse &&
sk->sk_state != TCP_LISTEN &&
(tb->num_owners < smallest_size || smallest_size == -1)) {
smallest_size = tb->num_owners;
smallest_rover = rover;
if (atomic_read(&hashinfo->bsockets) > (high - low) + 1 &&
!inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) {
snum = smallest_rover;
goto tb_found;
}
}/* 检查是否有端口绑定冲突,该端口是否能重用 */
if (!inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) {
snum = rover;
goto tb_found;
}
goto next;/* 此端口不可重用,看下一个 */
}
break;/* 找到了没被用的端口,退出 */
next:
spin_unlock(&head->lock);
next_nolock:
if (++rover > high)
rover = low;
} while (--remaining > 0); /* Exhausted local port range during search? It is not
* possible for us to be holding one of the bind hash
* locks if this test triggers, because if 'remaining'
* drops to zero, we broke out of the do/while loop at
* the top level, not from the 'break;' statement.
*/
ret = 1;
if (remaining <= 0) {
if (smallest_size != -1) {
snum = smallest_rover;
goto have_snum;
}
goto fail;
}
/* OK, here is the one we will use. HEAD is
* non-NULL and we hold it's mutex.
*/
snum = rover; /* 自动选择的可用端口 */
} else {
have_snum:
head = &hashinfo->bhash[inet_bhashfn(net, snum,
hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)
if (net_eq(ib_net(tb), net) && tb->port == snum)
goto tb_found;/* 发现端口在用 */
}
tb = NULL;
goto tb_not_found;
tb_found:
if (!hlist_empty(&tb->owners)) {
if (sk->sk_reuse == SK_FORCE_REUSE)
goto success; if (tb->fastreuse > 0 &&
sk->sk_reuse && sk->sk_state != TCP_LISTEN &&
smallest_size == -1) {
goto success;
} else {
ret = 1;
if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, true)) {
if (sk->sk_reuse && sk->sk_state != TCP_LISTEN &&
smallest_size != -1 && --attempts >= 0) {
spin_unlock(&head->lock);
goto again;
} goto fail_unlock;
}
}
}
tb_not_found:
ret = 1;
if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
net, head, snum)) == NULL)
goto fail_unlock;
if (hlist_empty(&tb->owners)) {/* 端口上有绑定sock时 */
if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)
tb->fastreuse = 1;
else
tb->fastreuse = 0;
} else if (tb->fastreuse &&
(!sk->sk_reuse || sk->sk_state == TCP_LISTEN))
tb->fastreuse = 0;
success:
if (!inet_csk(sk)->icsk_bind_hash)
inet_bind_hash(sk, tb, snum);
WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
ret = 0; fail_unlock:
spin_unlock(&head->lock);
fail:
local_bh_enable();
return ret;
}
我们可以指定系统自动分配端口号时,端口的区间: /proc/sys/net/ipv4/ip_local_port_range,默认为:32768 61000 也可以指定要保留的端口区间: /proc/sys/net/ipv4/ip_local_reserved_ports,默认为空 端口绑定冲突
面向连接的、传输层的协议族相关的操作函数集: /*
* Pointers to address related TCP functions
* (i.e. things that depend on the address family)
*/
struct inet_connection_sock_af_ops {
...
int (*bind_conflict) (const struct sock *sk, const struct inet_bind_bucket *tb, bool relax);
...
};
const struct inet_connection_sock_af_ops ipv4_specific = {
...
.bind_conflict = inet_csk_bind_conflict, /* 用于判断绑定端口是否冲突 */
...
}; int inet_csk_bind_conflict(const struct sock *sk,
const struct inet_bind_bucket *tb, bool relax)
{
struct sock *sk2;
struct hlist_node *node;
int reuse = sk->sk_reuse; /*
* Unlike other sk lookup places we do not check
* for sk_net here, since _all_ the socks listed
* in tb->owners list belong to the same net - the
* one this bucket belongs to. * 遍历此端口上的sock。
*/ sk_for_each_bound(sk2, node, &tb->owners) {
/* 冲突的条件1:不是同一socket、绑定在相同的设备上 */
if (sk != sk2 &&
!inet_v6_ipv6only(sk2) &&
(!sk->sk_bound_dev_if ||
!sk2->sk_bound_dev_if ||
sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) {
/* 冲突的条件2:绑定在相同的IP上
* 冲突的条件3(符合一个即满足):
* 3.1 本socket不允许重用
* 3.2 链表中的socket不允许重用
* 3.3 链表中的socket处于监听状态
*/
if (!reuse || !sk2->sk_reuse ||
sk2->sk_state == TCP_LISTEN) {
const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2);
if (!sk2_rcv_saddr || !sk_rcv_saddr(sk) ||
sk2_rcv_saddr == sk_rcv_saddr(sk))
break;
}
if (!relax && reuse && sk2->sk_reuse &&
sk2->sk_state != TCP_LISTEN) {
const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2); if (!sk2_rcv_saddr || !sk_rcv_saddr(sk) ||
sk2_rcv_saddr == sk_rcv_saddr(sk))
break;
}
}
}
return node != NULL;
}
Q: 什么情况下会出现冲突呢?
A: 同时符合以下条件才会冲突:
1. 绑定的设备相同(不允许自动选择设备)
2. 绑定的IP地址相同(不允许自动选择IP)
3 以下条件有一个成立:
3.1 要绑定的socket不允许重用
3.2 已绑定的socket不允许重用
3.3 已绑定的socket处于监听状态
3.4 relax参数为false
我们看到系统自动选择端口时,relax为false,是不允许这种情况的。
tcp: bind() fix autoselection to share ports
The current code checks for conflicts when the application requests a specific port.
If there is no conflict, then the request is granted.
On the other hand, the port autoselection done by the kernel fails when all ports are bound
even when there is a port whith no conflict available.
The fix changes port autoselection to check if there is a conflict and use it if not.
作者的意思是,在系统自动选择端口时,判断可重用端口的主要条件为:
tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != TCP_LISTEN
而其实不符合此条件、但通过bind_conflict()检查的端口也是可以重用的
简单的把上面那几行代码就这样加进去,系统自动选择端口的思路就变为:
1. 随机选取一个端口。
2. 检查其是否被使用了。
2.1 没有被使用,那么就是这个端口了,退出:)
2.2 被使用了,检查重用是否有冲突。
2.2.1 没有冲突,就重用这个端口,退出!
2.2.2 有冲突,继续遍历。
3. 端口++,重复1和2。
linux 内核后面 增加了SO_REUSEPORT, 并且多进程监听同一个端口,这个 也要考虑呗,
这个是为了解决惊群问题,进群的问题 有很多方案 为什么要 选择这一种 ,后面在一一细说
Socket bind系统调用简要分析的更多相关文章
- Socket与系统调用深度分析
学习一下对Socket与系统调用的分析分析 一.介绍 我们都知道高级语言的网络编程最终的实现都是调用了系统的Socket API编程接口,在操作系统提供的socket系统接口之上可以建立不同端口之间的 ...
- Socket与系统调用深层分析
实验背景: Socket API编程接口之上可以编写基于不同网络协议的应用程序: Socket接口在用户态通过系统调用机制进入内核: 内核中将系统调用作为一个特殊的中断来处理,以socket相关系统调 ...
- Socket shutdown close简要分析
shutdown 系统调用关闭连接的读数据通道 写数据通道 或者 读写数据通道: 关闭读通道:丢弃socket fd 读数据以及调用shutdown 后到达的数据: 关闭写通道:不同协议处理不同:t ...
- Socket connect 等简要分析
connect 系统调用 分析 #include <sys/types.h> /* See NOTES */#include <sys/socket.h>int connect ...
- 以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析
套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程. 端 ...
- socket相关系统调用的调用流程
最近一直在读内核网络协议栈源码,这里以ipv4/tcp为例对socket相关系统调用的流程做一个简要整理,这些相关系统调用的内部细节虽然各有不同,但其调用流程则基本一致: 调用流程: (1)系统调用 ...
- bind系统调用
/* * Bind a name to a socket. Nothing much to do here since it's * the protocol's responsibility to ...
- 【tomcat】启动报错:Failed to initialize end point associated with ProtocolHandler ["http-apr-8080"] java.lang.Exception: Socket bind failed 和java.net.BindException: Address already in use: JVM_Bind错误解决
背景:[新手] 将开发机子上的Tomcat连同其中的项目,一起拷贝到服务器上,启动tomcat的start.bat,然后报错如下: 问题1: Failed to initialize end poin ...
- 运行tomcat,报错:Socket bind failed: [730048] ?????????×???(Э?é/???????/???)????í??错误
运行tomcat时,报错: Socket bind failed: [730048] ?????????×???(Э?é/???????/???)????í??错误 原因分析: 这是因为之前已开启了一 ...
随机推荐
- tomcat加载失败
tomcat启动加载信息如下: Connected to server [2017-10-16 09:02:28,149] Artifact basic-admin:war exploded: Art ...
- day61 Pyhton 框架Django 04
内容回顾 1.django处理请求的流程: 1. 在浏览器的地址栏输入地址,回车发get请求: 2. wsgi模块接收请求: 3. 在urls.py文件中匹配地址,找到对应的函数: 4. 执行函数,返 ...
- Oracle函数总结
<Trunc()> 描 述(实际应用):截取小数或者日期整数 简 介:https://baike.baidu.com/item/trunc/9657216?fr=al ...
- 【C语言编程入门笔记】排序算法之快速排序,一文轻松掌握快排!
排序算法一直是c语言重点,各个算法适应不用的环境,同时,在面试时,排序算法也是经常被问到的.今天我们介绍下快速排序,简称就是快排. 1.快速排序思想: 快排使用 分治法 (Divide and con ...
- 【贪心算法】HDU 5969 最大的位或
题目内容 Vjudge链接 给出一个闭区间,找该区间内两个数,使这两个数的按位或最大. 输入格式 包含至多\(10001\)组测试数据. 第一行有一个正整数,表示数据的组数. 接下来每一行表示一组数据 ...
- 帮你解读什么是Redis缓存穿透和缓存雪崩(包含解决方案)
一.缓存处理流程 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果. 二.缓存穿透 描述: 缓存穿透是指缓存和数 ...
- 第二十五章 ansible基础
一.Ansible概述 1.什么是Ansible Ansible是一个自动化统一配置管理工具,自动化主要体现在Ansible集成了丰富模块以及功能组件,可以通过一个命令完成一系列的操作,进而能减少重复 ...
- zookeeper核心之ZAB协议就这么简单!
背景 我们都知道 Zookeeper 是基于 ZAB 协议实现的,在介绍 ZAB 协议之前,先回顾一下 Zookeeper 的起源与发展. Zookeeper 究竟是在什么样的时代背景下被提出?为了解 ...
- Jmeter入门(3)- Jmeter录制脚本
一. 录制web端 1. Badboy的介绍和安装 1.1 使用第三方工具Badboy来录制. 免费的web自动化测试工具 一个浏览器模拟工具 主要进行脚本的录制和回访,和对录制脚本进行调试,可以将脚 ...
- 为iOS编译FFmpeg静态库
为iOS编译FFmpeg静态库 环境:OS X Yosemite (版本10.10.5) Xcode (Version 7.1.1 (7B1005)) 一.资料准备: (1)ffmpeg源 ...