import socket
import socket
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(128)
print("Server listening on: {}".format(server.getsockname()))
client, addr = server.accept()
print("Client connected: {}".format(addr))
while True:
data = client.recv(4096)
if not data:
print("Client has disconnected")
break
client.send(data)
server.close()
I/O is hard
• Sync i/o is bad, async i/o is Good (TM)
• Different paradigms in Unix vs
Windows
• “are you ready?” vs “call me later”
• Event loops are The Way To Go
• See the c10k problem
import twisted
• Uses select, poll, kqueue, epoll from
the select module
• IOCP on Windows
• Integration with other event loops: Qt
• Factory/Protocol/Transport
abstractions
• Deferred
import tornado
• Uses select, poll, kqueue, epoll from
the select module
• select() on Windows :-(
• Mainly oriented to web development
• Synchronous looking API with
coroutines
import gevent
• Uses libevent in version 0.x and libev in
1.x
• select() on Windows :-(
• Syncronous API using greenlet
import asyncore
• raise RuntimeError(“NOT GOOD ENOUGH”)
• “asyncore: included batteries don’t fit”
bit.ly/182HcHT
import asyncio
• Reference implementation for
PEP-3156
• Basic components for doing async i/o
• Works (officially) on Python >= 3.3 [*]
Goals
• Modern implementation of async i/o for
Python
• Use yield from (PEP-380)
• But don’t force it
• Don’t use anything that requires
Python > 3.3
• Interoperability with other frameworks
Goals
• Unix and Windows support
• IPv4 and IPv6
• TCP, UDP and pipes
• Basic SSL (secure by default)
• Subprocesses
Non goals
• Perfection
• Replacing current frameworks
• Protocol implementations
• Replace httplib, smtplib, ...
• Make it work on Python < 3.3
Coroutines, Futures & Tasks
• Coroutines
• a generator function, can receive values
• decorated with @coroutine
• Future
• promise of a result or an error
• Task
• Future which runs a coroutine
Coroutines & yield from
import asyncio
import socket
loop = asyncio.get_event_loop()
@asyncio.coroutine
def handle_client(client, addr):
print("Client connected: {}".format(addr))
while True:
data = yield from loop.sock_recv(client, 4096)
if not data:
print("Client has disconnected")
break
client.send(data)
@asyncio.coroutine
def accept_connections(server_socket):
while True:
client, addr = yield from loop.sock_accept(server_socket)
asyncio.async(handle_client(client, addr))
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 1234))
server.listen(128)
server.setblocking(False)
print("Server listening on: {}".format(server.getsockname()))
loop.run_until_complete(accept_connections(server))
Coroutines & yield from
• Imagine the yield from is not there
• Imagine the code is executed
sequentially
• Not exactly the formal definition of yield
from (from PEP-380)
Futures
• Similar to Futures from PEP-3148
•
concurrent.futures.Future
• API (almost) identical:
•
f.set_result(); r = f.result()
•
f.set_exception(e); e = f.exception()
•
f.done()
•
f.add_done_callback(x); f.remove_done_callback(x)
•
f.cancel(); f.cancelled()
Futures + Coroutines
• yield from works with Futures!
• f = Future()
•
Someone will set the result or exception
• r = yield from f
•
Waits until done and returns f.result()
• Usually returned by functions
Tasks
• Unicorns covered in fairy dust
• It’s a coroutine wrapped in a Future
• WAT
• Inherits from Future
• Works with yield from
• r = yield from Task(coro(...))
Tasks vs coroutines
• A coroutine doesn’t “advance” without
a scheduling mechanism
• Tasks can advance in their own
• The event loop is the scheduler!
• Magic!
Example
import asyncio
loop = asyncio.get_event_loop()
clients = {} # task -> (reader, writer)
def accept_client(client_reader, client_writer):
task = asyncio.Task(handle_client(client_reader, client_writer))
clients[task] = (client_reader, client_writer)
def client_done(task):
del clients[task]
task.add_done_callback(client_done)
@asyncio.coroutine
def handle_client(client_reader, client_writer):
while True:
data = (yield from client_reader.readline())
client_writer.write(data)
f = asyncio.start_server(accept_client, '127.0.0.1', 12345)
server = loop.run_until_complete(f)
loop.run_forever()
Transports & Protocols
• Transport: represents a connection
(socket, pipe, ...)
• Protocol: represents an application
(HTTP server, IRC client, ...)
• They always go together
• API is based on function calls and
callbacks
Clients & servers
• loop.create_connection(...)
• creates a Transport and a Protocol
• loop.create_server(...)
• creates a Transport and a Protocol for each
accepted connection
• returns a Server object
Clients & servers
• loop.open_connection(...)
• wrapper around create_connection, returns
(stream_reader, stream_writer)
• loop.start_server(...)
• wrapper around create_server, calls a
callback with (stream_reader, stream_writer)
for each accepted conection
Transport -> Protocol
• connection_made(transport)
• data_received(data)
• eof_received()
• connection_lost(exc)
• UDP, pipes and subprocesses are
slightly different
More Transport methods
• can_write_eof()
• abort()
• get_extra_info(key)
• ‘socket’
• ‘sockname’, ‘peername’
• ‘sslcontext’
• ...
Enter Trollius!
• Backport of asyncio for Python >= 2.6
• By Victor Stinner
• Slightly different syntax
•
•
yield instead of yield from
raise Return(x) instead of return x on
generators
• pip install trollius
That was all?
• Yes and No
• Go read PEP-3156
• Implement a simple protocol (IRC
client)
• Checkout the third party libraries
• Use asyncio in your next project