asyncio runner

Streams

Streams are a high-level way to work with network connections in Python's asyncio module. They allow you to send and receive data without having to use low-level protocols or callbacks.

How to use streams

To use streams, you first need to create a reader and a writer. You can do this by calling the asyncio.open_connection() function. This function takes two arguments: the host and port of the server you want to connect to.

Once you have a reader and writer, you can use them to send and receive data. To send data, you call the writer.write() function. To receive data, you call the reader.read() function.

Example

Here is an example of a simple TCP echo client written using asyncio streams:

import asyncio

async def tcp_echo_client(message):
    reader, writer = await asyncio.open_connection(
        '127.0.0.1', 8888)

    print(f'Send: {message!r}')
    writer.write(message.encode())
    await writer.drain()

    data = await reader.read(100)
    print(f'Received: {data.decode()!r}')

    print('Close the connection')
    writer.close()
    await writer.wait_closed()

asyncio.run(tcp_echo_client('Hello World!'))

Real-world applications

Streams can be used in a variety of real-world applications, including:

  • Writing network servers and clients

  • Implementing network protocols

  • Sending and receiving data over a network

Conclusion

Streams are a powerful tool for working with network connections in Python's asyncio module. They provide a high-level interface that makes it easy to send and receive data without having to use low-level protocols or callbacks.


Stream Functions in asyncio

Streams are a fundamental concept in asynchronous programming, allowing data to be sent and received continuously over a network or between processes. asyncio provides several top-level functions for creating and working with streams:

1. asyncio.open_connection()

Creates a new network connection to a specified host and port.

Example:

async def connect_to_server():
    reader, writer = await asyncio.open_connection("example.com", 8080)
    # Use writer to send data to the server
    # Use reader to receive data from the server

2. asyncio.run_coroutine_threadsafe()

Runs an asynchronous coroutine in a separate thread, possibly with a different event loop.

Example:

async def threaded_coroutine():
    return "Hello from a thread!"

message = await asyncio.run_coroutine_threadsafe(threaded_coroutine(), asyncio.get_event_loop())

3. asyncio.get_running_loop()

Returns the current event loop.

Example:

loop = asyncio.get_running_loop()
loop.run_until_complete(my_async_function())

4. asyncio.create_task()

Creates a new asyncio task, which is a coroutine that runs concurrently with the main event loop.

Example:

async def my_coroutine():
    return "Hello from a task!"

task = asyncio.create_task(my_coroutine())
result = await task

5. asyncio.gather()

Waits for all the given coroutines to complete and returns a list of their results.

Example:

tasks = [
    asyncio.create_task(coroutine1()),
    asyncio.create_task(coroutine2()),
    asyncio.create_task(coroutine3()),
]
results = await asyncio.gather(*tasks)

6. asyncio.wait_for()

Waits for the given coroutine to complete and returns its result. Raises asyncio.TimeoutError if the coroutine does not complete within the specified timeout.

Example:

try:
    result = await asyncio.wait_for(my_coroutine(), timeout=1.0)
except asyncio.TimeoutError:
    print("Coroutine timed out.")

7. asyncio.sleep()

Suspends the execution of the current coroutine for the specified number of seconds.

Example:

await asyncio.sleep(1.0)

Potential Applications:

Stream functions are widely used in many real-world applications, including:

  • Web servers: Handle HTTP requests and responses using stream functions to handle data transfer.

  • Data processing pipelines: Create pipelines of coroutines that process data asynchronously, using stream functions for data transfer between stages.

  • Database connections: Establish and maintain connections to databases using stream functions for data transfer.

  • Real-time communication: Create WebSocket servers and clients for real-time data exchange using stream functions.


What is asyncio-runner?

asyncio-runner is a Python module that makes it easy to write asynchronous network applications. It provides a simple API for creating and managing network connections, and it can be used with any asyncio-compatible event loop.

open_connection() Function

The open_connection() function in asyncio-runner establishes a network connection and returns a pair of objects: a reader and a writer. The reader object is used to read data from the connection, and the writer object is used to write data to the connection.

Arguments:

  • host: The hostname or IP address of the server to connect to.

  • port: The port number of the server to connect to.

  • limit: The buffer size limit for the reader object.

  • ssl: A boolean value indicating whether to use SSL encryption.

  • family: The address family to use.

  • proto: The protocol to use.

  • flags: The socket flags to use.

  • sock: A socket object to use.

  • local_addr: The local address to bind to.

  • server_hostname: The server hostname to use for SSL encryption.

  • ssl_handshake_timeout: The timeout for the SSL handshake.

  • ssl_shutdown_timeout: The timeout for the SSL shutdown.

  • happy_eyeballs_delay: The delay between IPv4 and IPv6 connections.

  • interleave: A boolean value indicating whether to interleave IPv4 and IPv6 connections.

Return Value:

A pair of objects: a reader and a writer.

Example:

import asyncio

async def main():
    reader, writer = await asyncio.open_connection('example.com', 80)
    writer.write(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
    data = await reader.read(1024)
    print(data.decode())

asyncio.run(main())

Real-World Applications:

asyncio-runner can be used to write a wide variety of asynchronous network applications, such as:

  • Web servers

  • Web clients

  • Chat servers

  • File servers

  • Database servers

  • Proxy servers

Potential Applications:

Here are some potential applications for asyncio-runner in the real world:

  • A web server that serves static files and dynamic content.

  • A web client that can make HTTP requests and parse responses.

  • A chat server that allows multiple clients to chat with each other.

  • A file server that allows clients to upload and download files.

  • A database server that allows clients to store and retrieve data.

  • A proxy server that forwards requests to other servers.


Simplified Explanation of start_server Function

What it Does: start_server creates a socket server that listens for incoming client connections.

Arguments:

  • client_connected_cb: A callback function that is called whenever a new client connects. This function should receive two arguments: a reader and a writer object.

  • host: The IP address or hostname to listen on (defaults to None, which listens on all available interfaces).

  • port: The port number to listen on (defaults to None, which will choose a random available port).

  • limit: The buffer size limit for incoming data (defaults to 64 KiB).

  • family: The socket family to use (defaults to socket.AF_UNSPEC, which will use IPv4 or IPv6 as appropriate).

  • flags: Flags to pass to the socket creation function (defaults to socket.AI_PASSIVE, which allows the socket to accept incoming connections).

  • sock: An existing socket to use (optional).

  • backlog: The maximum number of pending connections to allow (defaults to 100).

  • ssl: An SSL context to use for secure connections (optional).

  • reuse_address: Whether to allow multiple servers to bind to the same address (defaults to None, which is platform-dependent).

  • reuse_port: Whether to allow multiple servers to bind to the same port (defaults to None, which is platform-dependent).

  • ssl_handshake_timeout: The timeout for SSL handshakes (defaults to None, which uses the default system timeout).

  • ssl_shutdown_timeout: The timeout for SSL shutdown (defaults to None, which uses the default system timeout).

  • start_serving: Whether to start serving immediately (defaults to True).

How to Use:

To use start_server, you need to define a client_connected_cb callback function that receives a reader and a writer object. Then, you can call start_server with the required arguments:

import asyncio

async def client_connected_cb(reader, writer):
    # Read data from the client.
    data = await reader.read(1024)
    # Send data to the client.
    writer.write(data)
    # Close the connection.
    writer.close()

# Create the server.
server = asyncio.start_server(client_connected_cb, host='localhost', port=8080)

# Start the server.
asyncio.run(server)

Real-World Examples:

  • Web server: A web server listens for incoming HTTP requests and responds with HTML pages.

  • Chat server: A chat server allows multiple users to connect and chat with each other.

  • File transfer server: A file transfer server allows users to upload and download files.

Note: The code snippets and explanations may vary slightly depending on the version of Python and asyncio you are using.


Unix Sockets

Unix sockets are a type of communication channel that allows processes to communicate with each other on the same computer. They are similar to TCP sockets, but they are only available on Unix-like operating systems (such as Linux, macOS, and Solaris).

Creating a Unix Socket

To create a Unix socket, you can use the socket function, passing in the AF_UNIX constant for the family parameter. You can also specify the SOCK_STREAM constant for the type parameter if you want to create a stream socket, or the SOCK_DGRAM constant for the type parameter if you want to create a datagram socket.

import socket

# Create a Unix stream socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

# Bind the socket to a specific path
sock.bind('/tmp/my_socket')

Connecting to a Unix Socket

To connect to a Unix socket, you can use the connect method of the socket object. You need to pass in the path to the socket that you want to connect to.

# Connect to the Unix socket
sock.connect('/tmp/my_socket')

Sending and Receiving Data

Once you have connected to a Unix socket, you can send and receive data using the send and recv methods of the socket object.

# Send data to the socket
sock.send(b'Hello world')

# Receive data from the socket
data = sock.recv(1024)

Closing a Unix Socket

When you are finished using a Unix socket, you should close it using the close method of the socket object.

# Close the socket
sock.close()

Real-World Applications

Unix sockets are used in a variety of real-world applications, including:

  • Inter-process communication: Unix sockets can be used to allow different processes on the same computer to communicate with each other.

  • Remote procedure calls: Unix sockets can be used to implement remote procedure calls (RPCs), which allow clients to invoke methods on remote servers.

  • Database access: Unix sockets can be used to connect to database servers and execute queries.

  • File transfer: Unix sockets can be used to transfer files between different computers.

  • Web servers: Unix sockets can be used to implement web servers that listen for HTTP requests on a specific path.


Simplified Explanation:

What is open_unix_connection?

It's a function that lets you create a connection to a Unix domain socket.

Unix Domain Socket?

It's like a pipe that connects two programs running on the same computer.

How do you use open_unix_connection?

You call it and pass in the path to the socket file. It returns two things:

  • A reader object that you can use to read data from the socket

  • A writer object that you can use to write data to the socket

Example:

import asyncio

async def main():
    reader, writer = await asyncio.open_unix_connection("/tmp/my_socket")

    # Write some data to the socket
    writer.write(b"Hello, world!")

    # Read some data from the socket
    data = await reader.read(100)

    # Close the connection
    writer.close()

if __name__ == "__main__":
    asyncio.run(main())

Potential Applications:

Unix domain sockets are often used for inter-process communication (IPC) on Unix systems. For example, they can be used:

  • To connect to a database server

  • To share data between multiple processes

  • To control a remote program


Simplified Explanation of asyncio.start_unix_server()

Purpose:

This function creates a server that listens for incoming connections on a Unix socket. When a client connects, it calls a specified callback function to handle the connection.

Parameters:

  • client_connected_cb: The callback function that will be called when a client connects.

  • path: The path to the Unix socket file.

  • limit: The maximum number of simultaneous clients that the server can handle.

  • sock: An existing Unix socket object to use. If provided, the function takes ownership of the socket.

  • backlog: The number of incoming connections that can be queued before the server starts refusing connections.

  • ssl: An SSLContext object to use for secure connections.

  • ssl_handshake_timeout: The timeout for SSL handshakes.

  • ssl_shutdown_timeout: The timeout for SSL shutdowns.

  • start_serving: Whether to start serving immediately after creating the server.

How it Works:

  1. The function creates a Unix socket file at the specified path or uses an existing socket if provided.

  2. It configures the socket to listen for incoming connections and sets the backlog limit.

  3. When a client connects, the server creates a new socket object for the connection.

  4. The server then calls the specified callback function with the new socket object as an argument.

Example:

import asyncio

async def handle_client(reader, writer):
    data = await reader.read(1024)
    writer.write(data.upper())
    writer.close()

# Create a Unix socket server
server = await asyncio.start_unix_server(handle_client, "/tmp/my_socket")

# Start serving
asyncio.run(server.serve_forever())

Real-World Applications:

  • Web servers: Serving web pages to clients over a secure connection.

  • File sharing: Allowing clients to upload and download files.

  • Remote control: Controlling a computer or device remotely via a Unix socket connection.


StreamReader

Simplified Explanation:

The StreamReader class in asyncio represents a way to read data from a stream. It's like a pipe that allows you to receive information from a source, such as a network connection.

How It Works:

When you establish a network connection, asyncio creates a StreamReader object for you. This object handles the receiving of data from the other end of the connection. The StreamReader provides methods like read() and readline() to retrieve data in chunks or as lines of text.

Asynchronous Iteration:

StreamReader also supports asynchronous iteration, meaning you can use it in a async for loop. This allows you to iterate over the data received from the stream in an efficient, non-blocking way.

Not Recommended to Instantiate Directly:

It's generally not recommended to create StreamReader objects directly. Instead, use the open_connection() or start_server() functions provided by asyncio. These functions will handle the creation of the StreamReader object for you.

Real-World Example:

Imagine you're developing a web server. When a client connects to the server, the server creates a StreamReader object to receive the HTTP request from the client. The StreamReader allows the server to read the request data and process it.

Complete Code Implementation:

import asyncio

async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    data = await reader.read(1024)  # Read up to 1024 bytes of data
    writer.write(data)  # Echo the data back to the client

async def start_server():
    server = asyncio.start_server(handle_client, 'localhost', 8888)
    async with server:
        await server.serve_forever()

if __name__ == '__main__':
    asyncio.run(start_server())

Potential Applications:

  • Web servers: Receiving HTTP requests and sending responses.

  • Chat applications: Receiving messages from connected clients.

  • Data streaming: Receiving data from a source in real-time.


feed_eof()

This method tells the stream that there is no more data coming. It's like waving goodbye to the stream and saying, "That's all, folks!"

read(n=-1)

This method reads up to n bytes from the stream. If you don't specify n or set it to -1, it will read all the remaining data in the stream and return it as a bytes object. If the stream has ended (EOF), it will return an empty bytes object.

readline()

This method reads one line from the stream, where a line is a sequence of bytes ending with a newline character (). If the stream ends (EOF) and a newline character is not found, it will return the partially read data. If the stream ends (EOF) and the internal buffer is empty, it will return an empty bytes object.

readexactly(n)

This method reads exactly n bytes from the stream. If the stream ends (EOF) before n bytes can be read, it will raise an IncompleteReadError exception. You can use the partial attribute of the IncompleteReadError exception to get the partially read data.

readuntil(separator=b'\n')

This method reads data from the stream until a specified separator is found. Once the separator is found, the data and separator will be removed from the internal buffer and returned. The returned data will include the separator at the end.

If the amount of data read exceeds the configured stream limit, a LimitOverrunError exception will be raised. The data will be left in the internal buffer and can be read again.

If the stream ends (EOF) before the complete separator is found, an IncompleteReadError exception will be raised. The internal buffer will be reset and the partial attribute of the IncompleteReadError exception may contain a portion of the separator.

Real-world applications:

  • Reading data from a file

  • Reading data from a network socket

  • Reading data from a serial port

  • Reading data from a stream of data, such as a log file or a data feed


Method: at_eof()

Simplified Explanation:

This method checks if there's nothing left to read from the buffer. It returns True if the buffer is empty and the feed_eof() method has been called.

Detailed Explanation:

Every time you read from a buffer, data is taken out of it. Once there's no more data to read, the buffer is considered empty. The feed_eof() method is used to indicate that no more data will be written to the buffer.

Calling at_eof() lets you know if the buffer is empty and no more data can be added. It's useful when you want to perform end-of-file operations, such as closing a file or connection.

Example:

async def read_file(file_name):
    with open(file_name, "rb") as f:
        reader = asyncio.StreamReader(f)
        data = await reader.read()
        if reader.at_eof():
            print("EOF reached, all data read.")

Real-World Application:

at_eof() is commonly used in server applications. When a client disconnects, the server needs to know if there's any unfinished data in the buffer. By checking at_eof(), the server can close the connection and free up resources.


StreamWriter

A StreamWriter object represents a way to write data to a network connection or file. You can think of it as a pipe that you can send data through.

How to create a StreamWriter

You can create a StreamWriter object by using the open_connection or start_server functions. For example:

import asyncio

async def main():
    # Create a StreamWriter object to write data to a remote server
    writer = await asyncio.open_connection('example.com', 80)

    # Send some data to the server
    writer.write(b'Hello, world!')

    # Close the connection
    writer.close()

asyncio.run(main())

How to use a StreamWriter

Once you have a StreamWriter object, you can use it to write data to the underlying connection. You can do this by calling the write method. The write method takes a byte string as an argument. For example:

writer.write(b'Hello, world!')

You can also use the writelines method to write multiple lines of data. The writelines method takes a list of byte strings as an argument. For example:

writer.writelines([b'Hello, ', b'world!'])

When to use a StreamWriter

You can use a StreamWriter object whenever you need to send data over a network connection or to a file. For example, you could use a StreamWriter object to send data to a web server, to a database, or to a file on your local computer.

Real-world applications of StreamWriter

StreamWriter objects are used in a wide variety of real-world applications, including:

  • Web servers

  • Database clients

  • File transfer programs

  • Chat programs


Simplified Explanation:

What is the write() method in Python's asyncio-runner module?

The write() method allows you to send data over a network connection established using the asyncio-runner module.

How does the write() method work?

When you call the write() method, it tries to send the data you provide to the other end of the connection right away. If it can't send it immediately (for example, because the connection is busy), the data is stored in a temporary buffer.

Once the connection is ready, the write() method will automatically send the data from the buffer.

Why use the write() method along with the drain() method?

The drain() method waits until all the data in the buffer has been sent. This ensures that all your data reaches the other end of the connection.

How do you use the write() and drain() methods together?

Here's an example of how to use the write() and drain() methods together:

import asyncio
import asyncio_runner

async def main():
    # Create a network connection
    stream = await asyncio_runner.connect("example.com", 80)

    # Send data to the connection
    data = "Hello, world!"
    stream.write(data)

    # Wait until all data has been sent
    await stream.drain()

    # The data has now been sent to the other end of the connection

Potential applications in the real world:

  • Sending data to a web server

  • Sending data to a database

  • Sending data to a remote device

  • Any situation where you need to send data over a network connection


What is asyncio-runner?

asyncio-runner is a Python module that simplifies the task of creating and managing asyncio tasks. It provides a convenient way to run multiple tasks concurrently and wait for them to complete.

What is the writelines() method?

The writelines() method is used to write a list (or any iterable) of bytes to an underlying socket immediately. If the write operation fails, the data is queued in an internal write buffer until it can be sent.

Simplified Explanation:

Imagine you have a water pipe (the socket) and a bucket of water (the data). The writelines() method allows you to pour the water from the bucket into the pipe. If the pipe is clogged, the water is temporarily stored in a nearby bucket (the write buffer) until the pipe is clear.

How to use writelines() with drain():

To ensure that all the data has been written to the socket, you should use the drain() method after calling writelines(). The drain() method waits until the write buffer is empty, ensuring that all the data has been sent.

Real-World Example:

Here's an example of how you might use writelines() and drain() to send a list of messages over a socket:

import asyncio
import asyncio_runner

async def send_messages(messages, socket):
    """Sends a list of messages over a socket."""

    # Write the messages to the socket
    for message in messages:
        socket.writelines(message.encode('utf-8'))

    # Wait until all the data has been sent
    await socket.drain()

# Create a list of messages to send
messages = ['Hello', 'World', 'Goodbye']

# Create a socket
socket = socket.socket()

# Create an asyncio task to send the messages
task = asyncio_runner.create_task(send_messages(messages, socket))

# Run the task and wait for it to complete
asyncio_runner.run(task)

Potential Applications:

The writelines() method is useful in any situation where you need to send data over a socket. Some examples include:

  • Sending files over a network

  • Communicating with a remote server

  • Streaming data to a client


Method: close()

Simplified Explanation:

Imagine you have a water pipe connected to a faucet. When you want to stop the water flow, you close the faucet. Similarly, the close() method closes your data stream and the "pipe" (socket) that carries the data.

Detailed Steps:

  1. The close() method is called on a stream object.

  2. The method sends a message to the operating system to close the socket associated with the stream.

  3. The stream stops receiving or sending data.

  4. The socket is closed, freeing up system resources.

Example Code:

import asyncio

async def main():
    reader, writer = await asyncio.open_connection('example.com', 80)
    # Send some data to the server
    writer.write(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
    # Receive some data from the server
    data = await reader.read(100)
    # Close the stream and socket
    writer.close()
    await writer.wait_closed()

asyncio.run(main())

Real-World Application:

The close() method is commonly used when you no longer need to send or receive data on a stream. For example, after you receive a response from a server, you can close the stream to indicate that you're done. This helps the operating system release memory and system resources.


Method: can_write_eof()

Purpose: Checks if the underlying communication channel (transport) supports sending an end-of-file (EOF) signal to indicate the end of data transmission.

Explanation:

Imagine you're having a conversation with someone through a walkie-talkie. To end the conversation, you typically say "over" or "end" to signal that you've finished speaking. In a similar way, the can_write_eof() method checks if the transport layer of your communication connection supports sending an EOF signal to let the other side know that you're done transmitting data.

Return Value:

  • True: The transport supports sending EOF signals.

  • False: The transport does not support sending EOF signals.

Example:

import asyncio

async def check_eof_support():
    reader, writer = await asyncio.open_connection("example.com", 80)

    # Check if the transport supports writing EOF
    can_write_eof = writer.can_write_eof()

    if can_write_eof:
        print("EOF supported")
    else:
        print("EOF not supported")

asyncio.run(check_eof_support())

Applications:

  • Detecting when a remote device is no longer sending data and closing the connection accordingly.

  • Flushing buffers and ensuring that all data has been transmitted before closing a connection.


asyncio-runner module

The asyncio-runner module provides a way to run async functions in a synchronous context. This can be useful for testing async functions, or for running them in environments that do not support asyncio.

write_eof() method

The write_eof() method closes the write end of the stream after the buffered write data is flushed. This can be useful for signaling to the other end of the stream that no more data will be sent.

transport attribute

The transport attribute returns the underlying asyncio transport. This can be useful for getting access to the low-level transport API.

Complete code implementation

The following complete code implementation shows how to use the write_eof() method and the transport attribute:

import asyncio
import asyncio_runner

async def main():
    # Create a TCP server
    server = asyncio.start_server(lambda r, w: asyncio_runner.run(handle_connection(r, w)), '127.0.0.1', 8888)

    # Start the server
    await server

async def handle_connection(reader, writer):
    # Read data from the client
    data = await reader.read(1024)

    # Write data to the client
    writer.write(data)

    # Close the write end of the stream
    writer.write_eof()

    # Get the underlying asyncio transport
    transport = writer.transport

    # Do something with the transport
    print(transport.get_extra_info('peername'))

# Run the main function in a synchronous context
asyncio_runner.run(main())

Real-world applications

The write_eof() method can be used in a variety of real-world applications, such as:

  • Signaling to a client that no more data will be sent

  • Closing a file after writing data to it

  • Closing a network connection after sending data

Potential applications in real world for each:

  • Signaling to a client that no more data will be sent: This can be useful in situations where the client is expecting a response from the server, but the server has no more data to send.

  • Closing a file after writing data to it: This can be useful in situations where the file is being written to by multiple processes, and it is important to ensure that the file is closed correctly when all of the data has been written.

  • Closing a network connection after sending data: This can be useful in situations where the network connection is being used to send data to a remote host, and it is important to ensure that the connection is closed correctly when all of the data has been sent.


get_extra_info(name, default=None)

This method allows you to access additional information about the transport. For example, you can check if it's encrypted or get the remote address.

transport = ...
info = transport.get_extra_info("encryption")
if info:
    print("This transport is encrypted")

drain()

This method is used to control the flow of data. When you write data to a stream, it's stored in a buffer before being sent. If the buffer gets too full, the stream can become blocked and writing will be paused. The drain() method waits until there is enough space in the buffer to continue writing. This ensures that data is sent smoothly and efficiently.

writer = ...
writer.write(data)
await writer.drain()  # Wait until the data is sent

start_tls(sslcontext, server_hostname=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None)

This method is used to securely upgrade an existing connection to TLS (a cryptographic protocol that ensures data privacy and integrity). You provide an SSL context object and it takes care of the encryption, certificate verification, and key exchange.

ssl_context = ssl.SSLContext()
transport = ...
transport.start_tls(ssl_context, server_hostname="example.com")

Real-world applications:

  • Secure communication: TLS is widely used in web browsing, email, banking, and other applications where data privacy is crucial.

  • Flow control: The drain() method is useful when you need to guarantee that data is sent in a controlled manner, such as when sending large files or sensitive information.


Method: is_closing()

  • Purpose: Checks if the stream is closed or in the process of being closed.

  • Example:

    reader, writer = await asyncio.open_connection(...)
    
    if reader.is_closing():
        print("Stream is closed")

Coroutine Method: wait_closed()

  • Purpose: Waits until the stream is closed.

  • Usage: This should be called after the :meth:close method to wait until the underlying connection is closed.

  • Example:

    reader, writer = await asyncio.open_connection(...)
    
    writer.close()
    await writer.wait_closed()

Example of TCP Echo Client Using Streams:

  • Purpose: Creates a TCP echo client using the asyncio.open_connection function.

  • Code:

    import asyncio
    
    async def tcp_echo_client(message):
        reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
    
        print(f'Send: {message!r}')
        writer.write(message.encode())
        await writer.drain()
    
        data = await reader.read(100)
        print(f'Received: {data.decode()!r}')
    
        print('Close the connection')
        writer.close()
        await writer.wait_closed()
    
    asyncio.run(tcp_echo_client('Hello World!'))

Example of TCP Echo Server Using Streams:

  • Purpose: Creates a TCP echo server using the asyncio.start_server function.

  • Code:

    import asyncio
    
    async def handle_echo(reader, writer):
        data = await reader.read(100)
        message = data.decode()
        addr = writer.get_extra_info('peername')
    
        print(f"Received {message!r} from {addr!r}")
    
        print(f"Send: {message!r}")
        writer.write(data)
        await writer.drain()
    
        print("Close the connection")
        writer.close()
        await writer.wait_closed()
    
    async def main():
        server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)
    
        addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
        print(f'Serving on {addrs}')
    
        async with server:
            await server.serve_forever()
    
    asyncio.run(main())

Example of Getting HTTP Headers:

  • Purpose: Queries the HTTP headers of a given URL.

  • Code:

    import asyncio
    import urllib.parse
    import sys
    
    async def print_http_headers(url):
        url = urllib.parse.urlsplit(url)
        if url.scheme == 'https':
            reader, writer = await asyncio.open_connection(url.hostname, 443, ssl=True)
        else:
            reader, writer = await asyncio.open_connection(url.hostname, 80)
    
        query = (
            f"HEAD {url.path or '/'} HTTP/1.0\n"
            f"Host: {url.hostname}\n"
            f"User-Agent: {urllib.request.getuseragent()}\n"
            f"\n"
        )
    
        writer.write(query.encode('latin-1'))
        while True:
            line = await reader.readline()
            if not line:
                break
    
            line = line.decode('latin1').rstrip()
            if line:
                print(f'HTTP header> {line}')
    
        writer.close()
        await writer.wait_closed()
    
    url = sys.argv[1]
    asyncio.run(print_http_headers(url))

Example of Registering an Open Socket to Wait for Data:

  • Purpose: Registers an open socket to wait for data using the asyncio.open_connection function.

  • Code:

    import asyncio
    import socket
    
    async def wait_for_data():
        loop = asyncio.get_running_loop()
    
        rsock, wsock = socket.socketpair()
    
        reader, writer = await asyncio.open_connection(sock=rsock)
    
        loop.call_soon(wsock.send, 'abc'.encode())
    
        data = await reader.read(100)
    
        print("Received:", data.decode())
        writer.close()
        await writer.wait_closed()
    
        wsock.close()
    
    asyncio.run(wait_for_data())

Potential Applications in the Real World

  • TCP Echo Client and Server: These examples demonstrate how to create simple TCP-based network applications.

  • HTTP Header Query: The HTTP header query example can be used to retrieve and analyze HTTP headers for debugging or data collection purposes.

  • Socket Registration: The open socket registration example shows how to integrate existing socket-based applications with asyncio, allowing for asynchronous event-driven handling.