0

I wanted to try out the new asyncio module from Python 3.5.1. Here's my test code:

import asyncio

class EchoClientProtocol:
    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 datagram_received(self, data, addr):
        print("Received:", data.decode())

        print("Close the socket")
        #self.transport.close()

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

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

loop = asyncio.get_event_loop()
message = "Hello World!"
connect = loop.create_datagram_endpoint(
    lambda: EchoClientProtocol(message, loop),
    remote_addr=('127.0.0.1', 9999))
transport, protocol = loop.run_until_complete(connect)
loop.run_forever()
transport.close()
loop.close()

When I run this, the interpreter gives me:

Traceback (most recent call last):
  File "C:\Users\oxygen\Documents\GitProjects\tests\python\udp\client.py", line
35, in <module>
    loop.run_forever()
  File "C:\Python35-32\lib\asyncio\base_events.py", line 295, in run_forever
    self._run_once()
  File "C:\Python35-32\lib\asyncio\base_events.py", line 1218, in _run_once
    event_list = self._selector.select(timeout)
  File "C:\Python35-32\lib\selectors.py", line 314, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
  File "C:\Python35-32\lib\selectors.py", line 305, in _select
    r, w, x = select.select(r, w, w, timeout)
OSError: [WinError 10038] an operation was attempted on something that is not a socket

I think this is caused by the sequence of self.transport.sendto(self.message.encode()) and self.transport.close(). If I understand correctly, the sendto method is asynchronous and it actually gets invoked after I close the socket by calling the close method. Is there any way to solve this?

Hexdigit _
  • 187
  • 1
  • 2
  • 10

2 Answers2

1

The DatagramTransport (actually _SelectorDatagramTransport) schedules two actions on connection, the first one is connection_made from given protocol, the second is _read_ready (recvfrom) - in that order (https://github.com/python/asyncio/blob/master/asyncio/selector_events.py#L996).

Since you close transport on connection_made, the following action (_read_ready) fails. Remove self.transport.close() from there.

You might find interesting the asyncio udp examples.

kwarunek
  • 12,141
  • 4
  • 43
  • 48
  • Hey, thanks for your comment! My code is actually pretty much an edited version of the example you linked. Could you please provide some more help, on how to open the connection without the reading action? I would really like to just establish the connection, send data and close it. I was trying to find some help in the docs, and I found [this method](https://docs.python.org/3/library/asyncio-stream.html#asyncio.open_connection), would that help me? – Hexdigit _ Jun 30 '16 at 14:49
  • UDP is connectionless protocol (https://en.wikipedia.org/wiki/User_Datagram_Protocol) – kwarunek Jul 01 '16 at 21:10
0

This one resolves on_con_lost future after send:

import asyncio


class EchoClientProtocol:
    def __init__(self, message, on_con_lost):
        self.message = message
        self.on_con_lost = on_con_lost

    def connection_made(self, transport):
        print("Send:", self.message)
        self.transport = transport
        self.transport.sendto(self.message.encode())
        self.on_con_lost.set_result(True)


async def main() -> None:
    loop = asyncio.get_running_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: EchoClientProtocol(
            "Hello World!",
            on_con_lost=loop.create_future(),
        ),
        remote_addr=("127.0.0.1", 9999),
    )

    try:
        await asyncio.sleep(0.5)
        await protocol.on_con_lost
    finally:
        transport.close()


if __name__ == "__main__":
    asyncio.run(main())
frost-nzcr4
  • 1,540
  • 11
  • 16