主要查看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. 多测师讲解性能测试_面试题_001高级讲师肖sir

    什么叫做性能测试?1. 软件的性能是软件的一种非功能特性,它关注的不是软件是否能够完成特定的功能,所以一般来说性能测试介入的时机是在功能测试完成之后.另外,由定义中的及时性可知性能也是一种指标,可以 ...

  2. chrome(谷歌)登录失败解决方案

    相信有很多小伙伴和我一样,同步chrome的收藏夹,这样也便于随时可以查看自己收藏的网址.但是同步文件,必须先要登录chrome账号,登录chrome账号时,总是会报黄页,或者一直加载不出来.接下来, ...

  3. 编程语言那么多,为什么偏偏是C语言成了大学的必修课?

    谁叫你不幸生在中国了?--何祚庥(中国科学院院士) 这是一本给非计算机专业的大学生的C语言的书."我不是学计算机的,为啥要学C语言?"这个问题每年在中华大地都会被问上几百万次. 被 ...

  4. Spring Boot使用Mybatis实现增删改查

    java.com.wms.model.Admin.java 1 package com.wms.model; 2 3 import java.sql.Timestamp; 4 5 public cla ...

  5. spring boot:swagger3的安全配置(swagger 3.0.0 / spring security / spring boot 2.3.3)

    一,swagger有哪些环节需要注意安全? 1,生产环境中,要关闭swagger application.properties中配置: springfox.documentation.swagger- ...

  6. centos8:linux平台查看线程(ps/pstree/top)

    一,ps/pstree/top命令所属的rpm包 pstree所属的包 [root@blog ~]# whereis pstree pstree: /usr/bin/pstree /usr/bin/p ...

  7. PHP字符串如何转换成if条件语句

    例如: $condition = "2 == 2 && 3 == 5"; if ( $condition ){ echo 1; } 怎样把 $condition 转 ...

  8. 通过jQuery来获取DropDownList的Text/Value属性值

    脚本代码: <script src="Scripts/jquery-1.4.1-vsdoc.js" type="text/javascript">& ...

  9. 阿里云MNS官方PHP版SDK缺少message tag问题处理

    最近在使用阿里云MNS官方PHP版SDK的过程中,发现发送到topic的消息,不能设置tag(其它语言版本SDK支持tag,如java,python),但在阿里云控制台页面发送是可以设置tag的. 因 ...

  10. 理解DES算法

    首先 了解对称密码加密技术:采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密.但是有时候密钥不用完全相同 只要相似也可以.因为用一个密钥可 ...