5

我尝试在 Python 中使用线程(ThreadingMixIn)对 TCPServer 进行编程。socket.error: [Errno 48] Address already in use问题是当我尝试再次运行它时,我无法正确关闭它。这是触发问题的 Python 代码的最小示例:

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()

所有主要代码应该做的是启动服务器,关闭它并再次运行它。但它会触发上述错误,因为第一次关闭后套接字尚未释放。

我认为设置self.allow_reuse_address = True可以解决问题,但没有奏效。当 python 程序完成后,我可以立即再次运行它,它可以启动服务器一次(但不能再次启动两次)。
但是,当我随机化端口(例如替换为)时,问题就消失了,1234因为1234+i没有其他服务器正在侦听该地址。

有一个类似的 SO Q从 ThreadingTCPServer 正常关闭,但解决方案(设置allow_reuse_addressTrue不适用于我的代码,我不使用 ThreadingTCPServer)。

我如何必须修改我的代码才能在我的代码中启动服务器两次?

更多信息:我这样做的原因是我想为我的 python 项目运行一些单元测试。这需要提供我的软件应该连接到的(假)服务器。

编辑
我刚刚找到了对我的问题最正确的答案:我必须fake_server.server_close()在我的主要执行代码的末尾添加(就在之后fake_server.shutdown())。我在TCPServer实现的源文件中找到了它。它所做的只是self.socket.close()

4

3 回答 3

4

不知何故,fake_server当您分配给它时不会解除绑定(在for语句的第一行中)。

要解决这个问题,只需fake_server在循环结束时删除:

        del fake_server # force server to unbind
于 2011-03-07T10:37:14.933 回答
4

这篇文章帮助我克服了未关闭的套接字问题。我遇到了同样的问题,想在这里发布我对 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()

我用的方法:导入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

好好享受!

于 2012-06-17T18:07:03.623 回答
2

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()
于 2011-03-07T10:37:15.353 回答