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

发布于 2024-09-02 20:43:20 字数 1911 浏览 4 评论 0原文

让我们考虑一下 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 函数会引发异常。 相反,即使在 close 调用之后, recvfrom 似乎仍然会阻塞等待数据的线程。 为什么会有这种奇怪的行为? 我一直使用这种模式在 C++ 和 JAVA 中实现 UDP 服务器,并且它总是有效。

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

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

谢谢。

let's consider this code in 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"

It runs an interactive shell with which I can start and stop an UDP server.
The server is implemented through a class which launches a thread in which there's a infinite loop of recv/onPacket callback inside a try/except block which should detect the error and the exits from the loop.
What I expect is that when I type "stop" on the shell the socket is closed and an exception is raised by the recvfrom function because of the invalidation of the file descriptor.
Instead, it seems that recvfrom still to block the thread waiting for data even after the close call.
Why this strange behavior ?
I've always used this patter to implements an UDP server in C++ and JAVA and it always worked.

I've tried also with a "select" passing a list with the socket to the xread argument, in order to get an event of file descriptor disruption from select instead that from recvfrom, but select seems to be "insensible" to the close too.

I need to have a unique code which maintain the same behavior on Linux and Windows with python 2.5 - 2.6.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

宣告ˉ结束 2024-09-09 20:43:20

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

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

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

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

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

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

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

The usual solution is to have a pipe tell the worker thread when to die.

  1. Create a pipe using os.pipe. This gives you a socket with both the reading and writing ends in the same program. It returns raw file descriptors, which you can use as-is (os.read and os.write) or turn into Python file objects using os.fdopen.

  2. The worker thread waits on both the network socket and the read end of the pipe using select.select. When the pipe becomes readable, the worker thread cleans up and exits. Don't read the data, ignore it: its arrival is the message.

  3. When the master thread wants to kill the worker, it writes a byte (any value) to the write end of the pipe. The master thread then joins the worker thread, then closes the pipe (remember to close both ends).

P.S. Closing an in-use socket is a bad idea in a multi-threaded program. The Linux close(2) manpage says:

It is probably unwise to close file descriptors while they may be in use by system calls in other threads in the same process. Since a file descriptor may be re-used, there are some obscure race conditions that may cause unintended side effects.

So it's lucky your first approach did not work!

划一舟意中人 2024-09-09 20:43:20

这不是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

This is not java. Good hints:

  • Don't use threads. Use asynchronous IO.
  • Use a higher level networking framework

Here's an example using 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()

Example session:

My Server Admin Console!
myserver> foo  
Unknown command: 'foo'
myserver> start
listening on udp 8888
myserver> quit
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文