如何从 python 中的另一个线程中止 socket.recvfrom() ?
这看起来像 How do我从Python中的另一个线程中中止socket.recv(),但事实并非如此,因为我想中止线程中的recvfrom(),该线程是UDP,而不是TCP。
这可以通过 poll() 或 select.select() 解决吗?
This looks like a duplicate of How do I abort a socket.recv() from another thread in Python, but it's not, since I want to abort recvfrom() in a thread, which is UDP, not TCP.
Can this be solved by poll() or select.select() ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
如果您想解锁从另一个线程读取 UDP,请向其发送数据报!
平均值,
马丁
If you want to unblock a UDP read from another thread, send it a datagram!
Rgds,
Martin
处理这种异步中断的一个好方法是旧的 C 管道技巧。您可以创建一个管道并在套接字和管道上使用
select
/poll
:现在,当您需要中断接收器时,您只需向管道发送一个字符即可。起始点
interruptable_socket.py
测试套件:
test_interruptable_socket.py
演变
现在这段代码只是一个起点。为了使其更通用,我认为我们应该解决以下问题:
socket.recv()
之前调用一个函数,将中断扩展到其他recv
方法变得非常简单首先,我们将
test_interrupt_async()
更改为检查异常而不是空消息:此后,我们可以将
return ''
替换引发 InterruptException
并且测试再次通过。准备扩展的版本可以是:
interruptable_socket.py
现在包装了更多
recv
函数,实现 Windows 版本或处理套接字超时变得非常简单。A good way to handle this kind of asynchronous interruption is the old C pipe trick. You can create a pipe and use
select
/poll
on both socket and pipe: Now when you want interrupt receiver you can just send a char to the pipe.Starting point
interruptable_socket.py
A test suite:
test_interruptable_socket.py
Evolution
Now this code is just a starting point. To make it more generic I see we should fix follow issues:
socket.recv()
, extend interrupt to othersrecv
methods become very simpleFirst of all we change
test_interrupt_async()
to check exception instead empty message:After this we can replace
return ''
byraise InterruptException
and the tests pass again.The ready to extend version can be :
interruptable_socket.py
Now wraps more
recv
function, implement a windows version or take care of socket timeouts become really simple.这里的解决办法是强行关闭socket。问题在于,执行此操作的方法是特定于操作系统的,并且 Python 不能很好地抽象执行此操作的方法或后果。基本上,您需要在套接字上执行 shutdown() 操作,然后执行 close() 操作。在 POSIX 系统(例如 Linux)上,关闭是强制 recvfrom 停止的关键元素(仅调用 close() 是无法做到这一点的)。在Windows上,shutdown()不会影响recvfrom,close()是关键元素。如果您在 C 中实现此代码并使用本机 POSIX 套接字或 Winsock 套接字,这正是您会看到的行为,因此 Python 在这些调用之上提供了一个非常薄的层。
在 POSIX 和 Windows 系统上,此调用序列会导致引发 OSError。但是,异常的位置及其详细信息是特定于操作系统的。在 POSIX 系统上,调用 shutdown() 时会引发异常,并且异常的 errno 值设置为 107(传输端点未连接)。在 Windows 系统上,调用 recvfrom() 时会引发异常,并且异常的 winerror 值设置为 10038(尝试对非套接字的操作)。这意味着无法以与操作系统无关的方式执行此操作,代码必须考虑 Windows 和 POSIX 行为和错误。这是我写的一个简单的例子:
The solution here is to forcibly close the socket. The problem is that the method for doing this is OS-specific and Python does not do a good job of abstracting the way to do it or the consequences. Basically, you need to do a shutdown() followed by a close() on the socket. On POSIX systems such as Linux, the shutdown is the key element in forcing recvfrom to stop (a call to close() alone won't do it). On Windows, shutdown() does not affect the recvfrom and the close() is the key element. This is exactly the behavior that you would see if you were implementing this code in C and using either native POSIX sockets or Winsock sockets, so Python is providing a very thin layer on top of those calls.
On both POSIX and Windows systems, this sequence of calls results in an OSError being raised. However, the location of the exception and the details of it are OS-specific. On POSIX systems, the exception is raised on the call to shutdown() and the errno value of the exception is set to 107 (Transport endpoint is not connected). On Windows systems, the exception is raised on the call to recvfrom() and the winerror value of the exception is set to 10038 (An operation was attempted on something that is not a socket). This means that there's no way to do this in an OS-agnositc way, the code has to account for both Windows and POSIX behavior and errors. Here's a simple example I wrote up:
在服务器和客户端套接字上执行退出命令。应该像这样工作:
Implement a quit command on the server and client sockets. Should work something like this:
要在Python中正确关闭tcp套接字,您必须在调用socket.close()之前调用socket.shutdown(arg)。请参阅python套接字文档,有关关闭的部分。
如果套接字是UDP,则不能调用socket.shutdown(...),它会引发异常。单独调用 socket.close() 会像 TCP 一样,使被阻止的操作保持阻塞状态。 close() 单独不会中断它们。
许多建议的解决方案(并非全部)不起作用或被认为很麻烦,因为它们涉及第三方库。我还没有测试 poll() 或 select()。肯定有效的方法如下:
首先,为运行 socket.recv() 的任何线程创建一个正式的 Thread 对象,并将句柄保存到它。其次,导入信号。 Signal 是一个官方库,它允许向进程发送/接收 linux/posix 信号(阅读其文档)。第三,要中断,假设线程的句柄称为 udpThreadHandle:
当然,在执行接收的实际线程/循环中:
注意,KeyboardInterrupt 的异常处理程序(由 SIGINT 生成)位于接收循环之外。这会默默地终止接收循环及其线程。
To properly close a tcp socket in python, you have to call socket.shutdown(arg) before calling socket.close(). See the python socket documentation, the part about shutdown.
If the socket is UDP, you can't call socket.shutdown(...), it would raise an exception. And calling socket.close() alone would, like for tcp, keep the blocked operations blocking. close() alone won't interrupt them.
Many suggested solutions (not all), don't work or are seen as cumbersome as they involve 3rd party libraries. I haven't tested poll() or select(). What does definately work, is the following:
firstly, create an official Thread object for whatever thread is running socket.recv(), and save the handle to it. Secondly, import signal. Signal is an official library, which enables sending/recieving linux/posix signals to processes (read its documentation). Thirdly, to interrupt, assuming that handle to your thread is called udpThreadHandle:
and ofcourse, in the actual thread/loop doing the recieving:
Notice, the exception handler for KeyboardInterrupt (generated by SIGINT), is OUTSIDE the recieve loop. This silently terminates the recieve loop and its thread.