最近在手写分布式爬虫的时候遇到一个问题,就是负责任务分发的服务器在关掉试图重启的时候总显示端口被占用,但又查不到端口被占用的进程号。
[ERROR] Address already in use
一番谷歌之后得知,操作系统的网络栈会非常谨慎的处理连接的关闭,仅仅用于监听的服务器套接字是可以立即关闭并操作系统忽略的,但是对于实际与客户端进行通信的连接套接字就不行了。即使客户端和服务器都关闭了连接并向对方发从了FIN数据包,连接套接字也无法立即取消。为什么呢?因为即使网络栈发送了最后一个数据包将套接字关闭,也还是无法确认该数据包是否可以被接收。如果数据包正好被网络丢弃了,那么另一方无法得知该数据包长时间无法传达的原因,可能会重新发送FIN数据包,希望能收到响应。
操作系统对上述问题的解决方案为,一个应用程序任务某个TCP连接最终关闭了,操作系统的网络栈实际上会在一个等待状态中将该连接的记录保存最多4分钟。RFC将这些状态命名为CLOSE-WAIT 和TIME-WAIT,当关闭的套接字还处于其中某一状态时,任何最终的FIN数据包都是可以得到适当响应的。
如何解决
只需要在server.bind()
之前增加一行代码
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- SO_REUSEADDR允许启动一个监听服务器并捆绑端口,即使以前建立的将此端口用做它们的本地端口的连接仍存在。这通常是重启监听服务器时使用(也就是我当时的情况)
谷歌结果说它还有另外的三个作用:
-
SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,但每个实例需要捆绑一个不同的本地IP地址。
-
SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。
-
SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。