来自 man 手册:
SO_REUSEADDR 指定在验证传递给 bind() 的地址时应使用的规则,如果协议支持,则应允许重用本地地址。此选项采用 int 值。这是一个布尔选项。
我什么时候应该使用它?"重用本地地址" 是什么意思?
来自 man 手册:
SO_REUSEADDR 指定在验证传递给 bind() 的地址时应使用的规则,如果协议支持,则应允许重用本地地址。此选项采用 int 值。这是一个布尔选项。
我什么时候应该使用它?"重用本地地址" 是什么意思?
很明显,在连接处于活动状态时,TCP/IP网络栈可以处理所有这些问题,但是在连接关闭后的那一刻会出现一个边缘情况。如果在会话结束时发送的数据包被复制并延迟,以至于4路关闭数据包在延迟的数据包到达接收方之前到达,栈将会忠实地关闭它的连接。然后稍后,延迟的重复数据包出现了。此时应该怎么做呢?
更重要的是,如果使用给定IP地址+TCP端口组合的开放套接字的程序关闭了其套接字,然后在短时间内另一个程序来监听同一IP地址和TCP端口号,它应该怎么办?(典型情况:一个程序被杀死并被快速重新启动。)
有几个选择:
禁止在至少两倍于数据包飞行最长时间的时间内重复使用该IP/端口组合。在TCP中,这通常称为2×MSL延迟。有时也会看到2×RTT,它大致相当。
这是所有常见TCP/IP堆栈的默认行为。 2×MSL通常在30到120秒之间,并显示在netstat
输出中作为TIME_WAIT
期间。在此时间之后,堆栈假定任何流氓数据包由于过期的TTLs而在途中被丢弃,因此该套接字离开TIME_WAIT
状态,允许重新使用该IP/端口组合。
允许新程序重新绑定到该IP/端口组合。在具有BSD sockets接口的堆栈中 - 基本上是所有Unix和类Unix系统,以及通过Winsock的Windows - 您必须通过在调用bind()
之前通过setsockopt()
设置SO_REUSEADDR
选项来请求此行为。
SO_REUSEADDR
通常在网络服务器程序中设置,因为常见的使用模式是进行配置更改,然后需要重新启动该程序以使更改生效。如果没有SO_REUSEADDR
,则在您杀死它时,如果先前实例中有连接打开,则重新启动程序的新实例中的bind()
调用将失败。这些连接将在30-120秒内保持TCP端口处于TIME_WAIT
状态,因此您会遇到上述情况1。
设置SO_REUSEADDR
的风险在于它会创建一个歧义:TCP数据包头中的元数据不足以唯一地标识该数据包是否过时,因此堆栈无法可靠地判断该数据包是否应被丢弃而不是传递给新侦听器的套接字,因为它显然是针对一个已死侦听器而言。
如果您没有看到这是真的,那么每个连接的监听机器的TCP/IP堆栈必须处理以下内容才能做出决策:
本地IP: 每个连接不唯一。实际上,我们在这里的问题定义是故意重用本地IP。
本地TCP端口: 同上。
远程IP: 导致歧义的机器可能会重新连接,因此这并不能帮助消除数据包的正确目的地。
远程端口: 在行为良好的网络堆栈中,出站连接的远程端口不会很快被重用,但它只有16位,因此您有30-120秒的时间来强制堆栈通过几万个选择并重用该端口。计算机在20世纪60年代就可以做到这么快了。
如果您的答案是远程堆栈应该在其端进行类似于TIME_WAIT
的操作以禁止ephemeral TCP port重用,则该解决方案假定远程主机是良性的。恶意参与者可以自由地重用该远程端口。
我想监听程序的堆栈可以选择严格禁止仅使用TCP 4元组的连接,以便在TIME_WAIT
状态下,给定的远程主机无法使用相同的远程临时端口重新连接,但我不知道是否有任何具有特定改进的TCP堆栈。
本地和远程TCP序列号: 这些也不足以唯一标识一个新的远程程序可能会使用相同的值。
TIME_WAIT
期间重新绑定所创建的歧义意味着你可以要么a) 旧侦听器的陈旧数据被错误地传递到属于新侦听器的套接字上,从而打破了侦听器的协议或在连接中不正确地注入陈旧数据; 或者b) 新侦听器套接字的新数据错误地分配给旧侦听器套接字,从而无意中丢失。TIME_WAIT
期间结束。TIME_WAIT
期间结束,还是承担不希望的数据丢失或意外数据注入的风险。SO_REUSEADDR
保持不变。程序员可能知道这些风险并选择保持默认设置,或者他们可能对问题一无所知,但正在获得明智默认设置的好处。
一些网络程序会让用户选择配置选项,将责任转嫁给最终用户或系统管理员。
SO_REUSEADDR允许您的服务器绑定到处于TIME_WAIT状态的地址。
此套接字选项告诉内核,即使此端口正忙(处于TIME_WAIT状态),也要继续重用它。 如果它很忙,但是处于另一种状态,则仍会收到“地址已在使用中”的错误。 如果您的服务器已关闭,然后立即重新启动,而套接字仍处于其端口上活动状态,则此选项很有用。
TIME_WAIT
状态的 TCP 连接。一个连接由两个地址标识:远程端口/IP 和本地端口/IP。请注意,当 TCP 服务器没有重新启动(始终使用相同的套接字)时,客户端连接一直在不断地进出并循环通过 TIME_WAIT
状态。新客户端不会因为之前的客户端处于 TIME_WAIT
而被阻止连接。那么,为什么只是因为服务器重新启动并重新创建套接字就会出现这种情况呢?文档中的解释站不住脚。 - Kaz如果尝试快速地使用相同的IP和端口对创建多个socket,则会出现“地址已在使用”错误,因为早期的socket尚未完全释放。使用SO_REUSEADDR将消除此错误,因为它将覆盖对任何先前实例的检查。请记住,TCP保证所有传输的数据都会被交付(如果可能)。当你关闭socket时,服务器进入TIME_WAIT状态,只是为了确保所有数据都已经传输完毕。当socket关闭时,双方通过向彼此发送消息来达成共识,即他们将不再发送任何数据。在握手完成后,看起来这已经足够了,socket应该关闭。问题有两个。首先,无法确定最后的ack是否成功通信。其次,如果传递了“漫游重复项”,则必须处理。
SO_LINGER
,那么这是否也是正确的,即前台滞留(阻塞 close()
)不包括 TIME_WAIT
?我知道 SO_LINGER
的值为零将完全避免 TIME_WAIT
(因为它完全避免了终止序列并发送 RST
)。 - haelix
SO_REUSEADDR
标志。 使用此标志时应小心,因为它会使TCP不太可靠。 没有上面的解释,这句话并没有什么帮助。 - patryk.bezasetsockopt(sock, SOL_SOCKET, SO_DONTLINGER, &"\x00\x00\x00\x00", 4);
。 - ntninjaTIME_WAIT
优化是一个非常棒的想法。 - armani