套接字选项SO_REUSEADDR和SO_REUSEPORT,它们有何不同?

套接字选项SO_REUSEADDR和SO_REUSEPORT,它们有何不同?它们在所有主要操作系统中都是相同的吗?

这个man pages以及套接字选项的程序员文档SO_REUSEADDRSO_REUSEPORT对于不同的操作系统是不同的,而且常常非常混乱。有些操作系统甚至没有选择SO_REUSEPORT..Web上充斥着有关这个主题的矛盾信息,而且通常您可以找到只适用于特定操作系统的一个套接字实现的信息,而这些信息甚至可能在文本中没有明确提到。

那么到底是怎么回事呢?SO_REUSEADDR不同于SO_REUSEPORT?

系统没有SO_REUSEPORT更有限?

如果我在不同的操作系统上使用这两种操作系统,那么预期的行为究竟是什么呢?


慕勒3428872
浏览 788回答 0
0回答

慕雪6442864

欢迎来到这个奇妙的便携世界.。或者说它的缺乏。在我们开始详细分析这两个选项并深入了解不同的操作系统如何处理它们之前,应该注意的是,BSD套接字实现是所有套接字实现的之母。基本上,所有其他系统都在某个时间点复制了BSD套接字实现(或至少复制了它的接口),然后开始自己开发BSD套接字。当然,BSD套接字实现也是同时进化的,因此后来复制它的系统获得了之前复制它的系统中缺少的特性。理解BSD套接字实现是理解所有其他套接字实现的关键,所以您应该阅读它,即使您不关心为BSD系统编写代码。在我们讨论这两个选项之前,您应该了解一些基本知识。TCP/UDP连接由五个值的元组标识:{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}这些值的任何唯一组合都标识连接。因此,没有两个连接可以具有相同的五个值,否则系统将无法再区分这些连接。控件创建套接字时,将设置套接字的协议。socket()功能。设置源地址和端口。bind()功能。设置目标地址和端口。connect()功能。由于UDP是一个无连接的协议,所以可以使用UDP套接字而不连接它们。但是允许将它们连接起来,并且在某些情况下对您的代码和一般应用程序设计非常有利。在无连接模式下,当第一次发送数据时未显式绑定的UDP套接字通常由系统自动绑定,因为未绑定的UDP套接字无法接收任何(应答)数据。对于未绑定的TCP套接字也是如此,它在连接之前是自动绑定的。如果显式绑定套接字,则可以将其绑定到端口。0意思是“任何港口”。由于套接字不能真正绑定到所有现有端口,在这种情况下,系统必须选择特定端口本身(通常来自预定义的、OS特定范围的源端口)。源地址也存在类似的通配符,可以是“任意地址”(0.0.0.0如属IPv 4及::在IPv 6的情况下)。与端口不同的是,套接字实际上可以绑定到“任意地址”,这意味着“所有本地接口的所有源IP地址”。如果套接字稍后连接,系统必须选择特定的源IP地址,因为套接字不能连接,同时绑定到任何本地IP地址。根据目标地址和路由表的内容,系统将选择适当的源地址,并将“任意”绑定替换为绑定到所选源IP地址。默认情况下,不能将两个套接字绑定到源地址和源端口的相同组合。只要源端口是不同的,源地址实际上是无关的。绑定socketA到A:X和socketB到B:Y,在哪里A和B是地址和X和Y都是端口,只要X != Y是真的。但是,即使X == Y,只要A != B是真的。例如:socketA属于FTP服务器程序,并绑定到192.168.0.1:21和socketB属于另一个FTP服务器程序,并绑定到10.0.0.1:21,这两个绑定都将成功。不过,请记住,套接字可能在本地绑定到“任何地址”。如果套接字绑定到0.0.0.0:21,它同时绑定到所有现有本地地址,在这种情况下,不能将其他套接字绑定到端口。21,而不管它试图绑定到哪个特定的IP地址,如0.0.0.0与所有现有本地IP地址冲突。到目前为止,所有的主要操作系统几乎都是一样的。当地址重用发挥作用时,事情就开始变得特定于OS了。我们从BSD开始,因为正如我前面所说的,它是所有套接字实现的之母。BSDSO_REUSEADDR如果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.0和192.168.0.1是不完全同意同一个地址,一个是所有本地地址的通配符,另一个是非常特定的本地地址。注意,上面的语句是真的,不管是哪种顺序。socketA和socketB被束缚;没有SO_REUSEADDR它总是会失败的SO_REUSEADDR它永远都会成功。为了给您一个更好的概述,让我们在这里创建一个表并列出所有可能的组合:SO_REUSEADDR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socketA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socketB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Result --------------------------------------------------------------------- &nbsp;&nbsp;ON/OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;192.168.0.1:21&nbsp;&nbsp;&nbsp;192.168.0.1:21&nbsp;&nbsp;&nbsp;&nbsp;Error&nbsp;(EADDRINUSE) &nbsp;&nbsp;ON/OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;192.168.0.1:21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10.0.0.1:21&nbsp;&nbsp;&nbsp;&nbsp;OK &nbsp;&nbsp;ON/OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10.0.0.1:21&nbsp;&nbsp;&nbsp;192.168.0.1:21&nbsp;&nbsp;&nbsp;&nbsp;OK &nbsp;&nbsp;&nbsp;OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0.0.0:21&nbsp;&nbsp;&nbsp;192.168.1.0:21&nbsp;&nbsp;&nbsp;&nbsp;Error&nbsp;(EADDRINUSE) &nbsp;&nbsp;&nbsp;OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;192.168.1.0:21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0.0.0:21&nbsp;&nbsp;&nbsp;&nbsp;Error&nbsp;(EADDRINUSE) &nbsp;&nbsp;&nbsp;ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0.0.0:21&nbsp;&nbsp;&nbsp;192.168.1.0:21&nbsp;&nbsp;&nbsp;&nbsp;OK &nbsp;&nbsp;&nbsp;ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;192.168.1.0:21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0.0.0:21&nbsp;&nbsp;&nbsp;&nbsp;OK &nbsp;&nbsp;ON/OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0.0.0:21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0.0.0:21&nbsp;&nbsp;&nbsp;&nbsp;Error&nbsp;(EADDRINUSE)上表假定socketA已成功绑定到socketA,然后socketB被创建,或者SO_REUSEADDR设置或不设置,并最终绑定到给出的地址。socketB.&nbsp;Result的绑定操作的结果。socketB..如果第一列说ON/OFF的价值SO_REUSEADDR与结果无关。好吧,SO_REUSEADDR对通配符地址有影响,很高兴知道。但这并不是唯一的效果。还有另一个众所周知的效果,这也是大多数人使用的原因。SO_REUSEADDR首先在服务器程序中。对于此选项的另一个重要用途,我们必须更深入地了解TCP协议是如何工作的。套接字有一个发送缓冲区,如果调用send()函数成功,这并不意味着请求的数据实际上已经发送出去,它只意味着数据已经添加到发送缓冲区。对于UDP套接字,通常会很快发送数据,如果不是立即发送的话,但是对于TCP套接字,在向发送缓冲区添加数据和让TCP实现真正发送数据之间可能会出现相对较长的延迟。因此,当您关闭TCP套接字时,发送缓冲区中可能仍有挂起的数据,这些数据尚未发送,但您的代码认为它已发送,因为send()呼叫成功。如果TCP实现在请求时立即关闭套接字,那么所有这些数据都会丢失,您的代码甚至不会知道这一点。据说TCP是一个可靠的协议,而丢失数据也不是很可靠。这就是为什么仍有数据要发送的套接字将进入一个名为TIME_WAIT当你关闭它的时候。在这种状态下,它将等待直到所有挂起的数据都被成功发送,或者直到超时,在这种情况下,套接字被强制关闭。内核在关闭套接字之前将等待的时间,无论它是否仍在运行中数据,都称为逗留时间..这个逗留时间在大多数系统上都是全局可配置的,默认情况下相当长(两分钟是许多系统上常见的值)。它还可以使用套接字选项在每个套接字上进行配置。SO_LINGER这可以用来使超时更短或更长,甚至完全禁用它。完全禁用它是一个非常糟糕的主意,因为优雅地关闭TCP套接字是一个稍微复杂的过程,涉及发送和回发几个数据包(以及重新发送这些数据包以防止丢失),而这个关闭过程也受到逗留时间..如果您禁用挥之不去,您的套接字可能不仅会在飞行中丢失数据,而且它也总是强制关闭,而不是优雅地关闭,这通常是不推荐的。关于如何优雅地关闭tcp连接的详细信息超出了此答案的范围,如果您想了解更多有关tcp连接的信息,我建议您查看一下本页..即使你残疾缠身SO_LINGER,如果您的进程没有显式关闭套接字就会死,那么BSD(可能还有其他系统)仍然会继续存在,忽略您配置的内容。例如,如果您的代码只是调用exit()(对于小型、简单的服务器程序来说非常常见),或者进程被一个信号(包括它可能因为非法的内存访问而崩溃)杀死了。因此,你不能做任何事情来确保一个插座不会在任何情况下停留。问题是,系统如何处理处于状态的套接字?TIME_WAIT?如果SO_REUSEADDR未设置,则套接字处于状态。TIME_WAIT被认为仍然绑定到源地址和端口,任何将新套接字绑定到相同地址和端口的尝试都将失败,直到套接字真正关闭为止,这可能需要与配置的套接字相同的时间。逗留时间..因此,不要期望在关闭套接字后立即重新绑定它的源地址。在大多数情况下,这将失败。但是,如果SO_REUSEADDR为要绑定的套接字设置,另一个套接字绑定到相同的地址和状态下的端口。TIME_WAIT完全被忽略,毕竟它已经“半死”了,并且您的套接字可以绑定到完全相同的地址,没有任何问题。在这种情况下,它不扮演其他套接字可能具有完全相同的地址和端口的角色。请注意,将套接字绑定到与垂死的套接字完全相同的地址和端口。TIME_WAIT如果另一个套接字仍然“工作”,状态可能会产生意外的、通常是不希望的副作用,但这超出了这个答案的范围,而且幸运的是,这些副作用在实践中是相当罕见的。还有一件事你应该知道SO_REUSEADDR..只要绑定到的套接字启用了地址重用,上面写的所有内容都能工作。没有必要使用另一个套接字,该套接字已经绑定或处于TIME_WAIT在绑定时也设置了此标志。决定绑定是否成功或失败的代码只检查SO_REUSEADDR将套接字的标志输入bind()调用,对于检查过的所有其他套接字,甚至没有查看此标志。SO_REUSEPORTSO_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_REUSEPORT和SO_REUSEADDR,在插座上。关于这件事没什么可说的了SO_REUSEPORT除此之外,它是晚于SO_REUSEADDR这就是为什么您不会在其他系统的许多套接字实现中找到它,这些实现在添加此选项之前“分叉”BSD代码,并且在此选项之前无法将两个套接字绑定到BSD中的完全相同的套接字地址。连接()返回EADDRINUSE?大多数人都知道bind()可能会因错误而失败EADDRINUSE然而,当您开始玩弄地址重用时,您可能会遇到奇怪的情况:connect()这个错误也会失败。这怎么可能?在连接添加到套接字之后,远程地址怎么可能已经在使用呢?将多个套接字连接到完全相同的远程地址以前从未出现过问题,那么这里发生了什么问题呢?正如我在回答的最上面所说的,连接是由五个值的元组定义的,记得吗?我也说过,这五个值必须是唯一的,否则系统就不能再区分两个联系了,对吗?使用地址重用,您可以将相同协议的两个套接字绑定到相同的源地址和端口。这意味着这五个值中的三个对于这两个套接字来说已经是相同的。如果您现在尝试将这两个套接字都连接到相同的目标地址和端口,您将创建两个连接的套接字,它们的元组是完全相同的。这是行不通的,至少对于TCP连接(UDP连接并不是真正的连接)。如果数据到达这两个连接中的任何一个,系统将无法判断数据属于哪个连接。至少目标地址或目的端口对于任何一个连接都必须不同,这样系统就可以确定传入数据属于哪个连接。因此,如果将相同协议的两个套接字绑定到相同的源地址和端口,并试图将它们连接到相同的目标地址和端口,connect()如果出现错误,实际上会失败。EADDRINUSE对于第二个套接字,您尝试连接,这意味着一个具有相同的元组五个值的套接字已经连接起来了。多播地址大多数人忽略了多播地址存在的事实,但它们确实存在。单播地址用于一对一通信,多播地址用于一对多通信.大多数人在了解IPv 6时就知道了组播地址,但IPv 4中也存在组播地址,尽管这种特性从未在公共互联网上得到广泛应用。意义SO_REUSEADDR对多播地址的更改,因为它允许将多个套接字绑定到完全相同的源多播地址和端口的组合。换句话说,对于多播地址SO_REUSEADDR行为与SO_REUSEPORT单播地址。实际上,代码处理SO_REUSEADDR和SO_REUSEPORT对于多播地址,这意味着你可以说SO_REUSEADDR暗示SO_REUSEPORT对于所有的多播地址和相反的方式。FreeBSD/OpenBSD/NetBSD所有这些都是最初BSD代码中比较晚的分支,这就是为什么它们都提供了与BSD相同的选项,而且它们的行为方式也与BSD相同。MacOS(MacOS X)MacOS的核心是一个名为“bsd样式的UNIX”。达尔文,基于相当晚的BSD代码(BSD 4.3),后来甚至与Mac OS 10.3发行版的FreeBSD 5代码库(当时的版本)重新同步,这样苹果就可以获得完全的POSIX遵从性(MacOS是经过POSIX认证的)。尽管它的核心有一个微内核(马赫),内核的其余部分(“XNU)基本上只是一个BSD内核,这就是为什么MacOS提供与BSD相同的选项,它们的行为方式也与BSD相同。IOS/Watch OS/tvOSIOS只是一个MacOS分叉,内核略有修改和修剪,用户空间工具集和默认框架集略有不同。Watch OS和tvOS是iOS分叉,它们被进一步剥离(特别是Watch OS)。据我所知,他们的行为都和MacOS完全一样。linuxLinux<3.9在Linux3.9之前,只有以下选项SO_REUSEADDR已经存在了。此选项的行为通常与BSD中相同,但有两个重要的例外:只要侦听(服务器)TCP套接字绑定到特定端口,则SO_REUSEADDR选项完全忽略了针对该端口的所有套接字。将第二个套接字绑定到同一个端口只有在BSD中也有可能,而不需要SO_REUSEADDR准备好了。例如:您不能绑定到通配符地址,然后绑定到更具体的地址或相反的方式,如果您设置的话,两者在bsd中都是可能的。SO_REUSEADDR..您可以做的是,您可以绑定到同一个端口和两个不同的非通配符地址,因为这是始终允许的。在这方面,Linux比BSD更具限制性。第二个例外是,对于客户端套接字,此选项的行为与SO_REUSEPORT在BSD中,只要两个人在被绑之前都设置了这个旗子。允许这样做的原因很简单,重要的是能够将多个套接字绑定到不同协议的完全相同的udp套接字地址,而且以前没有。SO_REUSEPORT在3.9之前,SO_REUSEADDR做了相应的修改以填补这一空白。在这方面,Linux没有BSD那么严格。Linux>=3.9Linux 3.9增加了选项SO_REUSEPORT对Linux也是如此。该选项的行为与BSD中的选项完全相同,并且允许绑定到完全相同的地址和端口号,只要所有套接字在绑定之前都设置了此选项。然而,对于SO_REUSEPORT关于其他系统:为防止“港口劫持”,有一个特殊限制:所有希望共享相同地址和端口组合的套接字必须属于共享相同有效用户ID的进程!因此,一个用户不能“窃取”另一个用户的端口。这是一种特殊的魔法来弥补失踪者的损失。SO_EXCLBIND/SO_EXCLUSIVEADDRUSE旗子。此外,内核还为SO_REUSEPORT在其他操作系统中找不到的套接字:对于udp套接字,它试图均匀地分发数据报,对于tcp侦听套接字,它试图分发传入的连接请求(那些通过调用接受的请求)。accept())共享相同地址和端口组合的所有套接字。因此,应用程序可以轻松地在多个子进程中打开同一个端口,然后使用SO_REUSEPORT以获得一个非常便宜的负载平衡。安卓尽管整个Android系统与大多数Linux发行版有些不同,但它的核心工作是一个稍微修改过的Linux内核,因此适用于Linux的所有内容也应该适用于Android。窗Windows只知道SO_REUSEADDR选项,没有SO_REUSEPORT..设置SO_REUSEADDR在Windows中的套接字上,行为类似于设置SO_REUSEPORT和SO_REUSEADDR在BSD中的套接字上,只有一个例外:SO_REUSEADDR可以始终绑定到与已绑定的套接字完全相同的源地址和端口,即使另一个套接字在绑定时没有设置此选项..这种行为有点危险,因为它允许应用程序“窃取”另一个应用程序的连接端口。不用说,这可能对安全产生重大影响。微软意识到这可能是一个问题,因此增加了另一个套接字选项。SO_EXCLUSIVEADDRUSE..设置SO_EXCLUSIVEADDRUSE在套接字上,请确保如果绑定成功,源地址和端口的组合只属于此套接字,其他套接字不能绑定到它们,即使它有。SO_REUSEADDR准备好了。有关这些标志的更多细节SO_REUSEADDR和SO_EXCLUSIVEADDRUSE在Windows上工作,它们是如何影响绑定/重新绑定的,微软好心地提供了一个类似于我的表格,接近回复的顶部。只要访问这个页面然后向下滚动一点。实际上有三个表,第一个表显示旧行为(以前的Windows 2003),第二个表显示行为(Windows 2003及更高版本),第三个表显示Windows 2003及以后的行为是如何变化的,如果bind()电话由不同的用户拨打。索拉里斯Solaris是SunOS的继任者。SunOS最初是基于BSD的叉,SunOS 5,后来是基于SVR 4的叉,但是SVR 4是BSD、System V和Xenix的合并,所以Solaris在某种程度上也是BSD叉,而且是早期的。因此Solaris只知道SO_REUSEADDR,没有SO_REUSEPORT..这个SO_REUSEADDR它的行为与在BSD中的行为基本相同。据我所知,没有办法像SO_REUSEPORT在Solaris中,这意味着不可能将两个套接字绑定到完全相同的地址和端口。与Windows类似,Solaris可以为套接字提供独占绑定。此选项命名为SO_EXCLBIND..如果此选项是在套接字绑定之前在套接字上设置的,则设置SO_REUSEADDR在另一个套接字上,如果测试两个套接字是否存在地址冲突,则无效。例如:如果socketA绑定到通配符地址,并且socketB有SO_REUSEADDR启用并绑定到非通配符地址和与socketA,此绑定通常会成功,除非socketA有SO_EXCLBIND启用,在这种情况下,它将失败,而不管SO_REUSEADDR旗socketB.其他系统如果您的系统没有在上面列出,我编写了一个小测试程序,您可以使用这个程序来了解您的系统如何处理这两个选项。如果你认为我的结果是错的,请先运行该程序,然后发布任何评论,并可能作出错误的主张。代码所需构建的所有代码都是一些POSIX api(用于网络部件)和一个c99编译器(实际上,大多数非c99编译器只要提供这些编译器就可以工作得很好。)inttypes.h和stdbool.h;gcc在提供完整的C99支持之前很长一段时间都支持)。程序只需运行系统中至少一个接口(本地接口除外)就可以分配IP地址,并设置使用该接口的默认路由。该程序将收集该IP地址,并将其用作第二个“特定地址”。它测试您可以想到的所有可能的组合:TCP和UDP协议普通套接字,侦听(服务器)套接字,多播套接字SO_REUSEADDR在套接字1、套接字2或两个套接字上设置SO_REUSEPORT在套接字1、套接字2或两个套接字上设置所有您可以利用的地址组合0.0.0.0(通配符),127.0.0.1(特定地址),以及在主接口上找到的第二个特定地址(对于多播,它只是224.1.2.3在所有测试中)把结果打印在一张漂亮的桌子上。它也适用于那些不知道的系统SO_REUSEPORT在这种情况下,这个选项就没有经过测试。程序不容易测试的是SO_REUSEADDR中的套接字上的动作TIME_WAIT状态,因为强制并将套接字保持在该状态是非常棘手的。幸运的是,大多数操作系统在这里的行为似乎就像BSD,而大多数情况下,程序员可以简单地忽略这种状态的存在。这是密码(我不能把它包括在这里,答案有一个大小限制,代码会把这个答复推到极限)。
打开App,查看更多内容
随时随地看视频慕课网APP