如何在发送一个数据报后立即关闭DatagramTransport?

时间:2016-02-22 10:47:41

标签: python python-asyncio

我正在尝试在发送UDP数据包后立即关闭传输,我得到Exception in callback _SelectorDatagramTransport._read_ready()

import asyncio


class MyProtocol:
    def __init__(self, message, loop):
        self.message = message
        self.loop = loop
        self.transport = None

    def connection_made(self, transport):
        self.transport = transport
        print("Send:", self.message)
        self.transport.sendto(self.message.encode())
        self.transport.close()  # <----------

    def error_received(self, exc):
        print('Error received', exc)

    def connection_lost(self, exc):
        print("Socket closed, stop the event loop")
        self.loop.stop()

loop = asyncio.get_event_loop()
message = "hello"
connect = loop.create_datagram_endpoint(lambda: MyProtocol(message, loop), remote_addr=('127.0.0.1', 2222))
transport, protocol = loop.run_until_complete(connect)

loop.run_forever()

我得到的完整堆栈跟踪是在CPython 3.5.1中运行上面的代码片段时:

Socket closed, stop the event loop
Exception in callback _SelectorDatagramTransport._read_ready()
handle: <Handle _SelectorDatagramTransport._read_ready()>
Traceback (most recent call last):
  File "/home/ecerulm/.pyenv/versions/3.5.1/lib/python3.5/asyncio/selector_events.py", line 1002, in _read_ready
    data, addr = self._sock.recvfrom(self.max_size)
AttributeError: 'NoneType' object has no attribute 'recvfrom'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ecerulm/.pyenv/versions/3.5.1/lib/python3.5/asyncio/events.py", line 125, in _run
    self._callback(*self._args)
  File "/home/ecerulm/.pyenv/versions/3.5.1/lib/python3.5/asyncio/selector_events.py", line 1008, in _read_ready
    self._fatal_error(exc, 'Fatal read error on datagram transport')
  File "/home/ecerulm/.pyenv/versions/3.5.1/lib/python3.5/asyncio/selector_events.py", line 587, in _fatal_error
    self._loop.call_exception_handler({
AttributeError: 'NoneType' object has no attribute 'call_exception_handler'

我认为只有在UDP数据包被主动拒绝且ICMP目的地无法访问(我对此不感兴趣)时才会生成异常。

所以问题是这样做的正确方法是什么。发送后我不再对这种连接感兴趣所以我想尽快摆脱运输。 DatagramTransport.sendto()的文档只是说方法没有阻止。但是我怎么知道发送完成的时间? (完整我的意思是什么时候交给操作系统,而不是交付到远程)。

是否有任何其他asyncio协程异步发送UDP数据包await(甚至可能跳过整个create_datagram_endpoint)?

1 个答案:

答案 0 :(得分:1)

  

是否有任何其他asyncio协程异步发送UDP数据包await

我会基于DatagramTransport来源,将其包装在Future中以使其变得可屈服/等待。它会在出错时引发异常,并在成功时返回Trueexample PoC code

import asyncio
import socket

class UDPClient():

    def __init__(self, host, port, loop=None):
        self._loop = asyncio.get_event_loop() if loop is None else loop
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._sock.setblocking(False)
        self._addr = (host, port)
        self._future = None
        self._data = None

    def sendto(self, data):
        self._future = asyncio.Future(loop=self._loop)
        self.data = data if isinstance(data, bytes) else str(data).encode('utf-8')
        loop.add_writer(self._sock.fileno(), self._sendto)
        return self._future

    def _sendto(self):
        try:
            self._sock.sendto(self.data, self._addr)
        except (BlockingIOError, InterruptedError):
            return
        except OSError as exc:
            self.abort(exc)
        except Exception as exc:
            self.abort(exc)
        else:
            self.close()
            self._future.set_result(True)

    def abort(self, exc):
        self.close()
        self._future.set_exception(exc)

    def close(self):
        self._loop.remove_writer(self._sock.fileno())
        self._sock.close()

比简单的例子看起来像:

@asyncio.coroutine
def test():
    yield from UDPClient('127.0.0.1', 1234).sendto('ok')

# or 3.5+ syntax
# async def test():
#     await UDPClient('127.0.0.1', 1234).sendto('ok')


loop = asyncio.get_event_loop()
loop.run_until_complete(test())