SO_REUSEADDR和SO_REUSEPORT选项

    最近看redis源码,看到redis的网络模型,借机对socket编程和TCP/IP协议做了进一步的巩固和熟悉。其中对socket选项SO_REUSEADDR和SO_REUSEPORT写了一些demo,文章根据测试结果对SO_REUSEADDR选项和SO_REUSEPORT选项做一个总结,同时对博客的总结做一个纠正。

    先来了解一下socket默认的行为:

    ·每个TCP连接都是由唯一的五元组<协议、源IP、源PORT、目的IP、目的PORT>进行标识,任何两条有效连接不可能具有完全相同的五元组。

    ·socket编程存在通配绑定(IPV4的0.0.0.0:PORT和IPV6的:::PORT)和特定的绑定(比如127.0.0.1:3602),无论我们先进行特殊的绑定然后进行通配绑定,还是先进行通配绑定然后进行特殊绑定,只要二者的IP+PORT存在冲突,就不可能绑定成功,通常出现Address already in use的错误。

    ·通常情况下,当连接主动关闭的一方进入到TIME_WAIT的状态时,该连接占用的IP+PORT不能被再次绑定并占用。

    当然,SO_RESUEADDR和SO_REUSEPORT能够改变以上三种默认的行为,但是在不同的系统上却有不同的表现。文章对Linux下和BSD(选取Mac)系统下的测试结果进行总结。以两个IP地址为例,分别是通配地址0.0.0.0、回旋地址127.0.0.1,使用3602的端口。

  SO_REUSEADDR选项

    SO_REUSEADDR选项目的是在以下两个方面改变socket默认的行为:

    ·绑定的IP+PORT存在冲突时,允许进行绑定。

    ·主动关闭连接的一方处于TIME_WAIT的时候,允许新的连接重新绑定与TIME_WAIT状态的连接有冲突的IP+PORT,并立即发送数据。

    首先,对下面表格中使用的标识进行说明,“First”表示先启动进程,“After”表示后启动;“是”表示设置了对应的标识,“否”表示没有设置;“TIME_WAIT”表示连接处于TIME_WAIT状态;“S”表示成功,“F”表示失败。

   SO_REUSEADDR对绑定的影响

    下面,我们以表格的形式给出不同系统不使用SO_REUSEADDR和使用SO_REUSEADDR时,是否允许绑定的情况对比。

   BSD系统

    首先来看BSD系统下SO_REUSEADDR的对于socket绑定行为的影响.

表1 BSD系统使用SO_REUSEADDR选项

127.0.0.1(After,否)
0.0.0.0(After,否)
127.0.0.1(After,是)
0.0.0.0(After,是)
127.0.0.1(First,否)
F F F S
0.0.0.0(First,否)
F F S F
127.0.0.1(First,是)
F F F S
0.0.0.0(First,是)
F F S F

    通过对比,我们得出在BSD系统中使用SO_REUSEADDR的以下几点结论:

    1、SO_REUSEADDR只是解决了通配绑定和具体的IP+PORT的冲突问题;无论是否使用SO_REUSEADDR,当一个IP+PORT组合已经被使用之后,另一个连接无法绑定完全相同的IP+PORT。

    2、解决通配绑定和具体的IP+PORT绑定之间并没有顺序的限制;不使用SO_REUSEADDR的时候,无论先进行通配绑定还是先进行具体绑定,另一种绑定都不可能成功;第一个启动的程序可以不用设置SO_REUSEADDR选项,但是只要后面启动的程序(无论是使用通配绑定还是使用具体的IP+PORT绑定)设置了SO_REUSEADDR选项,如果存在IP+PORT的冲突,那么绑定也能成功。

    这里需要说明一下,如果我们启动两个进程分别绑定127.0.0.1、0.0.0.0和相同的端口,如果一个启动client进程连接到127.0.0.1的话,会连接到绑定127.0.0.1地址的进程;如果只有绑定0.0.0.0的进程存在,那么client连接到该进程。

   Linux系统

    Linux系统下SO_REUSEADDR对socket绑定行为的影响,使用的Linux系统版本为Linux 2.6.32。

表2 Linux系统使用SO_REUSEADDR对绑定行为的影响

  127.0.0.1(After,否) 0.0.0.0(After,否) 127.0.0.1(After,是) 0.0.0.0(After,是)
127.0.0.1(First,否) F F F F
0.0.0.0(First,否) F F F F
127.0.0.1(First,是) F F F F
0.0.0.0(First,是) F F F F

    在Linux下,SO_REUSEADDR对于绑定的地址冲突并没有什么影响,无论是否使用SO_REUSEADDR,只要IP+PORT之间存在冲突,后面的绑定就会失败,博客中却说先进行特定绑定再进行通配绑定才能成功,否则不能成功(X)

   TIME_WAIT下SO_REUSEPORT对有冲突的IP+PORT的影响

     每一个TCP连接的socket描述符都有各自的发送缓冲区和接收缓冲区,且都可以分别使用setsockopt进行设置。连接建立之后,我们使用send、write等函数发送数据时,都是将数据写入到socket缓冲区中就立即返回,此时数据并没有立即被发送到网络上,至于什么时候将数据发送到网络上是由操作系统的调度、网络情况和接收端的通告窗口等各种因素决定。因此,从写入数据进socket的缓冲区直到数据实际被发送出去可能存在一定时间的延迟。TCP是一种可靠的连接,当主动关闭连接的一方close一个socket描述符的时候,该方将经历TIME_WAIT状态,以等待缓冲区的数据发送到对端、已经发送出的数据离开网络或者重传数据(重传断开连接的四次握手过程中的最后一个ACK),因此TIME_WAIT状态的连接使用的IP+PORT仍然被认为是一个有效的IP+PORT组合,相同机器上不能够在该IP+PORT组合上进行绑定。这个状态的持续时间可以通过改变内核参数进行设置,Linux下通过sudo sysctl -w net.ipv4.tcp.fin.timeout=value 进行设置。表3和表4总结了SO_REUSEADDR对于TIME_WAIT状态的下IP+PORT的影响。
    在下面的例子中,我们先在server端关闭连接,然后再在client端关闭连接,那么server端将进入到TIME_WAIT状态,然后在server端启动测试的进程,观察IP+PORT有冲突时SO_REUSEADDR对启动新进程的影响。

    BSD系统

表3 TIME_WAIT状态下SO_REUSEADDR对重新绑定的影响


127.0.0.1(After,否)
0.0.0.0(After,否) 127.0.0.1(After,是) 0.0.0.0(After,是)
127.0.0.1(First,否,TIME_WAIT)
F F S S
0.0.0.0(First,否,TIME_WAIT)
F F S S
127.0.0.1(First,是,TIME_WAIT)
F F S S
0.0.0.0(First,是,TIME_WAIT)
F F S S

   BSD系统下,无论原来的进程是否使用SO_REUSEADDR选项,如果当前启动进程绑定的IP+PORT存在冲突且使用了SO_REUSEADD选项,那么该进程就可以成功启动,并且能够立即接受数据;否则,不能绑定成功。此时通过netstat -an -p tcp | grep 3602查看,可以看到一个处于TIME_WAIT状态的连接和一个处于ESTABLISHED状态的连接,如图1所示。


图1 BSD系统中SO_REUSEADDR对处于TIME_WAIT状态的IP+PORT的复用

    Linux系统

表4 TIME_WAIT状态下SO_REUSEADDR对重新绑定的影响


127.0.0.1(After,否)
0.0.0.0(After,否)
127.0.0.1(After,是)
0.0.0.0(After,是)
127.0.0.1(First,否,TIME_WAIT)
F F F F
0.0.0.0(First,否,TIME_WAIT)
F F F F
127.0.0.1(First,是,TIME_WAIT)
F F S S
0.0.0.0(First,是,TIME_WAIT)
F F S S    

    Linux系统下,只有当创建处于TIME_WAIT状态的连接的进程使用了SO_REUSEADDR,并且当前要启动的绑定了有冲突的IP+PORT的进程也使用了SO_REUSEADDR选项,那么进程才能够绑定并启动成功。启动成功之后使用netstat -anpt | grep 3602查看可以看到如图2的连接状态。

图2 Linux系统中SO_REUSEADDR对处于TIME_WAIT状态的IP+PORT的复用

  SO_REUSEPORT选项

   SO_REUSEADDR解决了通配IP+PORT和具体的IP+PORT绑定之间的冲突,但是完全相同的IP+PORT绑定(无论是具体的IP还是通配)仍然出现Address already in use的错误,使用SO_REUSEPORT选项可以避免此错误。

   SO_REUSEPORT对绑定的影响

    BSD系统

表5 BSD系统SO_REUSEPORT对绑定行为的影响


127.0.0.1(After,否) 0.0.0.0(After,否) 127.0.0.1(After,是) 0.0.0.0(After,是)
127.0.0.1(First,否) F F F S
0.0.0.0(First,否) F F S F
127.0.0.1(Fitst,是) F F S S
0.0.0.0(First,是) F F S S   

    BSD系统下SO_REUSEPORT与SO_REUSEADDR对绑定行为的不同影响在于,如果先启动的进程和后启动的进程都设置了SO_REUSEADDR选项,那么即使是绑定完全相同的IP+PORT也能够启动成功;如果绑定了冲突的IP+PORT的前面的实例没有设置SO_REUSEPORT选项,但是只要后面的实例设置了SO_REUSEPORT选项,只要IP+PORT不是完全相同,后面的进程能够成功绑定并启动。比如,如果我们启动了一个绑定127.0.0.1+3602的进程,那么该进程可以再次被重复启动。下面,我们在所有的运行实例中都设置了SO_REUSEPORT选项,那么启动两次绑定127.0.0.1+3602的进程和一次绑定0.0.0.0+3602的进程之后,通过ps aux命令查看到的输出结果如图3所示,通过netstat查看连接的输出结果如图4所示。


图3 SO_REUSEPORT对绑定有冲突的IP+PORT的进程的影响


图4 SO_REUSEPORT对绑定的有冲突的IP+PORT的连接的影响

    如果所有的实例都设置了SO_REUSEPORT选项,绑定完全相同的IP+PORT的进程也能够重复启动。通过ps命令能够看到所有启动的进程,而且通过netstat命令能够看到所有启动的连接。

    Linux系统    

表6 Linux系统SO_REUSEPORT对绑定行为的影响


127.0.0.1((After,否) 0.0.0.0((After,否) 127.0.0.1((After,是) 0.0.0.0((After,是)
127.0.0.1(First,否) F F F F
0.0.0.0(First,否) F F F F
127.0.0.1(Fitst,是) F F S S
0.0.0.0(First,是) F F S S

    Linux系统下,如果两个实例绑定的IP+PORT存在冲突,那么只有当前、后启动的所有实例都设置了SO_REUSEPORT选项,进程才能够启动成功;存在冲突的实例中如果只有一个(无论前、后)设置了SO_REUSEPORT选项,那么后面启动的进程无法进行绑定,出现Address already in use的错误。所有实例都设置了SO_REUSEPORT选项的情况下,重复启动绑定127.0.0.1+3602的实例两次,绑定0.0.0.0+3602的实例一次,通过ps查看到的进程如图5所示;通过netstat查看到的连接如图5所示。

图5  Linux系统SO_REUSEPORT对启动进程的影响


图6 Linux系统SO_REUSEPORT对建立连接的影响

    对比图5和图6,发现虽然,成功启动了三个进程,但是netstat却只看到了两个连接,且是后面启动的进程的连接覆盖了前面的连接。关于这一点还是希望明白其中原理的大神解答一下。

    TIME_WAIT下SO_REUSEPORT对有冲突的IP+PORT的影响

    BSD系统

表7 BSD系统下TIME_WAIT状态下SO_REUSEPORT选项对启动进程的影响    


127.0.0.1(Second,否) 0.0.0.0(Second,否) 127.0.0.1(Second,是) 0.0.0.0(Second,是)
127.0.0.1(First,否,TIME_WAIT) F F S S
0.0.0.0(First,否,TIME_WAIT) F F S S
127.0.0.1(First,是,TIME_WAIT) F F S S
0.0.0.0(First,是,TIME_WAIT) F F S S
表8 Linux系统下TIME_WAIT状态下SO_REUSEPORT选项对启动进程的影响

127.0.0.1(Second,否)
0.0.0.0(Second,否)
127.0.0.1(Second,是)
0.0.0.0(Second,是)
127.0.0.1(First,否,TIME_WAIT)
F F F F
0.0.0.0(First,否,TIME_WAIT)
F F F F
127.0.0.1(First,是,TIME_WAIT)
F F S S
0.0.0.0(First,是,TIME_WAIT)
F F S S

    对比表7和表8,可以发现:

    ·BSD系统下无论使连接处于TIME_WAIT状态的原来的进程是否设置了SO_REUSEPORT,如果以后启动的进程绑定的IP+PORT与处于TIME_WAIT状态连接的IP+PORT存在冲突(完全相同或者是存在部分重合),那么只要以后启动的进程设置了SO_REUSEPORT,那么可以成功启动。

   ·Linux系统下,只有使处于TIME_WAIT连接状态的原来进程设置了SO_REUSEPORT选项,并且以后启动的进程绑定了有冲突的IP+PORT时也设置了SO_REUSEPORT选项,才能够成功绑定并启动以后的进程。

    以上是个人关于SO_REUSEADDR和SO_REUSEPORT选项的理解,欢迎吐槽。

猜你喜欢

转载自blog.csdn.net/GDJ0001/article/details/80801483
今日推荐