SO_REUSEADDR和SO_REUSEPORT有何不同?

套接字选项SO_REUSEADDRSO_REUSEPORT手册页和程序员文档对于不同的操作系统是不同的,并且常常非常混乱。有些操作系统甚至没有选项,因此_REUSEPORT。WEB上充斥着关于这个主题的相互矛盾的信息,通常您可以找到仅适用于特定操作系统的一个套接字实现的信息,这些信息甚至可能在本文中没有明确提及

那么,So\u REUSEADDRSo\u REUSEPORT到底有什么不同呢

没有的系统重用端口是否更有限

如果我在不同的操作系统上使用其中一种,那么预期的行为是什么

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

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

{<协议>,<src地址>,<src端口>,<目的地地址>,<目的地端口>}

这些值的任何唯一组合都标识连接。因此,任何两个连接都不能具有相同的五个值,否则系统将无法再区分这些连接

当使用socket()函数创建套接字时,将设置套接字的协议。源地址和端口由bind()函数设置。使用connect()函数设置目标地址和端口。由于UDP是一种无连接协议,因此可以使用UDP套接字而无需连接它们。但是允许连接它们,在某些情况下,这对代码和一般应用程序设计非常有利。在无连接模式下,首次通过UDP套接字发送数据时未显式绑定的UDP套接字通常由系统自动绑定,因为未绑定的UDP套接字无法接收任何(应答)数据。对于未绑定的TCP套接字也是如此,它在连接之前会自动绑定

如果显式绑定套接字,则可以将其绑定到端口0,这意味着;任何端口”;。由于套接字不能真正绑定到所有现有端口,因此在这种情况下,系统必须选择一个特定的端口本身(通常来自预定义的、特定于操作系统的源端口范围)。源地址也存在类似的通配符,可以是;任何地址;(0.0.0.0对于IPv4和对于IPv6)。与端口不同,套接字实际上可以绑定到;任何地址;这意味着;所有本地接口的所有源IP地址;。如果稍后连接套接字,则系统必须选择特定的源IP地址,因为套接字不能连接,同时也不能绑定到任何本地IP地址。根据目的地址和路由表的内容,系统将选择适当的源地址并替换;任何;绑定到所选源IP地址的绑定

默认情况下,不能将两个套接字绑定到源地址和源端口的相同组合。只要源端口不同,源地址实际上是无关的。如果ipA!=ipB保持为真,即使在portA==portB时也是如此。例如,socketA属于一个FTP服务器程序并绑定到192.168.0.1:21socketB属于另一个FTP服务器程序并绑定到10.0.0.1:21,两个绑定都将成功。但是,请记住,套接字可能在本地绑定到;任何地址;。如果一个套接字绑定到0.0.0:21,它将同时绑定到所有现有的本地地址,在这种情况下,任何其他套接字都不能绑定到端口21,无论它试图绑定到哪个特定的IP地址,因为0.0.0与所有现有的本地IP地址冲突

到目前为止,对于所有主要的操作系统来说,所说的一切几乎都是平等的。当地址重用开始发挥作用时,事情开始变得特定于操作系统。我们从BSD开始,因为正如我上面所说的,它是所有套接字实现之母

BSD

所以!

如果在绑定套接字之前在套接字上启用了SO\u REUSEADDR,则可以成功绑定该套接字,除非与绑定到的另一个套接字发生冲突,而该套接字正好是源地址和端口的相同组合。现在你可能想知道这和以前有什么不同?关键字是“quot;正是SO_REUSEADDR主要更改在搜索冲突时处理通配符地址(“任意IP地址”)的方式

如果没有SO_REUSEADDR,将socketA绑定到0.0.0:21,然后将socketB绑定到192.168.0.1:21将失败(错误EADDRINUSE),因为0.0.0意味着;任何本地IP地址“;,因此,该套接字认为所有本地IP地址都在使用,这也包括192.168.0.1。由于0.0.0192.168.0.1不是完全相同的地址,一个是所有本地地址的通配符,另一个是非常特定的本地地址,因此使用SO\u REUSEADDR将成功。请注意,无论以何种顺序绑定socketAsocketB,上述语句都是正确的;没有SO\u REUSEADDR它总是会失败,而有SO\u REUSEADDR它总是会成功

为了让您更好地了解情况,让我们在此制作一个表格,列出所有可能的组合:

因此,请重新使用Dr socketA socketB结果
---------------------------------------------------------------------
开/关192.168.0.1:21 192.168.0.1:21错误(EADDRINUSE)
开/关192.168.0.1:21 10.0.0.1:21正常
开/关10.0.0.1:21 192.168.0.1:21正常
关闭0.0.0.0:21 192.168.1.0:21错误(EADDRINUSE)
OFF 192.168.1.0:21 0.0.0.0:21错误(EADDRINUSE)
在0.0.0.0:21 192.168.1.0:21正常
192.168.1.0:21 0.0.0.0:21正常
开/关0.0.0:21 0.0.0.0.0:21错误(EADDRINUSE)

上表假设socketA已成功绑定到为socketA指定的地址,然后创建socketB,或者获取SO\u REUSEADDR设置,或者不设置,最后绑定到为socketB指定的地址Result是对socketB执行绑定操作的结果。如果第一列显示开/关,则的值与结果无关

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

套接字有一个发送缓冲区,如果调用send()函数成功,并不意味着请求的数据实际上已经发送出去,它只意味着数据已经添加到发送缓冲区。对于UDP套接字,数据通常会很快发送,如果不是立即发送,但是对于TCP套接字,在向发送缓冲区添加数据和让TCP实现真正发送数据之间可能会有相对较长的延迟。因此,当您关闭TCP套接字时,发送缓冲区中可能仍有挂起的数据,该数据尚未发送,但您的代码认为它已发送,因为send()调用成功。如果TCP实现在您请求时立即关闭套接字,那么所有这些数据都将丢失,您的代码甚至不知道这一点。TCP被认为是一种可靠的协议,像这样丢失数据不是很可靠。这就是为什么当您关闭一个仍有数据要发送的套接字时,它会进入一种称为TIME\u WAIT的状态。在该状态下,它将等待所有挂起的数据成功发送,或者等待超时,在这种情况下,套接字将强制关闭

最多,内核在关闭套接字之前等待的时间量,不管它是否仍有数据在传输中,称为延迟时间。在大多数系统上,逗留时间是全局可配置的,默认情况下相当长(在许多系统上,两分钟是一个常见值)。还可以使用socket选项对每个socket进行配置,因此_LINGER可用于缩短或延长超时,甚至完全禁用超时。但是,完全禁用它是一个非常糟糕的主意,因为优雅地关闭TCP套接字是一个稍微复杂的过程,需要来回发送两个数据包(以及在数据包丢失时重新发送这些数据包),而且整个关闭过程也受到延迟时间的限制。如果您禁用了逗留,您的

发表评论