We can create an asyncio server to accept and manage client socket connections.
An asyncio server is not created directly, instead, we can use a factory function to configure, create, and start a socket server. We can then use the server or accept client connections forever.
In this tutorial, you will discover how to create and use asyncio servers to accept client connections.
After completing this tutorial, you will know:
- How to create a socket server in asyncio.
- How to define the behavior and manage client connections via callback functions.
- How to safely manage the life-cycle of a server, including the handling of unexpected failures.
Let’s get started.
What is an Asyncio Server
An asyncio server accepts incoming TCP client connections.
It is represented in asyncio Python programs via the asyncio.Server class.
The create_server() method returns a Server instance, which wraps the sockets (or other network objects) used to accept requests.
— PEP 3156 – Asynchronous IO Support Rebooted: the “asyncio” Module
We use an asyncio.Server in our asyncio programs when we want to accept connections from other programs.
Run loops using all CPUs, download your FREE book to learn how.
How to Create An Asyncio Server
We should not create an asyncio.Server instance directly.
Do not instantiate the Server class directly.
— Event Loop, Asyncio API Documentation.
Instead, we can use helper functions to create an asyncio server and return an asyncio.Server instance.
These functions include:
- asyncio.start_server() module function.
- asyncio.start_unix_server() module function.
These are the preferred ways to create asyncio servers via the high-level asyncio API.
Server objects are created by loop.create_server(), loop.create_unix_server(), start_server(), and start_unix_server() functions.
— Event Loop, Asyncio API Documentation.
Two additional ways to create an asyncio server are via methods directly on the asyncio event loop, they are:
- loop.create_server() method on the event loop.
- loop.create_unix_server() method on the event loop.
These approaches to creating a server are not recommended for asyncio applications as they are part of the low-level asyncio API and therefore are intended for library developers.
Nevertheless, these methods are helpful to know, as their documentation better describes the arguments for the asyncio.start_server() and asyncio.start_unix_server() module functions.
The most common general way to create an asyncio server is via the asyncio.start_server() module function.
The server requires that a handler coroutine is specified that will handle incoming client connections.
The handler must take two arguments, a asyncio.StreamReader for reading data from the client and an asyncio.StreamWriter for writing data to the client.
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.
— Streams, Asyncio API Documentation.
A handler coroutine may look as follows:
1 2 3 |
# handler for client connections async def handler(reader, writer): pass |
The asyncio.start_server() function takes the name of the handler as an argument, and is typically configured with a default address and port number.
For example:
1 2 3 |
... # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888) |
By default, the server will begin listening and accepting client connections immediately. This can be disabled by setting the “start_serving” argument to False.
The example below creates a server and reports its details.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# SuperFastPython.com # example of creating a server import asyncio # handler for client connections async def handler(reader, writer): pass # main coroutine async def main(): # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888) # report the details of the server print(server) # start the asyncio event loop asyncio.run(main()) |
Running the program we can see that an asyncio server was created with the specified port number.
1 |
<Server sockets=(<asyncio.TransportSocket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 8888)>,)> |
How to Start Serving From An Asyncio Server
An asyncio.Server will begin accepting client connections by default as soon as it is created.
For example:
1 2 3 |
... # create an asyncio server server = await asyncio.start_server(handler, port=8888) |
Alternatively, we can create an asyncio.Server that does not start serving by setting the “start_serving” argument to False, then manually start serving when we are ready.
This can be achieved via the start_serving() method.
For example:
1 2 3 4 5 |
... # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888, start_serving=False) # start serving await server.start_serving() |
We need a way to ensure that the coroutine remains running so that we can continue to accept client connections for the duration of our program.
This can be achieved via the serve_forever() coroutine.
Start accepting connections until the coroutine is cancelled. Cancellation of serve_forever task causes the server to be closed. This method can be called if the server is already accepting connections. Only one serve_forever task can exist per one Server object.
— Streams, Asyncio API Documentation.
This will allow the server to begin accepting client connections, if not already configured to do so, and will never return, allowing the program to accept client connections for as long as the program runs.
For example:
1 2 3 4 5 |
... # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888) # accept client connections forever await server.serve_forever() |
The example below demonstrates this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# SuperFastPython.com # example of creating a server and serving forever import asyncio # handler for client connections async def handler(reader, writer): pass # main coroutine async def main(): # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888) # report the details of the server print(server) # accept client connections forever (kill via control-c) await server.serve_forever() # start the asyncio event loop asyncio.run(main()) |
Running the program creates the server and reports its details.
The program then begins serving and waiting for client connections forever.
The program must be stopped manually, such as via the Control-C key combination.
1 |
<Server sockets=(<asyncio.TransportSocket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 8888)>,)> |
Free Python Asyncio Course
Download your FREE Asyncio PDF cheat sheet and get BONUS access to my free 7-day crash course on the Asyncio API.
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 Check if an Asyncio Server is Serving
We can check if an asyncio.Server is currently serving or not via the is_serving() method.
For example:
1 2 3 |
... # check if it is serving serving = server.is_serving() |
An asyncio.Server may not be serving for a number of reasons, such as:
- It was configured to not server when it was created.
- It has been closed.
- It failed with an error and was closed.
- The serve_forever() task was canceled and was closed.
We can explore an example of this.
The example below creates an asyncio server that is not serving, checks its status, starts serving, then checks its status again.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# SuperFastPython.com # example of creating a server and checking if it is serving import asyncio # handler for client connections async def handler(reader, writer): pass # main coroutine async def main(): # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888, start_serving=False) # report the details of the server print(server) # check if it is serving print(f'Serving: {server.is_serving()}') # start serving await server.start_serving() # check if it is serving print(f'Serving: {server.is_serving()}') # start the asyncio event loop asyncio.run(main()) |
Running the example first creates the server and reports its details.
The status is checked and we can see that it is not serving, as we configured.
The server is then configured to start serving and the status is checked again, confirming that it is indeed now serving.
1 2 3 |
<Server sockets=(<asyncio.TransportSocket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 8888)>,)> Serving: False Serving: True |
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
How to Access a Client Connections in An Asyncio Server
A single asyncio.Server may manage many client connections.
There could be hundreds or even thousands of clients connected to a single server.
The asyncio.Server instance provides access to the client socket connections if needed.
This is provided via the “sockets” attribute that provides a list of asyncio.trsock.TransportSocket instances.
For example:
1 2 3 |
... # access list of all client sockets sockets = server.sockets |
This might be helpful if we need to report the number of currently connected clients.
For example:
1 2 3 |
... # report current client connections print(f'There are {len(server.sockets)} clients connected') |
How to Close an Asyncio Server
Once we are finished with the asyncio.Server in our program, we can close it.
This can be achieved by calling the close() method manually.
For example:
1 2 3 |
... # close the server server.close() |
This will mean that the server is no longer accepting client connections, although it may still have open client connections.
We can then call the wait_closed() to suspend the caller until the server is closed completely. This means that all client connections are now closed.
The idiom for manually closing the asyncio.Server may look as follows:
1 2 3 4 5 |
... # request that the server close server.close() # wait for the server to complete closing await server.wait_closed() |
The example below creates a server and starts serving, waits a moment, then closes the server again.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# SuperFastPython.com # example of creating a server and closing it again import asyncio # handler for client connections async def handler(reader, writer): pass # main coroutine async def main(): # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888) # report the details of the server print(server) # wait a moment await asyncio.sleep(2) # close the server server.close() # wait for the server to close await server.wait_closed() # report the details of the server print(server) # start the asyncio event loop asyncio.run(main()) |
Running the program first starts a server that accepts client connections.
The program then suspends for two seconds.
Finally, the server is requested to close, then suspends and waits for all client connections to close. There are no client connections so it closes quickly.
Reporting the final status of the server, we can see that it is no longer listening for incoming connections.
1 2 |
<Server sockets=(<asyncio.TransportSocket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 8888)>,)> <Server sockets=()> |
We can also close the server automatically.
This can be achieved by the context manager interface implemented by the asyncio.Server class.
Server objects are asynchronous context managers. When used in an async with statement, it’s guaranteed that the Server object is closed and not accepting new connections when the async with statement is completed
— Event Loop, Asyncio API Documentation.
For example:
1 2 3 4 5 |
... # use the server async with server: # ... # close automatically |
This is helpful to ensure the server is closed if some unexpected error occurs within the body of the “async with” block.
We can demonstrate this with a worked example, listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# SuperFastPython.com # example of creating a server and closing it via context manager import asyncio # handler for client connections async def handler(reader, writer): pass # main coroutine async def main(): # create an asyncio server server = await asyncio.start_server(handler, '127.0.0.1', port=8888) # report the details of the server print(server) # start using the server async with server: # wait a moment await asyncio.sleep(2) # server is closed automatically # report the details of the server print(server) # check if it is serving print(f'Serving: {server.is_serving()}') # start the asyncio event loop asyncio.run(main()) |
Running the example first creates the server that starts serving.
It is then used within the context manager, blocking for two seconds.
The context manager is exited and the server is closed and is no longer serving.
1 2 3 |
<Server sockets=(<asyncio.TransportSocket fd=6, family=2, type=1, proto=6, laddr=('127.0.0.1', 8888)>,)> <Server sockets=()> Serving: False |
We can use the context manager interface with the server_forever() method to ensure that the server is closed correctly if there is an error serving or if the server_forver() task is canceled.
For example:
1 2 3 4 5 6 |
... # use the server async with server: # accept client connections forever (kill via control-c) await server.serve_forever() # close automatically |
Further Reading
This section provides additional resources that you may find helpful.
Python Asyncio Books
- Python Asyncio Mastery, Jason Brownlee (my book!)
- Python Asyncio Jump-Start, Jason Brownlee.
- Python Asyncio Interview Questions, Jason Brownlee.
- 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.
- asyncio Recipes, Mohamed Mustapha Tahrioui, 2019.
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 create and use asyncio servers in Python.
Did I make a mistake? See a typo?
I’m a simple humble human. Correct me, please!
Do you have any additional tips?
I’d love to hear about them!
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Jakob Rosen on Unsplash
Do you have any questions?