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] ?????????×???(Э?é/???????/???)????í??错误 原因分析: 这是因为之前已开启了一 ...
随机推荐
- 【全网免费VIP观看】哔哩哔哩番剧解锁大会员-集合了优酷-爱奇艺-腾讯-芒果-乐视-ab站等全网vip视频免费破解去广告-高清普清电视观看-持续更新
哔哩哔哩番剧解锁大会员-集合了优酷-爱奇艺-腾讯-芒果-乐视-ab站等全网vip视频免费破解去广告-高清普清电视观看-持续更新 前言 突然想看电视,结果 没有VIP 又不想花钱,这免费的不久来啦. 示 ...
- centOS7 查看防火墙状态 开放端口
一.防火墙的开启.关闭.禁用命令 (1)设置开机启用防火墙:systemctl enable firewalld.service (2)设置开机禁用防火墙:systemctl disable fire ...
- go操作mysql数据库
Golang GORM使用 gorm gorm是go语言中实现数据库访问的ORM(对象关系映射)库.使用这个库,我们可以利用面向对象的方法,更加方便的对数据库中的数据进行CRUD(增删改查). 基本使 ...
- 赋予楼宇“智慧大脑”:厦门双子塔3D可视化
前言 今年10月7日,是国务院批准设立厦门经济特区40周年纪念日.1980年的这一天,国务院正式批复同意在厦门湖里地区划出一块2.5平方公里的土地,设立经济特区.厦门,成为中国最早设立的四个经济特区之 ...
- 市场清仓价格算法 python求矩阵不同行不同列元素和的最大值
问题描述 求矩阵不同行不同列元素和的最大值(最小值) 问题求解 1.通过scipy库求解 scipy.optimize库中的linear_sum_assignment方法可以求解 输入一个矩阵,参数m ...
- 500G Python从入门到进阶的视频资料
第一部分:Python资源500G百度网盘学习视频300+本电子书需要的小伙伴可以叫小编的Q群867067945 点击展开,查看完整图片 回复关键字:学习第二部分:Python就业指导 更多更详细的就 ...
- [阿里DIN]从论文源码学习 之 embedding_lookup
[阿里DIN]从论文源码学习 之 embedding_lookup 目录 [阿里DIN]从论文源码学习 之 embedding_lookup 0x00 摘要 0x01 DIN代码 1.1 Embedd ...
- JVM内存管理和垃圾回收
无论对于Java程序员还是大数据研发人员,JVM是必须掌握的技能之一.既是面试中经常问的问题,也是在实际业务中对程序进行调优.排查类似于内存溢出.栈溢出.内存泄漏等问题的关键.笔者将按下图分多篇文章详 ...
- LC滤波器简单设计法 - 一文读懂LC滤波器简单设计方法及原理介绍,LC值计算方法
LC滤波器概述 LC滤波器也称为无源滤波器,是传统的谐波补偿装置.LC滤波器之所以称为无源滤波器,顾名思义,就是该装置不需要额外提供电源.LC滤波器一般是由滤波电容器.电抗器和电阻器适当组合而成,与谐 ...
- 最全总结 | 聊聊 Python 办公自动化之 Excel(上)
1. 前言 在我们日常工作中,经常会使用 Word.Excel.PPT.PDF 等办公软件 但是,经常会遇到一些重复繁琐的事情,这时候手工操作显得效率极其低下:通过 Python 实现办公自动化变的很 ...