Python TCPServer地址已被占用,已关闭服务器并使用`allow_reuse_address`

20 投票
6 回答
16712 浏览
提问于 2025-04-17 18:12

这是我用来运行服务器的代码:

class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    #....

PORT = 8089

httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler)
httpd.allow_reuse_address = True

print "Serving forever at port", PORT
try:
    httpd.serve_forever()
except:
    print "Closing the server."
    httpd.server_close()
    raise

但是发生了这样的事情:

^CClosing the server.
Traceback (most recent call last):
  File "server.py", line 118, in <module>
    self.send_error(400, "Unimplemented GET command: %s" % (self.path,))
  File "/home/claudiu/local/lib/python2.6/SocketServer.py", line 224, in serve_forever
    r, w, e = select.select([self], [], [], poll_interval)
KeyboardInterrupt
(.virtualenv)claudiu@xxx:~/xxx$ python server.py
Traceback (most recent call last):
  File "server.py", line 122, in <module>
    httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler)
  File "/home/claudiu/local/lib/python2.6/SocketServer.py", line 402, in __init__
    self.server_bind()
  File "/home/claudiu/local/lib/python2.6/SocketServer.py", line 413, in server_bind
    self.socket.bind(self.server_address)
  File "<string>", line 1, in bind
socket.error: [Errno 98] Address already in use

为什么会这样呢?我关闭了服务器,并把 allow_reuse_address 设置为 True... 我使用的是 Python 2.6.8。

6 个回答

3

这是因为TCP有一个状态叫做TIME_WAIT

有人发现了这个确切的问题。

不过,如果我尝试停止服务器然后再启动它来测试一些修改,我会遇到一个随机的“socket.error: [Errno 98] 地址已经在使用中”的错误。这个错误只会在有客户端已经连接到服务器时出现。

我用netstat和ps检查了一下,发现虽然这个进程本身已经不再运行,但这个socket仍然在监听这个端口,并且状态是“TIME_WAIT”。基本上,操作系统会等一段时间,以确保这个连接没有剩余的数据包在路上。

15

出现 [Err 98] Address already in use 这个错误,是因为虽然我们已经关闭了一个连接,但系统还在等一段时间,以确保对方收到了我们关闭连接的请求(这和网络协议中的 TIME_WAIT 有关)。默认情况下,如果某个端口已经被一个连接占用,你是不能再绑定新的连接的。不过,你可以通过设置 allow_reuse_address(也叫 SO_REUSEADDR)来改变这个规则。

虽然可以修改 TCPServer.allow_reuse_addr(就像在 另一个回答 中提到的那样),但我觉得创建一个自己的 TCPServer 子类,并把 allow_reuse_address 设置为 True 会更干净:

import SocketServer
import SimpleHTTPServer
import time

class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET():
        time.sleep(60)
        self.request.sendall("I'm here!")

class ReuseAddrTCPServer(SocketServer.TCPServer):
    allow_reuse_address = True

PORT = 8089

httpd = ReuseAddrTCPServer(("", PORT), MyRequestHandler)
httpd.daemon_threads = True


print "Serving forever at port", PORT
try:
    httpd.serve_forever()
except:
    print "Closing the server."
    httpd.server_close()
    raise

你也可以直接在实例上设置 allow_reuse_address(这样就不用去改类了),但你需要使用 TCPServer(..., bind_and_activate=False),否则在你有机会更改 allow_reuse_address 设置之前,套接字就会被绑定。然后你需要手动调用 .server_bind().server_activate(),再调用 serve_forever()

...
httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler, bind_and_activate=False)
httpd.allow_reuse_address = True
httpd.daemon_threads = True
...
httpd.server_bind()
httpd.server_activate()
httpd.serve_forever()
33

感谢其他人的回答,我明白了。allow_reuse_address 应该设置在类上,而不是实例上:

SocketServer.TCPServer.allow_reuse_address = True
httpd = SocketServer.TCPServer(("", PORT), MyRequestHandler)

不过我还是不太明白,为什么关闭套接字后,它没有为下次服务器运行释放出来。

撰写回答