1

Pythonでこのコードを考えてみましょう:

import socket
import threading
import sys
import select


class UDPServer:
    def __init__(self):
        self.s=None
        self.t=None
    def start(self,port=8888):
        if not self.s:
            self.s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.s.bind(("",port))
            self.t=threading.Thread(target=self.run)
            self.t.start()
    def stop(self):
        if self.s:
            self.s.close()
            self.t.join()
            self.t=None
    def run(self):
        while True:
            try:
                #receive data
                data,addr=self.s.recvfrom(1024)
                self.onPacket(addr,data)
            except:
                break
        self.s=None
    def onPacket(self,addr,data):
        print addr,data


us=UDPServer()
while True:
    sys.stdout.write("UDP server> ")
    cmd=sys.stdin.readline()
    if cmd=="start\n":
        print "starting server..."
        us.start(8888)
        print "done"
    elif cmd=="stop\n":
        print "stopping server..."
        us.stop()
        print "done"
    elif cmd=="quit\n":
        print "Quitting ..."
        us.stop()
        break;

print "bye bye"

UDP サーバーを起動および停止できる対話型シェルを実行します。サーバーは、エラーを検出してループを終了する必要がある try/except ブロック内にrecv / onPacketコールバックの無限ループがあるスレッドを起動するクラスを通じて実装されます。私が期待しているのは、シェルで「stop」と入力すると、ソケットが閉じられ、ファイル記述子の無効化のためにrecvfrom関数によって例外が発生することです。代わりに、recvfromは、 close呼び出しの後でも、データを待機しているスレッドをブロックしているようです。なぜこの奇妙な動作ですか?私は常にこのパターンを使用して、C++ と Java で UDP サーバーを実装してきましたが、常に機能していました。

recvfromからの代わりにselectからファイル記述子の中断のイベントを取得するために、ソケットを含むリストをxread引数に渡す「 select 」も試しましたが、select最後まで「無意味」に見えます.

Python 2.5 - 2.6 を使用して、Linux と Windows で同じ動作を維持する独自のコードが必要です。

ありがとう。

4

2 に答える 2

4

通常の解決策は、いつ終了するかをワーカー スレッドに伝えるパイプを持たせることです。

  1. を使用してパイプを作成しますos.pipe。これにより、同じプログラム内で読み取りと書き込みの両方が終了するソケットが得られます。生のファイル記述子を返します。これは、そのまま使用するか (os.readおよびos.write)、または を使用して Python ファイル オブジェクトに変換できますos.fdopen

  2. ワーカー スレッドは、ネットワーク ソケットとパイプの読み取り側の両方select.selectで を使用して待機します。パイプが読み取り可能になると、ワーカー スレッドはクリーンアップして終了します。データを読み取らず、無視してください。その到着がメッセージです。

  3. マスター スレッドがワーカーを強制終了する場合、パイプの書き込み側に 1 バイト (任意の値) を書き込みます。次に、マスター スレッドがワーカー スレッドに参加し、パイプを閉じます (両端を閉じることを忘れないでください)。

PSマルチスレッドプログラムでは、使用中のソケットを閉じることは悪い考えです。Linux の close(2) マンページには次のように書かれています。

ファイル記述子が同じプロセス内の他のスレッドのシステム コールによって使用されている可能性があるときに、ファイル記述子を閉じることはおそらく賢明ではありません。ファイル記述子は再利用される可能性があるため、意図しない副作用を引き起こす可能性があるいくつかの不明瞭な競合状態があります。

したがって、最初のアプローチがうまくいかなかったのは幸運です!

于 2010-05-26T15:54:01.493 に答える
1

これはジャバではありません。良いヒント:

  • スレッドを使用しないでください。非同期 IO を使用します。
  • より高いレベルのネットワーク フレームワークを使用する

ツイストを使用した例を次に示します。

from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor, stdio
from twisted.protocols.basic import LineReceiver

class UDPLogger(DatagramProtocol):    
    def datagramReceived(self, data, (host, port)):
        print "received %r from %s:%d" % (data, host, port)


class ConsoleCommands(LineReceiver):
    delimiter = '\n'
    prompt_string = 'myserver> '

    def connectionMade(self):
        self.sendLine('My Server Admin Console!')
        self.transport.write(self.prompt_string)

    def lineReceived(self, line):
        line = line.strip()
        if line:
            if line == 'quit':
                reactor.stop()
            elif line == 'start':
                reactor.listenUDP(8888, UDPLogger())
                self.sendLine('listening on udp 8888')
            else:
                self.sendLine('Unknown command: %r' % (line,))
        self.transport.write(self.prompt_string)

stdio.StandardIO(ConsoleCommands())
reactor.run()

セッションの例:

My Server Admin Console!
myserver> foo  
Unknown command: 'foo'
myserver> start
listening on udp 8888
myserver> quit
于 2010-05-26T11:33:24.803 に答える