Python:如何在recv中等待数据时关闭UDP套接字?

时间:2010-05-26 10:57:47

标签: python multithreading sockets recv

让我们在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"

它运行一个交互式shell,我可以使用它来启动和停止UDP服务器。 服务器是通过一个类来实现的,该类启动一个线程,其中try / except块内有一个无限循环的 recv / onPacket 回调,它应该检测错误和退出从循环。 我期望的是当我在shell上键入“stop”时,套接字被关闭,并且 recvfrom 函数引发异常,因为文件描述符无效。 相反,即使在关闭调用之后,似乎 recvfrom 仍然阻止等待数据的线程。 为何这种奇怪的行为? 我总是使用这种模式来实现C ++和JAVA中的UDP服务器,它始终有效。

我还尝试使用“选择”将带有套接字的列表传递给 xread 参数,以便从<<>获取文件描述符中断事件strong>选择而不是 recvfrom ,但选择似乎对关闭“不敏感”。

我需要一个独特的代码,在Linux和Windows上使用python 2.5 - 2.6维护相同的行为。

感谢。

2 个答案:

答案 0 :(得分:3)

通常的解决方案是让管道告诉工人线程何时死亡。

  1. 使用os.pipe创建管道。这为您提供了一个在同一程序中具有读写结束的套接字。它返回原始文件描述符,您可以按原样使用os.reados.write),或使用os.fdopen转换为Python文件对象。

  2. 工作线程使用select.select等待 网络套接字和管道的读取端。当管道变得可读时,工作线程会清理并退出。不要读取数据,忽略它:它的到来就是信息。

  3. 当主线程要杀死工作线程时,它会将一个字节(任何值)写入管道的写入端。然后主线程加入工作线程,然后关闭管道(记得关闭两端)。

  4. P.S。在多线程程序中关闭正在使用的套接字是一个坏主意。 Linux close(2)联机帮助页说:

      

    关闭文件描述符可能是不明智的,因为它们可能在同一进程中的其他线程中被系统调用使用。由于文件描述符可能会被重复使用,因此有一些模糊的竞争条件可能会导致意外的副作用。

    所以很幸运,你的第一种方法不起作用!

答案 1 :(得分:1)

这不是java。好提示:

  • 不要使用线程。使用异步IO。
  • 使用更高级别的网络框架

以下是使用twisted的示例:

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