Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何从python中的另一个线程中止socket.recvfrom()?_Python_Multithreading_Udp_Pthreads - Fatal编程技术网

如何从python中的另一个线程中止socket.recvfrom()?

如何从python中的另一个线程中止socket.recvfrom()?,python,multithreading,udp,pthreads,Python,Multithreading,Udp,Pthreads,这看起来像的副本,但不是,因为我想在一个线程中中止recvfrom(),该线程是UDP,而不是TCP 这可以通过poll()或select.select()来解决吗?如果要取消阻止从另一个线程读取UDP,请向其发送数据报 Rgds, Martin在服务器和客户端套接字上执行quit命令。应该像这样工作: Thread1: status: listening handler: quit Thread2: client exec: socket.send "quit"

这看起来像的副本,但不是,因为我想在一个线程中中止recvfrom(),该线程是UDP,而不是TCP


这可以通过poll()或select.select()来解决吗?

如果要取消阻止从另一个线程读取UDP,请向其发送数据报

Rgds,
Martin

在服务器和客户端套接字上执行quit命令。应该像这样工作:

Thread1: 
    status: listening
    handler: quit

Thread2: client
    exec: socket.send "quit"  ---> Thread1.socket @ host:port

Thread1: 
    status: socket closed()

处理这种异步中断的一个好方法是旧的C管道技巧。您可以创建一个管道,并在套接字和管道上使用
select
/
poll
:现在,当您需要中断接收器时,只需向管道发送一个字符即可

  • 优点:
    • 可以同时适用于UDP和TCP
    • 协议是不可知的吗
  • 缺点:
    • Windows上的“管道上的选择/轮询”不可用,在这种情况下,应将其替换为另一个用作通知管道的UDP套接字
起点
interruptable_socket.py

import os
import socket
import select


class InterruptableUdpSocketReceiver(object):
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._r_pipe, self._w_pipe = os.pipe()
        self._interrupted = False

    def bind(self):
        self._socket.bind((self._host, self._port))

    def recv(self, buffersize, flags=0):
        if self._interrupted:
            raise RuntimeError("Cannot be reused")
        read, _w, errors = select.select([self._r_pipe, self._socket], [], [self._socket])
        if self._socket in read:
            return self._socket.recv(buffersize, flags)
        return ""

    def interrupt(self):
        self._interrupted = True
        os.write(self._w_pipe, "I".encode())
import socket
from threading import Timer
import time
from interruptable_socket import InterruptableUdpSocketReceiver
import unittest


class Sender(object):
    def __init__(self, destination_host, destination_port):
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        self._dest = (destination_host, destination_port)

    def send(self, message):
        self._socket.sendto(message, self._dest)

class Test(unittest.TestCase):
    def create_receiver(self, host="127.0.0.1", port=3010):
        receiver = InterruptableUdpSocketReceiver(host, port)
        receiver.bind()
        return receiver

    def create_sender(self, host="127.0.0.1", port=3010):
        return Sender(host, port)

    def create_sender_receiver(self, host="127.0.0.1", port=3010):
        return self.create_sender(host, port), self.create_receiver(host, port)

    def test_create(self):
        self.create_receiver()

    def test_recv_async(self):
        sender, receiver = self.create_sender_receiver()
        start = time.time()
        send_message = "TEST".encode('UTF-8')
        Timer(0.1, sender.send, (send_message, )).start()
        message = receiver.recv(128)
        elapsed = time.time()-start
        self.assertGreaterEqual(elapsed, 0.095)
        self.assertLess(elapsed, 0.11)
        self.assertEqual(message, send_message)

    def test_interrupt_async(self):
        receiver = self.create_receiver()
        start = time.time()
        Timer(0.1, receiver.interrupt).start()
        message = receiver.recv(128)
        elapsed = time.time()-start
        self.assertGreaterEqual(elapsed, 0.095)
        self.assertLess(elapsed, 0.11)
        self.assertEqual(0, len(message))

    def test_exception_after_interrupt(self):
        sender, receiver = self.create_sender_receiver()
        receiver.interrupt()
        with self.assertRaises(RuntimeError):
            receiver.recv(128)


if __name__ == '__main__':
    unittest.main()
import os
import socket
import select


class InterruptException(Exception):
    pass


class InterruptableUdpSocketReceiver(object):
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._async_interrupt = AsycInterrupt(self._socket)

    def bind(self):
        self._socket.bind((self._host, self._port))

    def recv(self, buffersize, flags=0):
        self._async_interrupt.wait_for_receive()
        return self._socket.recv(buffersize, flags)

    def interrupt(self):
        self._async_interrupt.interrupt()


class AsycInterrupt(object):
    def __init__(self, descriptor):
        self._read, self._write = os.pipe()
        self._interrupted = False
        self._descriptor = descriptor

    def interrupt(self):
        self._interrupted = True
        self._notify()

    def wait_for_receive(self):
        if self._interrupted:
            raise RuntimeError("Cannot be reused")
        read, _w, errors = select.select([self._read, self._descriptor], [], [self._descriptor])
        if self._descriptor not in read:
            raise InterruptException

    def _notify(self):
        os.write(self._write, "I".encode())
测试套件:

test\u interruptable\u socket.py

import os
import socket
import select


class InterruptableUdpSocketReceiver(object):
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._r_pipe, self._w_pipe = os.pipe()
        self._interrupted = False

    def bind(self):
        self._socket.bind((self._host, self._port))

    def recv(self, buffersize, flags=0):
        if self._interrupted:
            raise RuntimeError("Cannot be reused")
        read, _w, errors = select.select([self._r_pipe, self._socket], [], [self._socket])
        if self._socket in read:
            return self._socket.recv(buffersize, flags)
        return ""

    def interrupt(self):
        self._interrupted = True
        os.write(self._w_pipe, "I".encode())
import socket
from threading import Timer
import time
from interruptable_socket import InterruptableUdpSocketReceiver
import unittest


class Sender(object):
    def __init__(self, destination_host, destination_port):
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        self._dest = (destination_host, destination_port)

    def send(self, message):
        self._socket.sendto(message, self._dest)

class Test(unittest.TestCase):
    def create_receiver(self, host="127.0.0.1", port=3010):
        receiver = InterruptableUdpSocketReceiver(host, port)
        receiver.bind()
        return receiver

    def create_sender(self, host="127.0.0.1", port=3010):
        return Sender(host, port)

    def create_sender_receiver(self, host="127.0.0.1", port=3010):
        return self.create_sender(host, port), self.create_receiver(host, port)

    def test_create(self):
        self.create_receiver()

    def test_recv_async(self):
        sender, receiver = self.create_sender_receiver()
        start = time.time()
        send_message = "TEST".encode('UTF-8')
        Timer(0.1, sender.send, (send_message, )).start()
        message = receiver.recv(128)
        elapsed = time.time()-start
        self.assertGreaterEqual(elapsed, 0.095)
        self.assertLess(elapsed, 0.11)
        self.assertEqual(message, send_message)

    def test_interrupt_async(self):
        receiver = self.create_receiver()
        start = time.time()
        Timer(0.1, receiver.interrupt).start()
        message = receiver.recv(128)
        elapsed = time.time()-start
        self.assertGreaterEqual(elapsed, 0.095)
        self.assertLess(elapsed, 0.11)
        self.assertEqual(0, len(message))

    def test_exception_after_interrupt(self):
        sender, receiver = self.create_sender_receiver()
        receiver.interrupt()
        with self.assertRaises(RuntimeError):
            receiver.recv(128)


if __name__ == '__main__':
    unittest.main()
import os
import socket
import select


class InterruptException(Exception):
    pass


class InterruptableUdpSocketReceiver(object):
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._async_interrupt = AsycInterrupt(self._socket)

    def bind(self):
        self._socket.bind((self._host, self._port))

    def recv(self, buffersize, flags=0):
        self._async_interrupt.wait_for_receive()
        return self._socket.recv(buffersize, flags)

    def interrupt(self):
        self._async_interrupt.interrupt()


class AsycInterrupt(object):
    def __init__(self, descriptor):
        self._read, self._write = os.pipe()
        self._interrupted = False
        self._descriptor = descriptor

    def interrupt(self):
        self._interrupted = True
        self._notify()

    def wait_for_receive(self):
        if self._interrupted:
            raise RuntimeError("Cannot be reused")
        read, _w, errors = select.select([self._read, self._descriptor], [], [self._descriptor])
        if self._descriptor not in read:
            raise InterruptException

    def _notify(self):
        os.write(self._write, "I".encode())
进化 现在,这段代码只是一个起点。为了使其更通用,我认为我们应该解决以下问题:

  • 接口:在中断情况下返回空消息不是一个好办法,最好使用异常来处理它
  • 泛化:在
    socket.recv()
    之前,我们应该只调用一个函数,将中断扩展到其他
    recv
    方法变得非常简单
  • 可移植性:要将其简单地移植到windows,我们应该隔离对象中的异步通知,以便为我们的操作系统选择正确的实现
  • 首先,我们更改
    test\u interrupt\u async()
    以检查异常,而不是空消息:

    from interruptable_socket import InterruptException
    
    def test_interrupt_async(self):
        receiver = self.create_receiver()
        start = time.time()
        with self.assertRaises(InterruptException):
            Timer(0.1, receiver.interrupt).start()
            receiver.recv(128)
        elapsed = time.time()-start
        self.assertGreaterEqual(elapsed, 0.095)
        self.assertLess(elapsed, 0.11)
    
    在此之后,我们可以将
    返回'
    替换为
    引发中断异常
    ,测试再次通过

    准备扩展的版本可以是:

    interruptable_socket.py

    import os
    import socket
    import select
    
    
    class InterruptableUdpSocketReceiver(object):
        def __init__(self, host, port):
            self._host = host
            self._port = port
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self._r_pipe, self._w_pipe = os.pipe()
            self._interrupted = False
    
        def bind(self):
            self._socket.bind((self._host, self._port))
    
        def recv(self, buffersize, flags=0):
            if self._interrupted:
                raise RuntimeError("Cannot be reused")
            read, _w, errors = select.select([self._r_pipe, self._socket], [], [self._socket])
            if self._socket in read:
                return self._socket.recv(buffersize, flags)
            return ""
    
        def interrupt(self):
            self._interrupted = True
            os.write(self._w_pipe, "I".encode())
    
    import socket
    from threading import Timer
    import time
    from interruptable_socket import InterruptableUdpSocketReceiver
    import unittest
    
    
    class Sender(object):
        def __init__(self, destination_host, destination_port):
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
            self._dest = (destination_host, destination_port)
    
        def send(self, message):
            self._socket.sendto(message, self._dest)
    
    class Test(unittest.TestCase):
        def create_receiver(self, host="127.0.0.1", port=3010):
            receiver = InterruptableUdpSocketReceiver(host, port)
            receiver.bind()
            return receiver
    
        def create_sender(self, host="127.0.0.1", port=3010):
            return Sender(host, port)
    
        def create_sender_receiver(self, host="127.0.0.1", port=3010):
            return self.create_sender(host, port), self.create_receiver(host, port)
    
        def test_create(self):
            self.create_receiver()
    
        def test_recv_async(self):
            sender, receiver = self.create_sender_receiver()
            start = time.time()
            send_message = "TEST".encode('UTF-8')
            Timer(0.1, sender.send, (send_message, )).start()
            message = receiver.recv(128)
            elapsed = time.time()-start
            self.assertGreaterEqual(elapsed, 0.095)
            self.assertLess(elapsed, 0.11)
            self.assertEqual(message, send_message)
    
        def test_interrupt_async(self):
            receiver = self.create_receiver()
            start = time.time()
            Timer(0.1, receiver.interrupt).start()
            message = receiver.recv(128)
            elapsed = time.time()-start
            self.assertGreaterEqual(elapsed, 0.095)
            self.assertLess(elapsed, 0.11)
            self.assertEqual(0, len(message))
    
        def test_exception_after_interrupt(self):
            sender, receiver = self.create_sender_receiver()
            receiver.interrupt()
            with self.assertRaises(RuntimeError):
                receiver.recv(128)
    
    
    if __name__ == '__main__':
        unittest.main()
    
    import os
    import socket
    import select
    
    
    class InterruptException(Exception):
        pass
    
    
    class InterruptableUdpSocketReceiver(object):
        def __init__(self, host, port):
            self._host = host
            self._port = port
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self._async_interrupt = AsycInterrupt(self._socket)
    
        def bind(self):
            self._socket.bind((self._host, self._port))
    
        def recv(self, buffersize, flags=0):
            self._async_interrupt.wait_for_receive()
            return self._socket.recv(buffersize, flags)
    
        def interrupt(self):
            self._async_interrupt.interrupt()
    
    
    class AsycInterrupt(object):
        def __init__(self, descriptor):
            self._read, self._write = os.pipe()
            self._interrupted = False
            self._descriptor = descriptor
    
        def interrupt(self):
            self._interrupted = True
            self._notify()
    
        def wait_for_receive(self):
            if self._interrupted:
                raise RuntimeError("Cannot be reused")
            read, _w, errors = select.select([self._read, self._descriptor], [], [self._descriptor])
            if self._descriptor not in read:
                raise InterruptException
    
        def _notify(self):
            os.write(self._write, "I".encode())
    

    现在,封装更多的recv功能、实现windows版本或处理套接字超时变得非常简单。

    这里的解决方案是强制关闭套接字。问题在于,执行此操作的方法是特定于操作系统的,Python没有很好地抽象出执行方法或结果。基本上,您需要在套接字上执行shutdown()和close()。在POSIX系统(如Linux)上,关闭是强制recvfrom停止的关键因素(仅调用close()是不行的)。在Windows上,shutdown()不影响recvfrom,close()是关键元素。这正是您在C中实现此代码并使用本机POSIX套接字或Winsock套接字时所看到的行为,因此Python在这些调用之上提供了一个非常薄的层

    在POSIX和Windows系统上,此调用序列都会导致引发操作错误。但是,异常的位置和详细信息是特定于操作系统的。在POSIX系统上,调用shutdown()时引发异常,异常的errno值设置为107(未连接传输端点)。在Windows系统上,调用recvfrom()时引发异常,异常的winerror值设置为10038(尝试对非套接字的对象执行操作)。这意味着无法以与操作系统无关的方式执行此操作,代码必须同时考虑Windows和POSIX的行为和错误。下面是我写的一个简单的例子:

    import socket
    import threading
    import time
    
    class MyServer(object):
        def __init__(self, port:int=0):
            if port == 0:
                raise AttributeError('Invalid port supplied.')
    
            self.port = port
            self.socket = socket.socket(family=socket.AF_INET,
                    type=socket.SOCK_DGRAM)
            self.socket.bind(('0.0.0.0', port))
    
            self.exit_now = False
    
            print('Starting server.')
            self.thread = threading.Thread(target=self.run_server,
                    args=[self.socket])
            self.thread.start()
    
        def run_server(self, socket:socket.socket=None):
            if socket is None:
                raise AttributeError('No socket provided.')
    
            buffer_size = 4096
    
            while self.exit_now == False:
                data = b''
                try:
                    data, address = socket.recvfrom(buffer_size)
                except OSError as e:
                    if e.winerror == 10038:
                        # Error is, "An operation was attempted on something that
                        # is not a socket".  We don't care.
                        pass
                    else:
                        raise e
                if len(data) > 0:
                    print(f'Received {len(data)} bytes from {address}.')
    
        def stop(self):
            self.exit_now = True
            try:
                self.socket.shutdown(socket.SHUT_RDWR)
            except OSError as e:
                if e.errno == 107:
                    # Error is, "Transport endpoint is not connected".
                    # We don't care.
                    pass
                else:
                    raise e
            self.socket.close()
            self.thread.join()
            print('Server stopped.')
    
    
    if __name__ == '__main__':
        server = MyServer(5555)
        time.sleep(2)
        server.stop()
        exit(0)
    

    UDP或TCP与线程化与否无关。您可以使用旧的C技巧:创建一个假管道,并在假管道和套接字上使用select。当你想停止时,简单地发送一条消息给假。。。。如果我有时间,我将提交一份包含所有细节的答复。@qarma查看我的评论,并告诉我您是否对这种解决方案感兴趣。@Micheled'Amico是的,这是一份有效的答复solution@qarma如果
    (addr,port)
    组合是唯一的,我将在今天编写它。如果
    REUSEADDR
    处于启用状态,并且有几个进程正在侦听,那又如何呢?代码的数量太多了,为什么不直接对套接字进行子类化呢?因为在本例中,我不想处理所有4个
    recv
    方法并覆盖(和测试)所有这些方法。此外,如果我将
    socket
    子类化,我应该在所有
    recv
    s方法中处理套接字超时。这是一个示例和一套测试:用子类替换collaborator很简单(但您应该解决上述问题)。但在此之前,最好1-使用异常而不是返回空消息,2-创建一个用于检查的装饰器,并等待传入消息在所有4
    recv
    s中使用它,3-提取一个执行异步通知的对象,用windows中的UDP实现替换它。@qarma还有一件事:子类化是一个强大的工具,但有时最好使用协作器,因为当您将新对象子类化为父对象时,您应该考虑所有父对象的行为和责任。使用collaborator使之更简单遵循单一责任原则并使您的代码更干净:如果您在对象中仅使用
    recv_from
    ,为什么要创建一个负责所有
    socket
    方法的子类?当你需要一些其他方法时,你可以考虑在子类中改变它,你的测试会帮助你安全。哇,你是多产的!虽然我更喜欢与socket相同的API,并且代码库要小得多,但您的答案是这里最好的,ergo+50是您的。除非继承了socket FD,否则只需调用
    close()