class MyHttpHandler(BaseHTTPRequestHandler):
def do_GET(self):
print "do get"
self.send_response(code=200)
self.end_headers()
self.wfile.write("hello world")
if __name__ == '__main__':
serv = TCPServer(('', 20001), MyHttpHandler)
serv.serve_forever()
上面这段代码,运行,client访问多次后关闭,再启动,会报一个socket.error: [Errno 48] Address already in use
的错。lsof -i:20001
没能找到任何进程占用端口,一个乍一看很迷的错误,记录下,怕以后忘掉。
解决方案很简单,增加TCPServer.allow_reuse_address = True
。具体起作用的为socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
这。
原因如下。操作系统的网络栈会非常谨慎的处理连接的关闭,仅仅用于监听的服务器套接字是可以立即关闭并操作系统忽略的,但是对于实际与客户端进行通信的连接套接字就不行了。即使客户端和服务器都关闭了连接并向对方发从了FIN数据包,连接套接字也无法立即取消。为什么呢?因为即使网络栈发送了最后一个数据包将套接字关闭,也还是无法确认该数据包是否可以被接收。如果数据包正好被网络丢弃了,那么另一方无法得知该数据包长时间无法传达的原因,可能会重新发送FIN数据包,希望能收到响应。
操作系统对上述问题的解决方案为,一个应用程序任务某个TCP连接最终关闭了,操作系统的网络栈实际上会在一个等待状态中将该连接的记录保存最多4分钟。RFC将这些状态命名为CLOSE-WAIT 和TIME-WAIT,当关闭的套接字还处于其中某一状态时,任何最终的FIN数据包都是可以得到适当响应的。
因此,当服务器试图声明某个几分钟前运行的连接所使用的端口时,实际上是在试图声明从某种意义上仍在使用的端口。所以就报错了~
而SO_REUSEADDR可以指明应用程序能够使用一些网络客户端之前的连接正在关闭的端口。
网友评论