主要查看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系统调用简要分析的更多相关文章

  1. Socket与系统调用深度分析

    学习一下对Socket与系统调用的分析分析 一.介绍 我们都知道高级语言的网络编程最终的实现都是调用了系统的Socket API编程接口,在操作系统提供的socket系统接口之上可以建立不同端口之间的 ...

  2. Socket与系统调用深层分析

    实验背景: Socket API编程接口之上可以编写基于不同网络协议的应用程序: Socket接口在用户态通过系统调用机制进入内核: 内核中将系统调用作为一个特殊的中断来处理,以socket相关系统调 ...

  3. Socket shutdown close简要分析

    shutdown 系统调用关闭连接的读数据通道  写数据通道 或者 读写数据通道: 关闭读通道:丢弃socket fd 读数据以及调用shutdown 后到达的数据: 关闭写通道:不同协议处理不同:t ...

  4. Socket connect 等简要分析

    connect 系统调用 分析 #include <sys/types.h> /* See NOTES */#include <sys/socket.h>int connect ...

  5. 以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

    套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程. 端 ...

  6. socket相关系统调用的调用流程

    最近一直在读内核网络协议栈源码,这里以ipv4/tcp为例对socket相关系统调用的流程做一个简要整理,这些相关系统调用的内部细节虽然各有不同,但其调用流程则基本一致: 调用流程: (1)系统调用 ...

  7. bind系统调用

    /* * Bind a name to a socket. Nothing much to do here since it's * the protocol's responsibility to ...

  8. 【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 ...

  9. 运行tomcat,报错:Socket bind failed: [730048] ?????????×???(Э?é/???????/???)????í??错误

    运行tomcat时,报错: Socket bind failed: [730048] ?????????×???(Э?é/???????/???)????í??错误 原因分析: 这是因为之前已开启了一 ...

随机推荐

  1. 手把手搭建一个属于自己的在线 IDE

    背景 这几个月在公司内做一个跨前端项目之间共享组件/区块的工程,主要思路就是在 Bit 的基础上进行开发.Bit 主要目的是实现不同项目 共享 与 同步 组件/区块,大致思路如下: 在 A 项目中通过 ...

  2. 【C语言程序设计】—最近超火的小游戏—【数字炸弹】!

    ✍  准备工作和建议 一.程序的原理 在动手编程之前,得先跟大家说一下这个程序是干什么的. 我们可以称呼这个游戏为<数字炸弹>. 游戏的原理是这样: 每一轮电脑从 1 到 100 中随机抽 ...

  3. nginx优化:配置gzip压缩页面提高访问速度(nginx1.18.0)

    一,为什么nginx要使用gzip 1,压缩的作用: 页面使用gzip压缩之后, 页面大小可以压缩到原来的1/7左右, 传输速度和页面打开时间都可以大幅度提高, 有利于用户访问页面体验的提升 2,Ng ...

  4. js堆排序

    堆的预备知识 堆是一个完全二叉树. 完全二叉树: 二叉树除开最后一层,其他层结点数都达到最大,最后一层的所有结点都集中在左边(左边结点排列满的情况下,右边才能缺失结点). 大顶堆:根结点为最大值,每个 ...

  5. MATLAB中的参数估计函数详解及调用示例【联合整理】

    前言 因为最近项目上的需要,才发现MATLAB的统计工具箱中的参数估计函数,觉得很简单很好用,现在把所有的参数估计函数整理一下,并在最后面附上调用示例. 参与人员 由于时间关系,这篇随笔是两个人一起整 ...

  6. SpringBoot第四集:静态资源与首页定(2020最新最易懂)

    SpringBoot第四集:静态资源与首页定(2020最新最易懂) 问题 SpringBoot构建的项目结构如下:没有webapp目录,没有WEB-INF等目录,那么如果开发web项目,项目资源放在那 ...

  7. 白话科普,10s 了解 API

    作为一名又拍云的技术支持工程师,小拍每天都会接收到很多客户的提问.这其中,有很多客户会问:"小拍,请问云存储上传除了使用控制台的文件管理和 FTP 工具之外,有没有其他的途径进行上传呢?&q ...

  8. Python之list函数

  9. Markdown tricks

    编辑排版 仅仅了解 Markdown 语法还不够,知道这些 排版技巧 增色您的文章内容. 空格和空行 留白,从艺术角度上说,留白就是以"空白"为载体进而渲染出美的意境的艺术.从应用 ...

  10. Docker(3)- Centos 7.x 下 Docker 镜像加速配置

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 前言 默认情况下,Docker ...