首页 > 代码库 > Bind系统调用

Bind系统调用

Bind含义

一条tcp/udp 连接一般是有五元组进行标识的

{<proto>, <src addr>, <src port>, <dst addr>, <dst port>}

  Bind一般是在server端调用,通过bind,会把本端的地址和端口号与socket描述符进行绑定,而目的地址和端口在connect的时候才能确定;对于client端是不需要bind(但是可以bind),client只要在connect的时候指定目的地址和端口,远端的地址和端口内核会自动填充。这其实也解释了为什么server端需要bind,因为需要一个众所周知的地址来让client进行连接。

SO_REUSEADDR

一般情况下,同一个端口只允许bind一次,port是不能复用的,但是如果设置了端口复用的话,就另当别论了。

在bind的时候,我们可以指定地址为INADDR_ANY(0.0.0.0 for ipv4, ::for ipv6, wildcard address),这个地址意味这所有的本地地址,我们在连接的时候必须要选择一个具体的地址的。但是如果一个socketA bind了INADDR_ANY 和端口P,那么本地其他的任何进程都不能使用端口P。但是如果有一个socketB设置了SO_RESUEADDR选项,那么他是可以bind 端口P的。

所以选项SO_RESUEADDR的一个作用便是可以复用bind  INADDR_ANY地址的端口。

另外一个作用是复用TIME_WAIT状态的端口。TIME_WAIT状态有2个左右,第一是确保TCP的可靠的结束全双工的连接。试想在TCP断开连接的最后2个包,client收到server发送的FIN,发送ACK,如果此时ACK丢失,一段时间后sever端timeout重新发送FIN,如果client不是处于TIME_WAIT状态,client会发送RST,那么这个连接是没有被可靠的结束的。其第二个作用则是防止老的分组会在网络中消失。设想一条连接已经结束,过一段时间后,一条新的连接在相同的IP地址和端口上建立,如果上一个连接的某个分组因为在网络中delay,此时他到达client端。所以需要该状态以确保所有老的数据都消亡。

一个socket会在TIME_WAIT状态保持2MSL时间,这时如果想要在将一个socket bind到相同的地址和端口会失败。如果设置了SO_REUSEADDR则可以。

所以,SO_REUSEADDR的作用是:

  1. 可以复用bind INADDR_ANY的地址的socket的端口。
  2. 可以使用TIME_WAIT状态的socket的地址和端口。

UNP 中列举出了4种情况,第一个跟上面的2相同,第4种是允许完全重复的bind,但是要求协议支持。第二和三种其实都要求bind的本地地址不同。其实我们想一下,kernel是依靠五元组来区分一个连接的,即使是设置了REUSEADDR想要地址复用,但是依然是要能够相互区分才可以, 如上面的2是依靠TCP的TIME_WAIT状态来区分,而其他的则大部分是要求了本地的IP地址不同,。在Linux 上,经过我的实验,是要求所有的socket必须都要设置该标志才可以。

SO_REUSEPORT

该选项是允许bind 任意个socket到相同的地址和端口,只要所有的socket都设置了该选项。

Linux 实现

Linux bind的实现主要是在inet_csk_get_port.

1. 如果该socket设置了SO_RESUEPORT,且状态不为LISTEN,则允许复用。

bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN

2. 如果端口为0, 跳到4;如果指定了端口且不为0(0意味这由kernel自动选择端口),则查找端口inet_bind_bucket,如果找到(端口已经在使用),则到tb_found.没有(端口没有被使用),则到tb_not_found.

head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];
inet_bind_bucket_for_each(tb, &head->chain)
            if (net_eq(ib_net(tb), net) && tb->port == port)
                goto tb_found;
goto tb_not_found;jjj

3. 如果没有找到,则创建新的inet_bind_bucket,到达tb_found。在tb_found中,如果该端口已经有人在使用,在检查能否复用:

  • socket设置了强制复用(sk->sk_resue == SK_FORCE_RESUE)
  • socket设置了地址复用(SO_REUSEADDR, reuse 为true)且该端口的所有使用者也都允许地址复用(tb->fastreuse > 0)
  • socket 设置了端口复用(SO_REUSEPORT, sk->sk_reuseport), 且该端口的所有使用者都允许端口复用(tb->fastreuseport > 0),且socket的有效用户id等于port的有效用户id(uid_eq(tb->fastid, uid))

然后会检查复用port是否冲突bind_conflict。

如果该port没人在使用,则会设置fastresue 和fastreuseport。

 4. 此处由内核来选择port。linux 在此处做了一些优化,如使用了随机选择,使其分布均匀;如果设置了复用,则从端口的前半部分里选择端口;还有根据奇偶性做的优化。这部分代码相当的复杂,还是有些地方没看懂。

Bind系统调用