从Linux源码看Socket(TCP)的bind (2)

由于,我们在使用bind的时候很少随机端口号(在TCP服务器来说尤其如此),这段代码笔者就注释一下。一般只有一些特殊的远程过程调用(RPC)中会使用随机Server端随机端口号。

第二段,找到端口号或已经指定 have_snum: inet_bind_bucket_for_each(tb, &head->chain) if (net_eq(ib_net(tb), net) && tb->port == snum) goto tb_found; } tb = NULL; goto tb_not_found tb_found: // 如果此port已被bind 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) || (tb->fastreuseport > 0 && sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && smallest_size == -1) { // 这个分支表明之前bind的port和当前sock都设置了reuse同时当前sock状态不为listen // 或者同时设置了reuseport而且是同一个uid(注意,设置了reuseport后,可以同时listen同一个port了) 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) || (tb->fastreuseport > 0 && sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && smallest_size != -1 && --attempts >= 0) { // 若冲突,但是设置了reuse非listen状态或者设置了reuseport且出在同一个用户下 // 则可以进行重试 spin_unlock(&head->lock); goto again; } goto fail_unlock; } // 不冲突,走下面的逻辑 } tb_not_found: if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep, net, head, snum)) == NULL) goto fail_unlock; // 设置fastreuse // 设置fastreuseport success: ...... // 将当前sock链入tb->owner,同时tb->num_owners++ inet_bind_hash(sk, tb, snum); ret = 0; // 返回bind(绑定)成功 return ret; 判断端口号是否冲突

在上述源码中,判断端口号时否冲突的代码为

inet_csk(sk)->icsk_af_ops->bind_conflict 也即 inet_csk_bind_conflict int inet_csk_bind_conflict(const struct sock *sk, const struct inet_bind_bucket *tb, bool relax){ ...... sk_for_each_bound(sk2, &tb->owners) { // 这边判断表明,必须同一个接口(dev_if)才进入下内部分支,也就是说不在同一个接口端口的不冲突 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)) { if ((!reuse || !sk2->sk_reuse || sk2->sk_state == TCP_LISTEN) && (!reuseport || !sk2->sk_reuseport || (sk2->sk_state != TCP_TIME_WAIT && !uid_eq(uid, sock_i_uid(sk2))))) { // 在有一方没设置reuse且sock2状态为listen 同时 // 有一方没设置reuseport且sock2状态不为time_wait同时两者的uid不一样的时候 const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2); if (!sk2_rcv_saddr || !sk_rcv_saddr(sk) || // ip地址一样,才算冲突 sk2_rcv_saddr == sk_rcv_saddr(sk)) break; } // 非放松模式,ip地址一样,才算冲突 ...... return sk2 != NULL; } ...... }

上面代码的逻辑如下图所示:

从Linux源码看Socket(TCP)的bind

SO_REUSEADDR和SO_REUSEPORT

上面的代码有点绕,笔者就讲一下,对于我们日常开发要关心什么。
我们在上面的bind里面经常见到sk_reuse和sk_reuseport这两个socket的Flag。这两个Flag能够决定是否能够bind(绑定)成功。这两个Flag的设置在C语言里面如下代码所示:

setsockopt(sockfd_server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)); setsockopt(sockfd_server, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 }, sizeof(int));

在原生JAVA中

// java8中,原生的socket并不支持so_reuseport ServerSocket server = new ServerSocket(port); server.setReuseAddress(true);

在Netty(Netty版本 >= 4.0.16且Linux内核版本>=3.9以上)中,可以使用SO_REUSEPORT。

SO_REUSEADDR

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwdzw.html