服务器应该设置SO_REUSEADDR选项

你可能看到过这个错误提示:Address already in use(地址已经被使用了)。往往要几分钟后才能重启,那我们如何立即重启服务器呢?在此之前,先理解如下两件事情:

  1. TCP 的 TIME-WAIT状态
  2. TCP 连接的四元组(本地地址、本地端口、远端地址、远端端口)

TCP 连接中进行主动关闭(发送第一个FIN)的那一端会进入 TIME-WAIT 状态,并在此状态停留 2MSL,在此期间,该套接字的地址和端口仍然被占用,无法立即重新绑定到同样的地址和端口。这为我们在看到的行为提供了第一条线索:客户端主动关闭时,可以重启连接的任意一端,不会有什么问题,但当服务器主动关闭时,就无法重启,这是由于前一个连接仍然处于 TIME-WAIT 状态。

如果服务器重启,并且有客户端连接上来,就会有一条新的连接,这条连接可能都不是连接到同一台远端主机上的。如前所述,一条 TCP 连接可以由本地和远程地址以及端口号完全指定,所以,即使来自同一台远程主机的客户端连接到服务器上,只要它使用的端口号和前一个连接不同,就不会有什么问题。这是因为 TIME_WAIT 状态仅影响特定的四元组(<源IP, 源端口, 目标IP, 目标端口>),而不是整个服务器或客户端。

基于这些事实,重启服务器时 TCP 返回的错误就会让我们感到很疑惑。实际上问题并不在于 TCP,而是出在套接字API 上,TCP 只要求四元组是唯一的,而套接字 API 则需要两个调用才能完整地指定四元组。当 API 调用 bind 时,并不知道接下来是否会调用 connect,如果调用还需要在指定一个唯一的连接,还是尝试重用已经存在的那个连接之间作出选择。

幸运的是,这个问题有一种很简单的解决方案。可以先设置套接字选项 SO_REUSEADDR,允许一个套接字在 TIME_WAIT 状态下立即重新绑定到相同的地址和端口。

如果不设置 SO_REUSEADDR,当你尝试重新启动一个服务器程序(特别是在调试时),由于旧连接仍在 TIME_WAIT 状态,尝试绑定到相同的端口会导致 “Address already in use” 错误。这时服务器无法重新绑定到原有端口,可能会导致服务不可用。

在某些情况下,多个进程可能希望绑定到相同的地址和端口进行监听。这种情况通常出现在使用 UDP 协议或多个服务进程间共享一个监听端口时。在这种情况下,SO_REUSEADDR 允许多个套接字绑定到相同的地址和端口(只要每个套接字的协议不同,或者每个套接字是加入了不同的多播组的 UDP 套接字)。


⭐️内容取自译者陈涓、赵振平《TCP/IP高效编程:改善网络程序的44个技巧》,仅从中取出个人以为需要纪录的内容。不追求内容的完整性,却也不会丢失所记内容的逻辑性。如果需要了解细致,建议读原书。