You can use non-blocking TCP socket connections using the asyncio streams API.
In this tutorial, you will discover how to use asyncio streams in Python.
Let’s get started.
Table of Contents
Asyncio Streams
Asyncio provides non-blocking I/O socket programming.
This is provided via streams.
Streams are high-level async/await-ready primitives to work with network connections. Streams allow sending and receiving data without using callbacks or low-level protocols and transports.
— Asyncio Streams
Sockets can be opened that provide access to a stream writer and a stream writer.
Data can then be written and read from the stream using coroutines, suspending when appropriate.
Once finished, the socket can be closed.
The asyncio streams capability is low-level meaning that any protocols required must be implemented manually.
This might include common web protocols, such as:
- HTTP or HTTPS for interacting with web servers
- SMTP for interacting with email servers
- FTP for interacting with file servers.
The streams can also be used to create a server to handle requests using a standard protocol, or to develop your own application-specific protocol.
Now that we know what asyncio streams are, let’s look at how to use them.
Run your loops using all CPUs, download my FREE book to learn how.
How to Open a Connection
An asyncio TCP client socket connection can be opened using the asyncio.open_connection() function.
Establish a network connection and return a pair of (reader, writer) objects. The returned reader and writer objects are instances of StreamReader and StreamWriter classes.
— Asyncio Streams
This is a coroutine that must be awaited and will return once the socket connection is open.
The function returns a StreamReader and StreamWriter object for interacting with the socket.
For example:
1 2 3 |
... # open a connection reader, writer = await asyncio.open_connection(...) |
The asyncio.open_connection() function takes many arguments in order to configure the socket connection.
The two required arguments are the host and the port.
The host is a string that specifies the server to connect to, such as a domain name or an IP address.
The port is the socket port number, such as 80 for HTTP servers, 443 for HTTPS servers, 23 for SMTP and so on.
For example:
1 2 3 |
... # open a connection to an http server reader, writer = await asyncio.open_connection('www.google.com', 80) |
Encrypted socket connections are supported over the SSL protocol.
The most common example might be HTTPS which is replacing HTTP.
This can be achieved by setting the “ssl” argument to True.
For example:
1 2 3 |
... # open a connection to an https server reader, writer = await asyncio.open_connection('www.google.com', 443, ssl=True) |
Confused by the asyncio module API?
Download my FREE PDF cheat sheet
How to Start a Server
An asyncio TCP server socket can be opened using the asyncio.start_server() function.
Create a TCP server (socket type SOCK_STREAM) listening on port of the host address.
— Asyncio Event Loop
This is a coroutine that must be awaited.
The function returns an asyncio.Server object that represents the running server.
For example:
1 2 3 |
... # start a tcp server server = await asyncio.start_server(...) |
The three required arguments are the callback function, the host, and the port.
The callback function is a custom function specified by name that will be called each time a client connects to the server.
The client_connected_cb callback is called whenever a new client connection is established. It receives a (reader, writer) pair as two arguments, instances of the StreamReader and StreamWriter classes.
— Asyncio Streams
The host is the domain name or IP address that clients will specify to connect. The port is the socket port number on which to receive connections, such as 21 for FTP or 80 for HTTP.
For example:
1 2 3 4 5 6 7 |
# handle connections async def handler(reader, writer): # ... ... # start a server to receive http connections server = await asyncio.start_server(handler, '127.0.0.1', 80) |
Free Python Asyncio Course
Download my asyncio API cheat sheet and as a bonus you will get FREE access to my 7-day email course on asyncio.
Discover how to use the Python asyncio module including how to define, create, and run new coroutines and how to use non-blocking I/O.
How to Write Data with the StreamWriter
We can write data to the socket using an asyncio.StreamWriter.
Represents a writer object that provides APIs to write data to the IO stream.
— Asyncio Streams
Data is written as bytes.
Byte data can be written to the socket using the write() method.
The method attempts to write the data to the underlying socket immediately. If that fails, the data is queued in an internal write buffer until it can be sent.
— Asyncio Streams
For example:
1 2 3 |
... # write byte data writer.write(byte_data) |
Alternatively, multiple “lines” of byte data organized into a list or iterable can be written using the writelines() method.
For example:
1 2 3 |
... # write lines of byte data writer.writelines(byte_lines) |
Neither method for writing data blocks or suspends the calling coroutine.
After writing byte data it is a good idea to drain the socket via the drain() method.
Wait until it is appropriate to resume writing to the stream.
— Asyncio Streams
This is a coroutine and will suspend the caller until the bytes have been transmitted and the socket is ready.
For example:
1 2 3 4 5 |
... # write byte data writer.write(byte_data) # wait for data to be transmitted await writer.drain() |
Overwheled by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
How to Read Data with the StreamReader
We can read data from the socket using an asyncio.StreamReader.
Represents a reader object that provides APIs to read data from the IO stream.
— Asyncio Streams
Data is read in byte format, therefore strings may need to be encoded before being used.
All read methods are coroutines that must be awaited.
An arbitrary number of bytes can be read via the read() method, which will read until the end of file (EOF).
1 2 3 |
... # read byte data byte_data = await reader.read() |
Additionally, the number of bytes to read can be specified via the “n” argument.
Read up to n bytes. If n is not provided, or set to -1, read until EOF and return all read bytes.
— Asyncio Streams
This may be helpful if you know the number of bytes expected from the next response.
For example:
1 2 3 |
... # read byte data byte_data = await reader.read(n=100) |
A single line of data can be read using the readline() method.
This will return bytes until a new line character ‘\n’ is encountered, or EOF.
Read one line, where “line” is a sequence of bytes ending with \n. If EOF is received and \n was not found, the method returns partially read data. If EOF is received and the internal buffer is empty, return an empty bytes object.
— Asyncio Streams
This is helpful when reading standard protocols that operate with lines of text.
1 2 3 |
... # read a line data byte_line = await reader.readline() |
Additionally, there is a readexactly() method to read an exact number of bytes otherwise raise an exception, and a readuntil() that will read bytes until a specified character in byte form is read.
How to Close Connection
The socket can be closed via the asyncio.StreamWriter.
The close() method can be called which will close the socket.
The method closes the stream and the underlying socket.
— Asyncio Streams
This method does not block.
For example:
1 2 3 |
... # close the socket writer.close() |
Although the close() method does not block, we can wait for the socket to close completely before continuing on.
This can be achieved via the wait_closed() method.
Wait until the stream is closed. Should be called after close() to wait until the underlying connection is closed.
— Asyncio Streams
This is a coroutine that can be awaited.
For example:
1 2 3 4 5 |
... # close the socket writer.close() # wait for the socket to close await writer.wait_closed() |
We can check if the socket has been closed or is in the process of being closed via the is_closing() method.
For example:
1 2 3 4 |
... # check if the socket is closed or closing if writer.is_closing(): # ... |
Further Reading
This section provides additional resources that you may find helpful.
Books
- Python Asyncio Jump-Start, Jason Brownlee, 2022 (my book).
- Python Asyncio Interview Questions
- Asyncio Module API Cheat Sheet
I also recommend the following books:
- Python Concurrency with asyncio, Matthew Fowler, 2022.
- Using Asyncio in Python, Caleb Hattingh, 2020.
Guides
APIs
- asyncio — Asynchronous I/O
- Asyncio Coroutines and Tasks
- Asyncio Streams
- Asyncio Subprocesses
- Asyncio Queues
- Asyncio Synchronization Primitives
References
Takeaways
You now know how to use asyncio streams in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Leave a Reply