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 プログラムが終了したら、すぐにもう一度実行して、サーバーを 1 回 (ただし、2 回ではなく) 起動できます。
ただし、他のサーバーがそのアドレスでリッスンしていないため、ポートをランダム化すると (たとえば、に置き換えると) 1234、問題はなくなります。1234+i

同様の SO Q Shutting down gracefully from ThreadingTCPServerがありますが、解決策があります (自分のコードでは機能せず、ThreadingTCPServer を使用しallow_reuse_addressTrueいません)。

コードでサーバーを 2 回起動できるようにするには、コードをどのように変更する必要がありますか?

Some more information: The reason why I'm doing this is that I want to run some unit tests for my python project. This requires to provide a (fake) server that my software should to connect to.

edit:
I just found the most correct answer to my problem: I have to add fake_server.server_close() at the end of my main execution code (right after fake_server.shutdown()). I found it in the source file of the TCPServer implementation. All it does is self.socket.close().

4

3 に答える 3

4

この投稿は、閉じられていないソケットの問題を解決するのに役立ちました。私は同じ問題を抱えていたので、TCP サーバー クラス (およびクライアント メソッド) の単純な実装をここに投稿したいと思いました。

TCPThreadedServerクラスを作りました。使用するには、継承する必要があり、メソッドprocess(msg)をオーバーライドする必要があります。オーバーライドされたメソッドは、サーバーがメッセージを取得するたびに呼び出されmsg、notNoneオブジェクトを返す場合は、接続されたクライアントに文字列として返されます。

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

メソッドを使用しました:インポートソケット

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 に答える
4

どういうわけか、fake_server割り当てたときにバインドが解除されません(forステートメントの最初の行で)。

これを修正するにはfake_server、ループの最後で削除するだけです:

        del fake_server # force server to unbind
于 2011-03-07T10:37:14.933 に答える
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 インスタンス化の下のエントリ ポイントに次の 2 行を追加します。

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 に答える