当前位置: 首页 > 知识库问答 >
问题:

SO_REUSEADDR和SO_REUSEPORT有何不同?

慕弘义
2023-03-14

如果我在不同的操作系统上使用任何一个,那么预期的行为是什么?

共有1个答案

百里诚
2023-03-14

欢迎来到可移植性的奇妙世界...或者更确切地说是缺乏。在我们开始详细分析这两个选项并更深入地了解不同的操作系统如何处理它们之前,需要注意的是,BSD套接字实现是所有套接字实现之母。基本上,所有其他系统都在某个时间点复制了BSD套接字实现(或至少复制了它的接口),然后开始自己发展它。当然,BSD套接字实现也同时得到了发展,因此后来复制它的系统得到了以前复制它的系统所缺乏的特性。理解BSD套接字实现是理解所有其他套接字实现的关键,因此,即使您不想为BSD系统编写代码,也应该阅读它。

在我们研究这两个选项之前,您应该了解一些基本知识。TCP/UDP连接由五个值组成的元组标识:

{ , , , , }

到目前为止,所有主要操作系统所说的都是相同的。当地址重用开始发挥作用时,事情开始变得特定于操作系统。我们从BSD开始,因为如上所述,它是所有套接字实现的母体。

如果在绑定套接字之前启用了so_reuseaddr,则可以成功绑定套接字,除非与绑定到源地址和端口完全相同组合的另一套接字发生冲突。现在你可能想知道这和以前有什么不同吗?关键字是“确切”。so_reuseaddr主要更改搜索冲突时处理通配符地址(“任何IP地址”)的方式。

如果没有so_reuseaddr,将socketa绑定到0.0.0.0:21,然后将socketb绑定到192.168.0.1:21将失败(错误为eaddrinuse),因为0.0.0.0表示“任何本地IP地址”,因此所有本地IP地址都被该套接字使用,其中也包括192.168.0.1。使用so_reuseaddr将会成功,因为0.0.0.0192.168.0.1不是完全相同的地址,一个是所有本地地址的通配符,另一个是非常特定的本地地址。请注意,无论socketasocketb是按哪个顺序绑定的,上面的语句都是真的;如果没有so_reuseaddr,它总是失败,如果有so_reuseaddr,它总是成功。

为了给你一个更好的概述,我们在这里做一个表格,列出所有可能的组合:

SO_REUSEADDR       socketA        socketB       Result
---------------------------------------------------------------------
  ON/OFF       192.168.0.1:21   192.168.0.1:21    Error (EADDRINUSE)
  ON/OFF       192.168.0.1:21      10.0.0.1:21    OK
  ON/OFF          10.0.0.1:21   192.168.0.1:21    OK
   OFF             0.0.0.0:21   192.168.1.0:21    Error (EADDRINUSE)
   OFF         192.168.1.0:21       0.0.0.0:21    Error (EADDRINUSE)
   ON              0.0.0.0:21   192.168.1.0:21    OK
   ON          192.168.1.0:21       0.0.0.0:21    OK
  ON/OFF           0.0.0.0:21       0.0.0.0:21    Error (EADDRINUSE)

上表假设socketa已经成功绑定到为socketa给定的地址,然后创建socketb,或者设置so_reuseaddr,最后绑定到为socketb给定的地址。resultsocketb的绑定操作的结果。如果第一列显示on/off,则so_reuseaddr的值与结果无关。

好的,so_reuseaddr对通配符地址有影响,很好知道。但这并不是它的唯一效果。还有另一个众所周知的效果,这也是大多数人首先在服务器程序中使用so_reuseaddr的原因。对于此选项的另一个重要用途,我们必须更深入地了解TCP协议的工作方式。

问题是,系统如何处理状态为time_wait的套接字?如果未设置so_reuseaddr,则将状态为time_wait的套接字视为仍绑定到源地址和端口,并且任何将新套接字绑定到相同地址和端口的尝试都将失败,直到套接字真正关闭为止,这可能需要与配置的延迟时间一样长的时间。所以不要期望在关闭套接字后立即重新绑定它的源地址。在大多数情况下,这将失败。但是,如果为您试图绑定的套接字设置了so_reuseaddr,则绑定到同一地址和端口的另一个状态为time_wait的套接字将被忽略,毕竟它已经“半死不活”了,您的套接字可以绑定到完全相同的地址,不会有任何问题。在这种情况下,其他套接字可能具有完全相同的地址和端口并不起作用。请注意,在time_wait状态下,将一个套接字绑定到与一个正在死亡的套接字完全相同的地址和端口,如果另一个套接字仍在“工作”,可能会产生意想不到的、通常是不希望的副作用,但这超出了本答案的范围,幸运的是,这些副作用在实践中相当罕见。

关于so_reuseaddr您还应该知道最后一件事。只要您要绑定到的套接字启用了地址重用,上面写的所有内容都将工作。另一个套接字,即已经绑定或处于time_wait状态的套接字,在绑定时也没有必要设置此标志。决定绑定成功还是失败的代码只检查输入bind()调用的套接字的SO_REUSEADDR标志,对于检查的所有其他套接字,甚至不查看该标志。

so_reuseport是大多数人所期望的so_reuseaddr。基本上,so_reuseport允许您将任意数量的套接字绑定到完全相同的源地址和端口,只要所有先前绑定的套接字在绑定之前也设置了so_reuseport。如果绑定到地址和端口的第一个套接字未设置SO_REUSEPORT,则在第一个套接字再次释放其绑定之前,无论该套接字是否设置了SO_REUSEPORT,都不能将其他套接字绑定到完全相同的地址和端口。与so_reuesaddr不同,处理so_reuseport的代码不仅验证当前绑定的套接字是否设置了so_reuseport,而且还验证具有冲突地址和端口的套接字在绑定时是否设置了so_reuseport

SO_reusePort并不意味着SO_reuseAddr。这意味着,如果一个套接字在绑定时没有设置SO_REUSEPORT,而另一个套接字在绑定到完全相同的地址和端口时设置了SO_REUSEPORT,则绑定失败(这是意料之中的),但如果另一个套接字已经死亡并处于TIME_WAIT状态,则绑定也会失败。要能够将套接字绑定到与time_wait状态中的另一套接字相同的地址和端口,需要在该套接字上设置SO_reuseAddr或者在绑定它们之前必须在两个套接字上设置SO_reusePort。当然,允许在套接字上同时设置so_reuseportso_reuseaddr

关于so_reuseport,除了它比so_reuseaddr添加得晚之外,没有太多可说的,这就是为什么您在其他系统的许多套接字实现中找不到它的原因,这些系统在添加此选项之前“分叉”了BSD代码,而且在添加此选项之前,没有办法将两个套接字绑定到BSD中完全相同的套接字地址。

大多数人都知道bind()可能会失败,并出现错误eaddrinuse。但是,当您开始使用地址重用时,您可能会遇到一种奇怪的情况,即connect()也会失败,并出现该错误。怎么会这样呢?connect添加到套接字中的远程地址怎么可能已经在使用呢?将多个套接字连接到完全相同的远程地址以前从来都不是问题,那么这里出了什么问题呢?

正如我在回答的最上面所说的,连接是由五个值组成的元组定义的,记得吗?我还说过,这五个值必须是唯一的,否则系统就不能再区分两个连接了,对吧?那么,通过地址重用,您可以将相同协议的两个套接字绑定到相同的源地址和端口。这意味着这五个值中的三个对于这两个套接字已经是相同的。如果您现在尝试将这两个套接字也连接到相同的目的地址和端口,您将创建两个连接的套接字,它们的元组完全相同。这不能起作用,至少对于TCP连接不起作用(UDP连接无论如何都不是真正的连接)。如果数据到达两个连接中的任何一个,则系统无法判断数据属于哪个连接。至少目的地址或目的端口对于任何一个连接必须是不同的,这样系统就没有问题识别传入数据属于哪个连接。

因此,如果您将相同协议的两个套接字绑定到相同的源地址和端口,并尝试将它们都连接到相同的目的地址和端口,那么connect()实际上将失败,您尝试连接的第二个套接字将出现错误eaddrinuse,这意味着已经连接了一个具有五个值的相同元组的套接字。

大多数人忽略了组播地址存在的事实,但它们确实存在。单播地址用于一对一通信,多播地址用于一对多通信。大多数人在了解IPv6时就知道了组播地址,但IPv4中也存在组播地址,尽管这一特性从未在公共Internet上得到广泛应用。

所有这些都是原始BSD代码的后期分支,这就是为什么它们都提供了与BSD相同的选项,并且它们的行为方式也与BSD相同。

在其核心,macOS只是一个名为“Darwin”的BSD风格的UNIX,基于相当晚的BSD代码分叉(BSD4.3),后来甚至与Mac OS 10.3版本的FreeBSD5代码库(当时是当前的)重新同步,这样苹果就可以获得完全的POSIX遵从性(macOS是POSIX认证的)。尽管内核中有一个微内核(“Mach”),但内核的其余部分(“XNU”)基本上只是一个BSD内核,这就是为什么macOS提供了与BSD相同的选项,它们的行为方式也与BSD相同。

iOS只是一个macOS的分叉,内核略有修改和修剪,用户空间工具集略有缩减,默认框架集略有不同。watchOS和tvOS是iOS分叉,它们被进一步剥离(特别是watchOS)。据我所知,他们的行为和macOS完全一样。

>

  • 只要侦听(服务器)TCP套接字绑定到特定端口,那么针对该端口的所有套接字都将完全忽略SO_REUSEADDR选项。只有在BSD中不设置so_reuseaddr的情况下,才能将第二个套接字绑定到同一端口。例如。您不能绑定到通配符地址,然后绑定到更具体的通配符地址,或者反过来绑定,如果您设置so_reuseaddr,这两种情况在BSD中都是可能的。您可以做的是绑定到同一个端口和两个不同的非通配符地址,因为这总是允许的。在这方面,Linux比BSD更具限制性。

    第二个例外情况是,对于客户端套接字,该选项的行为与BSD中的so_reuseport完全相同,只要两者在绑定之前都设置了该标志。允许这样做的原因仅仅是,对于各种协议,能够将多个套接字完全绑定到相同的UDP套接字地址是很重要的,并且由于在3.9之前没有SO_reusePort,因此SO_reuseAddr的行为被相应地修改以填补这个空白。在这方面,Linux的限制比BSD少。

    Linux 3.9还为Linux添加了so_reuseport选项。此选项的行为与BSD中的选项完全相同,并且允许绑定到完全相同的地址和端口号,只要所有套接字在绑定之前都设置了此选项。

    >

  • 要防止“端口劫持”,有一个特别的限制:所有希望共享相同地址和端口组合的套接字必须属于共享相同有效用户ID的进程!因此一个用户不能“窃取”另一个用户的端口。这是某种特殊的魔法,可以在一定程度上补偿缺少的SO_EXCLBIND/SO_ExclusiveAddRuse标志。

    此外,内核还为so_reuseport套接字执行了一些在其他操作系统中没有的“特殊魔法”:对于UDP套接字,它尝试均匀地分配数据报;对于TCP侦听套接字,它尝试在共享相同地址和端口组合的所有套接字上均匀地分配传入的连接请求(通过调用accept()接受的请求)。因此,应用程序可以轻松地在多个子进程中打开同一个端口,然后使用so_reuseport来实现非常廉价的负载平衡。

    尽管整个Android系统与大多数Linux发行版有些不同,但它的核心是一个稍作修改的Linux内核,因此适用于Linux的所有内容都应该适用于Android。

    在Windows 2003之前,具有so_reuseaddr的套接字始终可以绑定到与已绑定的套接字完全相同的源地址和端口,即使另一套接字在绑定时没有设置此选项。此行为允许应用程序“窃取”另一个应用程序的连接端口。不用说,这有重大的安全影响!

    Microsoft意识到了这一点,并添加了另一个重要的套接字选项:SO_ExclusiveAddRuse。在套接字上设置SO_ExclusiveAddruse可确保如果绑定成功,源地址和端口的组合由此套接字独占,其他套接字都不能绑定到它们,即使设置了SO_ReuseAddr也不能绑定。

    这一默认行为首先在Windows2003中被改变,微软将其称为“增强的套接字安全性”(在所有其他主要操作系统上默认的行为的有趣名称)。欲知更多详情,请访问本页。有三个表:第一个表显示了经典行为(使用兼容模式时仍在使用!),第二个表显示了同一用户进行bind()调用时Windows 2003及更高版本的行为,第三个表显示了不同用户进行bind()调用时的行为。

    Solaris是Sunos的继任者。SunOS最初是基于BSD的分叉,SunOS5及以后的版本是基于SVR4的分叉,然而SVR4是BSD、System V和Xenix的合并,所以在某种程度上,Solaris也是一个BSD分叉,而且是一个相当早期的分叉。因此,Solaris只知道SO_reuseAddr,没有SO_reusePortso_reuseaddr的行为与BSD中的行为基本相同。据我所知,在Solaris中没有办法获得与so_reuseport相同的行为,这意味着不可能将两个套接字绑定到完全相同的地址和端口。

    与Windows类似,Solaris有一个选项,可以为套接字提供排他绑定。此选项名为so_exclbind。如果在绑定套接字之前对其设置了此选项,则在另一套接字上设置so_reuseaddr(如果对这两个套接字进行了地址冲突测试)将不起作用。例如。如果socketa绑定到通配符地址,并且socketb启用了so_reuseaddr并绑定到非通配符地址和与socketa相同的端口,则此绑定通常会成功,除非socketa启用了so_exclbind;在这种情况下,无论socketbso_reuseaddr标志是否为socketb都将失败。

    万一您的系统没有在上面列出,我编写了一个小测试程序,您可以使用它来了解您的系统是如何处理这两个选项的。此外,如果你认为我的结果是错误的,请首先运行该程序之前张贴任何评论和可能作出虚假的声明。

    构建代码所需要的只是一个小的POSIX API(用于网络部分)和一个C99编译器(实际上,大多数非C99编译器只要提供inttypes.hstdbool.h就会工作得很好;例如,gcc在提供完全的C99支持之前很久就支持这两个编译器)。

    该程序只需运行系统中的至少一个接口(本地接口以外的接口)具有分配的IP地址,并设置使用该接口的默认路由。程序将收集那个IP地址并将其用作第二个“特定地址”。

      null

    并将结果打印在一个漂亮的表格中。它还将在不知道so_reuseport的系统上工作,在这种情况下,该选项只是不进行测试。

    程序不容易测试的是so_reuseaddr如何作用于处于time_wait状态的套接字,因为强制并保持套接字处于该状态非常棘手。幸运的是,大多数操作系统在这里的行为似乎与BSD类似,大多数时候程序员可以忽略这种状态的存在。

    这是代码(我不能在这里包括它,答案有一个大小限制,代码将推动这个答复超过限制)。

  •  类似资料:
    • 问题内容: 在和套接字选项程序员单证,并针对不同的操作系统,不同的,往往混淆高度。有些操作系统甚至没有该选项。WEB充满了与此主题相关的信息,通常您会发现仅对于特定操作系统的一个套接字实现才是正确的信息,甚至在本文中也没有明确提及。 那么到底有什么不同呢? 系统没有更多限制吗? 如果我在不同的操作系统上使用任一操作系统,预期的行为到底是什么? 问题答案: 欢迎来到美好的便携性世界……或者说缺少它。

    • 组播地址 SO_REUSEADDR对于多播地址的含义发生了变化,因为它允许将多个套接字绑定到源多播地址和端口的完全相同的组合上。换句话说,对于多播地址,SO_REUSEADDR的行为与对于单播地址,SO_REUSEPORT的行为完全相同。实际上,代码对组播地址的SO_REUSEADDR和SO_REUSEPORT是相同的,这意味着您可以说SO_REUSEADDR意味着所有组播地址都是SO_REUSE

    • 本文向大家介绍详谈套接字中SO_REUSEPORT和SO_REUSEADDR的区别,包括了详谈套接字中SO_REUSEPORT和SO_REUSEADDR的区别的使用技巧和注意事项,需要的朋友参考一下 Socket的基本背景 在讨论这两个选项的区别时,我们需要知道的是BSD实现是所有socket实现的起源。基本上其他所有的系统某种程度上都参考了BSD socket实现(或者至少是其接口),然后开始了

    • 我在运行模拟器时得到以下错误,也调试不起作用,它说等待调试器附加。我想知道下面的问题是否与陷入“等待调试器附加”有关。 仿真程序:E0516 21:01:34.057349753 3279 socket_utils_common_posix.cc:201]检查so_reuseport:{“created”:“@1589644894.057229268”,“description”:“so_reus

    • 有人能解释一下这两种切片方法有什么不同吗? 我看过文档,也看到过这些答案,但我还是发现自己无法理解这三种方法有什么不同。在我看来,它们在很大程度上是可以互换的,因为它们处于较低的切片级别。 例如,假设我们希望获得的前五行。这两个是怎么工作的? 谁能说出三种情况,在使用上的区别比较清楚? 从前,我也想知道这两个函数与有什么不同,但是已经从pandas 1.0中删除了,所以我不再关心了。

    • 问题内容: 有人可以解释这三种切片方法有何不同吗? 我看过文档,也看过这些 答案,但是我仍然发现自己无法解释这三者之间的区别。在我看来,它们在很大程度上似乎是可互换的,因为它们处于切片的较低级别。 例如,假设我们要获取的前五行。这三者如何运作? 有人可以提出三种用法之间的区别更清楚的情况吗? 问题答案: 注意:在熊猫版本0.20.0及更高版本中,已弃用,建议改为使用loc和iloc。我留下了ix完