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"

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

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

我需要一个唯一的代码,它在 Linux 和 Windows 上使用 python 2.5 - 2.6 保持相同的行为。

谢谢。

4

2 回答 2

4

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

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

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

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

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

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

所以很幸运你的第一种方法没有奏效!

于 2010-05-26T15:54:01.493 回答
1

这不是java。好提示:

  • 不要使用线程。使用异步 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 回答