在关闭后直接在代码中再次启动带有ThreadingMixIn的TCPServer(出现`地址已在使用`错误)
我在用Python编写一个带线程的TCP服务器(ThreadingMixIn)。遇到的问题是,当我尝试再次运行它时,出现了socket.error: [Errno 48] Address already in use
的错误,导致我无法正常关闭服务器。以下是一个简单的代码示例,展示了这个问题是如何产生的:
import socket
import threading
import SocketServer
class FakeNetio230aHandler(SocketServer.BaseRequestHandler):
def send(self,message):
self.request.send(message+N_LINE_ENDING)
def handle(self):
self.request.send("Hello\n")
class FakeNetio230a(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def __init__(self, server_address, RequestHandlerClass):
self.allow_reuse_address = True
SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
if __name__ == '__main__':
for i in range(2):
fake_server = FakeNetio230a(("", 1234), FakeNetio230aHandler)
server_thread = threading.Thread(target=fake_server.serve_forever)
server_thread.setDaemon(True)
server_thread.start()
# might add some client connection here
fake_server.shutdown()
这段主要代码的目的是启动服务器、关闭它,然后再运行一次。但是因为在第一次关闭后,socket没有被释放,所以会出现上面提到的错误。
我原以为设置self.allow_reuse_address = True
可以解决这个问题,但并没有成功。当Python程序结束后,我可以立即再次运行它,并且可以启动服务器一次(但再次启动就不行了)。
不过,当我随机更改端口号(比如把1234
替换成1234+i
)时,问题就消失了,因为没有其他服务器在监听那个地址。
有一个类似的问题在SO上提到过,链接是Shutting down gracefully from ThreadingTCPServer,但那里的解决方案(把allow_reuse_address
设置为True
)对我的代码不管用,而且我也没有使用ThreadingTCPServer。
我该如何修改我的代码,以便能在我的代码中启动服务器两次?
更多信息:我这样做的原因是我想为我的Python项目运行一些单元测试。这需要提供一个(假的)服务器,让我的软件可以连接。
编辑:
我刚刚找到了最正确的解决办法:我需要在主执行代码的最后加上fake_server.server_close()
(就在fake_server.shutdown()
之后)。我是在TCPServer
的实现源文件中找到这个的。它的作用就是self.socket.close()
。
3 个回答
把你的 FakeNetio230a
定义改成这样:
class FakeNetio230a(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def __init__(self, server_address, RequestHandlerClass):
self.allow_reuse_address = True
SocketServer.TCPServer.__init__(self,
server_address,
RequestHandlerClass,
False) # do not implicitly bind
然后,在你创建 FakeNetio230a 的地方下面,添加这两行代码:
fake_server.server_bind() # explicitly bind
fake_server.server_activate() # activate the server
这里有个例子:
if __name__ == '__main__':
for i in range(2):
fake_server = FakeNetio230a(("", 1234), FakeNetio230aHandler)
fake_server.server_bind() # explicitly bind
fake_server.server_activate() # activate the server
server_thread = threading.Thread(target=fake_server.serve_forever)
server_thread.setDaemon(True)
server_thread.start()
# might add some client connection here
fake_server.shutdown()
这篇帖子帮我解决了未关闭套接字的问题。我也遇到了同样的问题,所以想在这里分享一下我简单实现的TCP服务器类(还有客户端方法)。
我创建了一个叫做 TCPThreadedServer
的类。要使用它,需要进行继承,并且必须重写 process(msg)
这个方法。每当服务器收到一条消息 msg
时,重写的方法就会被调用。如果这个方法返回的不是 None
,那么返回的内容会作为字符串发送给连接的客户端。
from SocketServer import TCPServer, StreamRequestHandler, ThreadingMixIn
import threading
class TCPThreadedServer(TCPServer, ThreadingMixIn):
class RequstHandler(StreamRequestHandler):
def handle(self):
msg = self.rfile.readline().strip()
reply = self.server.process(msg)
if reply is not None:
self.wfile.write(str(reply) + '\n')
def __init__(self, host, port, name=None):
self.allow_reuse_address = True
TCPServer.__init__(self, (host, port), self.RequstHandler)
if name is None: name = "%s:%s" % (host, port)
self.name = name
self.poll_interval = 0.5
def process(self, msg):
"""
should be overridden
process a message
msg - string containing a received message
return - if returns a not None object, it will be sent back
to the client.
"""
raise NotImplemented
def serve_forever(self, poll_interval=0.5):
self.poll_interval = poll_interval
self.trd = threading.Thread(target=TCPServer.serve_forever,
args = [self, self.poll_interval],
name = "PyServer-" + self.name)
self.trd.start()
def shutdown(self):
TCPServer.shutdown(self)
TCPServer.server_close(self)
self.trd.join()
del self.trd
我发现使用起来非常简单:
class EchoServerExample(TCPThreadedServer):
def __init__(self):
TCPThreadedServer.__init__(self, "localhost", 1234, "Server")
def process(self, data):
print "EchoServer Got: " + data
return str.upper(data)
for i in range(10):
echo = EchoServerExample()
echo.serve_forever()
response = client("localhost", 1234, "hi-%i" % i)
print "Client received: " + response
echo.shutdown()
我使用的方法是: import socket
def client(ip, port, msg, recv_len=4096,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
msg = str(msg)
response = None
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect((ip, port))
if timeout != socket._GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
sock.send(msg + "\n")
if recv_len > 0:
response = sock.recv(recv_len)
finally:
sock.close()
return response
希望你喜欢它!
有些情况下,当你在for
循环的第一行给fake_server
赋值时,它并不会解除绑定。
要解决这个问题,只需要在循环的最后去掉fake_server
:
del fake_server # force server to unbind