diff --git a/README.md b/README.md index f14a695..e5e3940 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,15 @@ - [I/O Multiplex (I/O多路复用) (Client)](https://leven-cn.github.io/python-cookbook/cookbook/core/net/io_multiplex_client) - [Pack/Unpack Binary Data - `struct`](https://leven-cn.github.io/python-cookbook/cookbook/core/net/struct) +### Asynchronous I/O (异步 I/O) + +- [Coroutine (协程)](https://leven-cn.github.io/python-cookbook/cookbook/core/asyncio/coroutine) +- [Run coroutines concurrently (并发执行协程)](https://leven-cn.github.io/python-cookbook/cookbook/core/asyncio/coroutine_concurrent) +- [Scheduled Tasks (调度任务)](https://leven-cn.github.io/python-cookbook/cookbook/core/asyncio/schedule) +- [Wait](https://leven-cn.github.io/python-cookbook/cookbook/core/asyncio/wait) +- [TCP Server](https://leven-cn.github.io/python-cookbook/cookbook/core/asyncio/tcp_server) +- [TCP Client](https://leven-cn.github.io/python-cookbook/cookbook/core/asyncio/tcp_client) + ## Build (构建) ### Command-Line Arguments Parser @@ -207,20 +216,13 @@ - [IPC - Socket Pair](https://leven-cn.github.io/python-cookbook/recipes/core/ipc_socketpair) - [IPC - UNIX Domain Socket (UDS, UNIX 域套接字) Server and Client](https://leven-cn.github.io/python-cookbook/recipes/core/ipc_unix_domain_socket) - Asynchronous I/O (异步 I/O) - - [Coroutine (协程)](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_coroutine) - - [Chain coroutines (串链协程)](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_coroutine_chain) - - [Run coroutines Concurrently (并发执行协程)](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_coroutine_chain) - - [Scheduled Tasks (调度任务)](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_schedule) - - [Wait](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_wait) - [Nonblocking Main Thread](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_nonblocking) - [Synchronization Primitives: Lock](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_synchronization_lock) - [Synchronization Primitives: Event](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_synchronization_event) - [Synchronization Primitives: Condition](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_synchronization_condition) - [Synchronization Primitives: Semapore (信号量)](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_synchronization_semapore) - [Queue (队列)](https://leven-cn.github.io/python-cookbook/recipes/core/asyncio_queue) - - [TCP Server (High-Level APIs)](https://leven-cn.github.io/python-cookbook/recipes/core/tcp_server_asyncio_high_api) - [TCP Server (Low-Level APIs)](https://leven-cn.github.io/python-cookbook/recipes/core/tcp_server_asyncio_low_api) - - [TCP Client - (High-Level APIs)](https://leven-cn.github.io/python-cookbook/recipes/core/tcp_client_asyncio_high_api) - [TCP Client - (Low-Level APIs)](https://leven-cn.github.io/python-cookbook/recipes/core/tcp_client_asyncio_low_api) - [UDP Server](https://leven-cn.github.io/python-cookbook/recipes/core/udp_server_asyncio) - [UDP Client](https://leven-cn.github.io/python-cookbook/recipes/core/udp_client_asyncio) diff --git a/recipes/core/asyncio_coroutine.md b/cookbook/core/asyncio/coroutine.md similarity index 60% rename from recipes/core/asyncio_coroutine.md rename to cookbook/core/asyncio/coroutine.md index 3019703..566c11e 100644 --- a/recipes/core/asyncio_coroutine.md +++ b/cookbook/core/asyncio/coroutine.md @@ -1,11 +1,8 @@ -# Asynchronous I/O - Create coroutine +# Asynchronous I/O - Coroutine -## Solution +## Recipes ```python -"""Asynchronous I/O - coroutine. -""" - import asyncio import logging import sys @@ -15,9 +12,21 @@ logging.basicConfig( ) -async def coroutine(arg: int): +async def coroutine(arg: int) -> tuple[int, str, str]: + """Coroutine demo.""" logging.debug(f'run coroutine: {arg}') - return arg + 1 + + result_1: str = await task(1, 1.0) + result_2: str = await task(2, 1.5) + + return arg + 1, result_1, result_2 + + +async def task(num: int, wait: float) -> str: + """Coroutine task demo.""" + logging.debug(f'run task {num}, wait {wait} seconds') + await asyncio.sleep(wait) + return f'task {num} result' coro = coroutine(1) @@ -26,7 +35,9 @@ if sys.version_info >= (3, 7): # Python 3.7+ logging.debug(f'result: {result}') else: # Low-level APIs - loop = asyncio.get_event_loop() + # `get_running_loop()` has been added since Python 3.7. + # `get_event_loop()` has been deprecated since Python 3.10. + loop = asyncio.get_running_loop() try: logging.debug('starting event loop') result = loop.run_until_complete(coro) @@ -35,8 +46,6 @@ else: loop.close() ``` -See [source code](https://github.com/leven-cn/python-cookbook/blob/main/examples/core/asyncio_coroutine.py) - ## References - [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) diff --git a/recipes/core/asyncio_coroutine_concurrent.md b/cookbook/core/asyncio/coroutine_concurrent.md similarity index 92% rename from recipes/core/asyncio_coroutine_concurrent.md rename to cookbook/core/asyncio/coroutine_concurrent.md index 85f4410..e730c83 100644 --- a/recipes/core/asyncio_coroutine_concurrent.md +++ b/cookbook/core/asyncio/coroutine_concurrent.md @@ -1,11 +1,8 @@ # Asynchronous I/O - Run coroutines Concurrently -## Solution +## Recipes ```python -"""Asynchronous I/O - Run coroutines concurrently. -""" - import asyncio import logging @@ -97,8 +94,6 @@ result = asyncio.run(main()) # Python 3.7+ logging.debug(f'result: {result}') ``` -See [source code](https://github.com/leven-cn/python-cookbook/blob/main/examples/core/asyncio_coroutine_concurrent.py) - ## References - [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) diff --git a/recipes/core/asyncio_schedule.md b/cookbook/core/asyncio/schedule.md similarity index 92% rename from recipes/core/asyncio_schedule.md rename to cookbook/core/asyncio/schedule.md index 58f169d..8479bf7 100644 --- a/recipes/core/asyncio_schedule.md +++ b/cookbook/core/asyncio/schedule.md @@ -1,11 +1,8 @@ # Asynchronous I/O - Scheduled Tasks -## Solution +## Recipes ```python -"""Asynchronous I/O - Scheduled Tasks. -""" - import asyncio import logging import time @@ -70,8 +67,6 @@ result = asyncio.run(main()) logging.debug(f'result: {result}') ``` -See [source code](https://github.com/leven-cn/python-cookbook/blob/main/examples/core/asyncio_schedule.py) - ## References - [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) diff --git a/recipes/core/tcp_client_asyncio_high_api.md b/cookbook/core/asyncio/tcp_client.md similarity index 80% rename from recipes/core/tcp_client_asyncio_high_api.md rename to cookbook/core/asyncio/tcp_client.md index c07a081..e74d7a8 100644 --- a/recipes/core/tcp_client_asyncio_high_api.md +++ b/cookbook/core/asyncio/tcp_client.md @@ -1,11 +1,8 @@ -# TCP Client - Asynchronous I/O (High-Level APIs) +# TCP Client - Asynchronous I/O ## Solution ```python -"""TCP Client - Asynchronous I/O (High-Level APIs). -""" - import asyncio import logging @@ -32,8 +29,6 @@ async def tcp_echo_client(data: bytes): asyncio.run(tcp_echo_client(b'Hello World!')) # Python 3.7+ ``` -See [source code](https://github.com/leven-cn/python-cookbook/blob/main/examples/core/tcp_client_asyncio_high_api.py) - ## References - [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) diff --git a/cookbook/core/asyncio/tcp_server.md b/cookbook/core/asyncio/tcp_server.md new file mode 100644 index 0000000..8190c1f --- /dev/null +++ b/cookbook/core/asyncio/tcp_server.md @@ -0,0 +1,134 @@ +# TCP Server - Asynchronous I/O + +## Recipes + +```python +import asyncio +import logging +import socket +import sys + +logging.basicConfig( + level=logging.DEBUG, style='{', format='[{threadName} ({thread})] {message}' +) + +recv_bufsize: int | None = None +send_bufsize: int | None = None + + +async def handle_echo( + reader: asyncio.StreamReader, writer: asyncio.StreamWriter +) -> None: + client_address = writer.get_extra_info('peername') + logging.debug(f'connected from {client_address}') + + sock: socket.socket = writer.get_extra_info('socket') + assert sock.type is socket.SOCK_STREAM + assert sock.getpeername() == client_address + assert sock.getsockname() == writer.get_extra_info('sockname') # server address + assert sock.gettimeout() == 0 + assert bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)) + assert bool(sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)) + assert bool(sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT)) + if hasattr(socket, 'TCP_QUICKACK'): + assert sys.platform == 'linux' + assert bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK)) + assert bool(sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)) + if sys.platform == 'linux': # Linux 2.4+ + assert sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE) == 1800 + elif hasattr(socket, 'TCP_KEEPALIVE'): # macOS and Python 3.10+ + assert sys.platform == 'darwin' and sys.version_info >= (3, 10) + assert not sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE) == 1800 + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 1800) + assert sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT) == 5 + assert sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL) == 15 + + # logging.debug(dir(sock)) + + # Recv + data = await reader.read(100) + logging.debug(f'recv: {data!r}') + + # Send + writer.write(data) + await writer.drain() + logging.debug(f'sent: {data!r}') + + writer.close() + + +async def tcp_echo_server( + host: str, + port: int, + *, + accept_queue_size: int = socket.SOMAXCONN, + keep_alive_idle: int | None = None, + keep_alive_cnt: int | None = None, + keep_alive_intvl: int | None = None, + start_serving: bool = False, +) -> None: + # Low-level APIs: loop.create_server() + server = await asyncio.start_server( + handle_echo, + host, + port, + reuse_address=True, + reuse_port=True, + backlog=accept_queue_size, + start_serving=start_serving, + ) + + # Prior to Python 3.7 `asyncio.Server.sockets` used to return an internal list of + # server sockets directly. + # In 3.7 a copy of that list is returned. + server_addressess = ', '.join(str(sock.getsockname()) for sock in server.sockets) + logging.debug(f'Serving on {server_addressess}') + + for sock in server.sockets: + # Issue: ? The socket option `TCP_NODELAY` is set by default in Python 3.6+ + assert not bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + # Keep-Alive + if ( + keep_alive_cnt is not None + and keep_alive_intvl is not None + and keep_alive_idle is not None + ): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + if sys.platform == 'linux': # Linux 2.4+ + sock.setsockopt( + socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, keep_alive_idle + ) + elif hasattr(socket, 'TCP_KEEPALIVE'): + assert sys.platform == 'darwin' and sys.version_info >= (3, 10) + sock.setsockopt( + socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, keep_alive_idle + ) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, keep_alive_cnt) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, keep_alive_intvl) + + # QUICK ACK + if hasattr(socket, 'TCP_QUICKACK'): + assert sys.platform == 'linux' + assert bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK)) + + # `asyncio.Server` object is an asynchronous context manager since Python 3.7. + if not start_serving: + async with server: + await server.serve_forever() + + +asyncio.run( + tcp_echo_server( + '127.0.0.1', 8888, keep_alive_idle=1800, keep_alive_cnt=5, keep_alive_intvl=15 + ) +) # Python 3.7+ +``` + +## References + +- [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) +- [Python - `socket` module](https://docs.python.org/3/library/socket.html) +- [PEP 3156 – Asynchronous IO Support Rebooted: the "asyncio" Module](https://peps.python.org/pep-3156/) +- [PEP 3151 – Reworking the OS and IO exception hierarchy](https://peps.python.org/pep-3151/) diff --git a/recipes/core/asyncio_wait.md b/cookbook/core/asyncio/wait.md similarity index 90% rename from recipes/core/asyncio_wait.md rename to cookbook/core/asyncio/wait.md index 8359cb2..2b4fba6 100644 --- a/recipes/core/asyncio_wait.md +++ b/cookbook/core/asyncio/wait.md @@ -1,11 +1,8 @@ # Asynchronous I/O - Wait -## Solution +## Recipes ```python -"""Asynchronous I/O - Wait. -""" - import asyncio import logging @@ -50,8 +47,6 @@ async def main(): asyncio.run(main()) # Python 3.7+ ``` -See [source code](https://github.com/leven-cn/python-cookbook/blob/main/examples/core/asyncio_wait.py) - ## References - [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) diff --git a/examples/core/asyncio_coroutine.py b/examples/core/asyncio_coroutine.py index 9a30bda..e800565 100644 --- a/examples/core/asyncio_coroutine.py +++ b/examples/core/asyncio_coroutine.py @@ -10,10 +10,21 @@ ) -async def coroutine(arg: int) -> int: +async def coroutine(arg: int) -> tuple[int, str, str]: """Coroutine demo.""" logging.debug(f'run coroutine: {arg}') - return arg + 1 + + result_1: str = await task(1, 1.0) + result_2: str = await task(2, 1.5) + + return arg + 1, result_1, result_2 + + +async def task(num: int, wait: float) -> str: + """Coroutine task demo.""" + logging.debug(f'run task {num}, wait {wait} seconds') + await asyncio.sleep(wait) + return f'task {num} result' coro = coroutine(1) diff --git a/examples/core/asyncio_coroutine_chain.py b/examples/core/asyncio_coroutine_chain.py deleted file mode 100644 index 7c6a675..0000000 --- a/examples/core/asyncio_coroutine_chain.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Asynchronous I/O - Chain coroutines. -""" - -import asyncio -import logging - -logging.basicConfig( - level=logging.DEBUG, style='{', format='[{threadName} ({thread})] {message}' -) - - -async def coroutine(arg: int) -> tuple[str, str]: - """Coroutine demo.""" - logging.debug(f'run coroutine: {arg}') - - result_1: str = await task(1, 1.0) - result_2: str = await task(2, 1.5) - - return (result_1, result_2) - - -async def task(num: int, wait: float) -> str: - """Coroutine task demo.""" - logging.debug(f'run task {num}, wait {wait} seconds') - await asyncio.sleep(wait) - return f'task {num} result' - - -coro = coroutine(1) -result: tuple[str, str] = asyncio.run(coro) # Python 3.7+ -logging.debug(f'result: {result}') diff --git a/examples/core/tcp_client_asyncio_high_api.py b/examples/core/asyncio_tcp_client.py similarity index 100% rename from examples/core/tcp_client_asyncio_high_api.py rename to examples/core/asyncio_tcp_client.py diff --git a/examples/core/asyncio_tcp_server.py b/examples/core/asyncio_tcp_server.py new file mode 100644 index 0000000..030dd41 --- /dev/null +++ b/examples/core/asyncio_tcp_server.py @@ -0,0 +1,124 @@ +"""TCP Server - Asynchronous I/O (High-Level APIs). +""" + +import asyncio +import logging +import socket +import sys + +logging.basicConfig( + level=logging.DEBUG, style='{', format='[{threadName} ({thread})] {message}' +) + +recv_bufsize: int | None = None +send_bufsize: int | None = None + + +async def handle_echo( + reader: asyncio.StreamReader, writer: asyncio.StreamWriter +) -> None: + client_address = writer.get_extra_info('peername') + logging.debug(f'connected from {client_address}') + + sock: socket.socket = writer.get_extra_info('socket') + assert sock.type is socket.SOCK_STREAM + assert sock.getpeername() == client_address + assert sock.getsockname() == writer.get_extra_info('sockname') # server address + assert sock.gettimeout() == 0 + assert bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)) + assert bool(sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)) + assert bool(sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT)) + if hasattr(socket, 'TCP_QUICKACK'): + assert sys.platform == 'linux' + assert bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK)) + assert bool(sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)) + if sys.platform == 'linux': # Linux 2.4+ + assert sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE) == 1800 + elif hasattr(socket, 'TCP_KEEPALIVE'): # macOS and Python 3.10+ + assert sys.platform == 'darwin' and sys.version_info >= (3, 10) + assert not sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE) == 1800 + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 1800) + assert sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT) == 5 + assert sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL) == 15 + + # logging.debug(dir(sock)) + + # Recv + data = await reader.read(100) + logging.debug(f'recv: {data!r}') + + # Send + writer.write(data) + await writer.drain() + logging.debug(f'sent: {data!r}') + + writer.close() + + +async def tcp_echo_server( + host: str, + port: int, + *, + accept_queue_size: int = socket.SOMAXCONN, + keep_alive_idle: int | None = None, + keep_alive_cnt: int | None = None, + keep_alive_intvl: int | None = None, + start_serving: bool = False, +) -> None: + # Low-level APIs: loop.create_server() + server = await asyncio.start_server( + handle_echo, + host, + port, + reuse_address=True, + reuse_port=True, + backlog=accept_queue_size, + start_serving=start_serving, + ) + + # Prior to Python 3.7 `asyncio.Server.sockets` used to return an internal list of + # server sockets directly. + # In 3.7 a copy of that list is returned. + server_addressess = ', '.join(str(sock.getsockname()) for sock in server.sockets) + logging.debug(f'Serving on {server_addressess}') + + for sock in server.sockets: + # Issue: ? The socket option `TCP_NODELAY` is set by default in Python 3.6+ + assert not bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + # Keep-Alive + if ( + keep_alive_cnt is not None + and keep_alive_intvl is not None + and keep_alive_idle is not None + ): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + if sys.platform == 'linux': # Linux 2.4+ + sock.setsockopt( + socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, keep_alive_idle + ) + elif hasattr(socket, 'TCP_KEEPALIVE'): + assert sys.platform == 'darwin' and sys.version_info >= (3, 10) + sock.setsockopt( + socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, keep_alive_idle + ) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, keep_alive_cnt) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, keep_alive_intvl) + + # QUICK ACK + if hasattr(socket, 'TCP_QUICKACK'): + assert sys.platform == 'linux' + assert bool(sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK)) + + # `asyncio.Server` object is an asynchronous context manager since Python 3.7. + if not start_serving: + async with server: + await server.serve_forever() + + +asyncio.run( + tcp_echo_server( + '127.0.0.1', 8888, keep_alive_idle=1800, keep_alive_cnt=5, keep_alive_intvl=15 + ) +) # Python 3.7+ diff --git a/examples/core/tcp_server_asyncio_high_api.py b/examples/core/tcp_server_asyncio_high_api.py deleted file mode 100644 index a599d96..0000000 --- a/examples/core/tcp_server_asyncio_high_api.py +++ /dev/null @@ -1,97 +0,0 @@ -"""TCP Server - Asynchronous I/O (High-Level APIs). -""" - -from __future__ import annotations - -import asyncio -import logging -import socket -import sys - -logging.basicConfig( - level=logging.DEBUG, style='{', format='[{threadName} ({thread})] {message}' -) - -tcp_quickack = True -recv_bufsize: int | None = None -send_bufsize: int | None = None - - -async def handle_echo( - reader: asyncio.StreamReader, writer: asyncio.StreamWriter -) -> None: - # `socket.getpeername()` - client_address = writer.get_extra_info('peername') - logging.debug(f'connected from {client_address}') - - # `socket.getsockname()` - # server_address = writer.get_extra_info('sockname') - - sock = writer.get_extra_info('socket') - assert sock.type is socket.SOCK_STREAM - assert sock.getpeername() == client_address - assert sock.getsockname() == writer.get_extra_info('sockname') - assert sock.gettimeout() == 0.0 - assert sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1 - assert sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1 - - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - - # TCP Keep-Alive - sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - if sys.platform == 'linux': # Linux 2.4+ - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1800) - elif sys.platform == 'darwin' and sys.version_info >= (3, 10): - sock.setsockopt( - socket.IPPROTO_TCP, - socket.TCP_KEEPALIVE, # pylint: disable=no-member - 1800, - ) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15) - - # handle_tcp_quickack(sock, tcp_quickack) - # logging.debug(dir(sock)) - - # Recv - data = await reader.read(100) - logging.debug(f'recv: {data!r}') - - # Send - writer.write(data) - await writer.drain() - logging.debug(f'sent: {data!r}') - - writer.close() - - -async def tcp_echo_server( - host: str, - port: int, - *, - backlog: int = socket.SOMAXCONN, -) -> None: - # Low-level APIs: loop.create_server() - # The socket option `TCP_NODELAY` is set by default in Python 3.6+ - server = await asyncio.start_server( - handle_echo, - host, - port, - reuse_address=True, - reuse_port=True, - backlog=backlog, - start_serving=True, - ) - - # Prior to Python 3.7 `asyncio.Server.sockets` used to return an internal list of - # server sockets directly. - # In 3.7 a copy of that list is returned. - server_addressess = ', '.join(str(sock.getsockname()) for sock in server.sockets) - logging.debug(f'Serving on {server_addressess}') - - # `asyncio.Server` object is an asynchronous context manager since Python 3.7. - async with server: - await server.serve_forever() - - -asyncio.run(tcp_echo_server('127.0.0.1', 8888)) # Python 3.7+ diff --git a/recipes/core/asyncio_coroutine_chain.md b/recipes/core/asyncio_coroutine_chain.md deleted file mode 100644 index 576e349..0000000 --- a/recipes/core/asyncio_coroutine_chain.md +++ /dev/null @@ -1,42 +0,0 @@ -# Asynchronous I/O - Create Chain coroutines - -## Solution - -```python -"""Asynchronous I/O - Chain coroutines. -""" - -import asyncio -import logging - -logging.basicConfig( - level=logging.DEBUG, style='{', format='[{threadName} ({thread})] {message}' -) - - -async def coroutine(arg: int): - logging.debug(f'run coroutine: {arg}') - - result_1 = await task(1, 1.0) - result_2 = await task(2, 1.5) - - return (result_1, result_2) - - -async def task(num: int, wait: float): - logging.debug(f'run task {num}, wait {wait} seconds') - await asyncio.sleep(wait) - return f'task {num} result' - - -coro = coroutine(1) -result = asyncio.run(coro) # Python 3.7+ -logging.debug(f'result: {result}') -``` - -See [source code](https://github.com/leven-cn/python-cookbook/blob/main/examples/core/asyncio_coroutine_chain.py) - -## References - -- [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) -- [PEP 3156 – Asynchronous IO Support Rebooted: the "asyncio" Module](https://peps.python.org/pep-3156/) diff --git a/recipes/core/tcp_server_asyncio_high_api.md b/recipes/core/tcp_server_asyncio_high_api.md deleted file mode 100644 index c6a185a..0000000 --- a/recipes/core/tcp_server_asyncio_high_api.md +++ /dev/null @@ -1,109 +0,0 @@ -# TCP Server - Asynchronous I/O (High-Level APIs) - -## Solution - -```python -"""TCP Server - Asynchronous I/O (High-Level APIs). -""" - -# PEP 604, Allow writing union types as X | Y (Python 3.10+) -from __future__ import annotations - -import asyncio -import logging -import socket - -from net import ( - handle_socket_bufsize, - handle_tcp_quickack, -) - -logging.basicConfig( - level=logging.DEBUG, style='{', format='[{threadName} ({thread})] {message}' -) - -tcp_quickack = True -recv_bufsize: int | None = None -send_bufsize: int | None = None - - -async def handle_echo(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): - # `socket.getpeername()` - client_address = writer.get_extra_info('peername') - logging.debug(f'connected from {client_address}') - - # `socket.getsockname()` - # server_address = writer.get_extra_info('sockname') - - sock = writer.get_extra_info('socket') - assert sock.type is socket.SOCK_STREAM - assert sock.getpeername() == client_address - assert sock.getsockname() == writer.get_extra_info('sockname') - assert sock.gettimeout() == 0.0 - assert sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1 - assert sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1 - handle_tcp_quickack(sock, tcp_quickack) - handle_socket_bufsize(sock, recv_bufsize, send_bufsize) - # logging.debug(dir(sock)) - - # Recv - data = await reader.read(100) - logging.debug(f'recv: {data!r}') - - # Send - writer.write(data) - await writer.drain() - logging.debug(f'sent: {data!r}') - - writer.close() - - -async def tcp_echo_server( - host: str, - port: int, - *, - backlog: int = 100, -): - # Low-level APIs: loop.create_server() - # The socket option `TCP_NODELAY` is set by default in Python 3.6+ - server = await asyncio.start_server( - handle_echo, - host, - port, - reuse_address=True, - reuse_port=True, - backlog=backlog, - start_serving=True, - ) - - # Prior to Python 3.7 `asyncio.Server.sockets` used to return an internal list of - # server sockets directly. - # In 3.7 a copy of that list is returned. - server_addressess = ', '.join(str(sock.getsockname()) for sock in server.sockets) - logging.debug(f'Serving on {server_addressess}') - - # `asyncio.Server` object is an asynchronous context manager since Python 3.7. - async with server: - await server.serve_forever() - - -asyncio.run(tcp_echo_server('127.0.0.1', 8888)) # Python 3.7+ -``` - -## More - -- [TCP Reuse Address](tcp_reuse_address) -- [Reuse Port](reuse_port) -- [TCP/UDP (Recv/Send) Buffer Size](net_buffer_size) -- [TCP `listen()` Queue](tcp_listen_queue) -- [TCP Keep-Alive](tcp_keepalive) -- [TCP Nodelay (Dsiable Nagle's Algorithm)](tcp_nodelay) -- [TCP Quick ACK (Disable Delayed ACK (延迟确认))](tcp_quickack) -- [TCP Slow Start (慢启动)](../../more/core/tcp_slowstart) - -## References - -- [Python - `asyncio` module](https://docs.python.org/3/library/asyncio.html) -- [Python - `socket` module](https://docs.python.org/3/library/socket.html) -- [PEP 3156 – Asynchronous IO Support Rebooted: the "asyncio" Module](https://peps.python.org/pep-3156/) -- [PEP 3151 – Reworking the OS and IO exception hierarchy](https://peps.python.org/pep-3151/)