Linux TCP套接字选项 之 SO_REUSEADDR && SO_REUSEPORT

thlm0 2019-10-28

说明

前面从stackoverflow上找了一篇讲这两个选项的文章,文章内容很长,读到最后对Linux中的这两个选项还是有些迷茫,所以重新写一篇文章来做一个总结;

本文只总结TCP单播部分,并且只讨论该选项的bind()系统调用部分,UDP,组播,开启选项之后数据包的调度等不做讨论;

man手册中对这两个套接字的描述
SO_REUSEADDR
SO_REUSEADDR
       Indicates  that  the  rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses.  For AF_INET
       sockets this means that a socket may bind, except when there is an active listening socket bound to the address.   When  the  listening
       socket  is bound to INADDR_ANY with a specific port then it is not possible to bind to this port for any local address.  Argument is an
       integer boolean flag.

原文简译:让bind()系统调用能够重用本地地址;对于AF_INET协议族的套接字来讲,除非有活动的监听套接字绑定到该地址,否则其他情况下可以重复绑定;如果一个监听套接字绑定到了通配地址INADDR_ANY,那么其他任何绑定到该端口的请求都是不允许的;

解释:

  • (1)不启用SO_REUSEADDR选项的情况下,或者(2)启用了SO_REUSEADDR选项但已绑定连接处于LISTEN状态下,任何重复绑定都不允许,包括:

(1) 如果原监听绑定是指定地址,则相同IP+相同端口,通配IP+相同端口不允许;但是不同IP+相同端口的绑定是允许的;

(2) 若果原监听绑定是通配地址,则任何向该端口的绑定都不可以;

如下面表格所示:

已绑定                请求绑定              成功与否
------------------------------------------------
192.168.0.100:22345  192.168.0.100:22345  失败
192.168.0.100:22345  0.0.0.0:22345        失败
192.168.0.100:22345  192.168.0.101:22345  成功
0.0.0.0:22345        192.168.0.100:22345  失败
0.0.0.0:22345        0.0.0.0:22345        失败
  • 在启用SO_REUSEADDR选项,并且已绑定的连接处于非LISTEN状态,则可以重复绑定;这也是使用SO_REUSEADDR的主要作用,即服务器重启时,当已绑定连接处于TIME_WAIT状态时,允许重复绑定;上面表格列出的所有情况均可以绑定成功;
  • 补充:SO_REUSEADDR选项要求必须每个绑定到该端口的套接字都开启,这点与BSD有区别,BSD只要当前请求绑定的开启就可以;
SO_REUSEPORT
SO_REUSEPORT
      Permits multiple AF_INET or AF_INET6 sockets to be bound to an identical socket address.  This  option  must  be  set  on  each  socket
      (including  the  first  socket) prior to calling bind(2) on the socket.  To prevent port hijacking, all of the processes binding to the
      same address must have the same effective UID.  This option can be employed with both TCP and UDP sockets.

      For TCP sockets, this option allows accept(2) load distribution in a multi-threaded server to be improved by using a distinct  listener
      socket  for  each  thread.   This  provides  improved  load  distribution  as  compared  to  traditional techniques such using a single
      accept(2)ing thread that distributes connections, or having multiple threads that compete to accept(2) from the same socket.

      For UDP sockets, the use of this option can provide better distribution of incoming datagrams to multiple  processes  (or  threads)  as
      compared to the traditional technique of having multiple processes compete to receive datagrams on the same socket.

原文简译:该选项允许完全相同地址重复绑定;需要重复绑定的每个套接字都要设置该选项,包括第一个;为了防止端口劫持,重复绑定必须是统一有效用户ID下的进程;在TCP服务器中,多个线程同时监听相同地址,则每个线程的accpet()调用会进行负载均衡,将请求进行平均分发;这与传统的套接字相比提供了更好的负载均衡,原方式比如单一线程accpet()来分发请求,或者多个accept()从一个套接字上竞争请求;

关于绑定,没有什么疑问,SO_REUSEADDR中列出表格中的地址都能绑定成功;

内核4.12.12中关于重用部分冲突检查的源码解析
if ((!reuse || !sk2->sk_reuse || sk2->sk_state == TCP_LISTEN) 
    && (!reuseport || !sk2->sk_reuseport || rcu_access_pointer(sk->sk_reuseport_cb) 
       || (sk2->sk_state != TCP_TIME_WAIT && !uid_eq(uid, sock_i_uid(sk2))))) {
        if (inet_rcv_saddr_equal(sk, sk2, true))
                    break;
}

源码解析,分以下四种情况:

未启用地址重用 && 未启用端口重用:检查冲突;

启用了地址重用 && 未启用端口重用:状态是LISTEN才检查冲突;

未启用地址重用 && 启用了端口重用:状态不是TIME_WAIT并且不是同一有效用户ID时,检查冲突;也就是说,假若是TIME_WAIT,则不需要检查;假如不是TIME_WAIT,但是有效用户ID相同,也不需要检查;

启用了地址重用 && 启用了端口重用:状态是LISTEN时,可能需要检查,需要继续判断端口重用,这时候只当有效用户ID不相同的时候,才需要检查;也就是说,可以相同用户ID的进程可以同时LISTEN多个相同的地址+端口;

相关推荐

LandryBean / 0评论 2011-02-06