使用套接字和流

Using sockets and streams

网络功能可以说是任何异步库中最重要的部分。AnyIO 在其支持的每个后端提供的低级原语之上, 包含了自己实现的高层网络功能。

目前, AnyIO 提供以下网络功能:

  • TCP 套接字(客户端 + 服务器)

  • UNIX 域套接字(客户端 + 服务器)

  • UDP 套接字

  • UNIX 数据报套接字

目前尚不支持更为特殊的网络形式, 如原始套接字和 SCTP。

警告

与标准 BSD 套接字接口和大多数其他网络库不同, AnyIO(从 2.0 版本起)通过引发 EndOfStream 异常来标示任何流的结束, 而不是返回一个空的字节对象。

使用 TCP 套接字

Working with TCP sockets

TCP(传输控制协议)是互联网上最常用的协议。它允许连接到远程主机的端口, 并以可靠的方式发送和接收数据。

要连接到某个地方的监听 TCP 套接字, 可以使用 connect_tcp():

from anyio import connect_tcp, run


async def main():
    async with await connect_tcp('hostname', 1234) as client:
        await client.send(b'Client\n')
        response = await client.receive()
        print(response)

run(main)

为了方便, 您还可以使用 connect_tcp() 在连接后与对端建立 TLS 会话, 方法是传递 tls=True, 或为 ssl_contexttls_hostname 传递一个非空值。

要接收传入的 TCP 连接, 首先使用 create_tcp_listener() 创建一个 TCP 监听器, 并在其上调用 serve():

from anyio import create_tcp_listener, run


async def handle(client):
    async with client:
        name = await client.receive(1024)
        await client.send(b'Hello, %s\n' % name)


async def main():
    listener = await create_tcp_listener(local_port=1234)
    await listener.serve(handle)

run(main)

有关更多信息, 请参阅 TLS 流 部分。

使用 UNIX 套接字

Working with UNIX sockets

UNIX 域套接字是 UNIX 类操作系统上的一种进程间通信形式。它们不能用于连接到远程主机, 并且在 Windows 上无法使用。

UNIX 域套接字的 API 与 TCP 套接字的 API 类似, 不同之处在于, 它使用的是文件系统路径, 而不是主机/端口组合。

这是从 TCP 示例转换为使用 UNIX 套接字的客户端代码:

from anyio import connect_unix, run


async def main():
    async with await connect_unix('/tmp/mysock') as client:
        await client.send(b'Client\n')
        response = await client.receive(1024)
        print(response)

run(main)

监听器代码如下:

from anyio import create_unix_listener, run


async def handle(client):
    async with client:
        name = await client.receive(1024)
        await client.send(b'Hello, %s\n' % name)


async def main():
    listener = await create_unix_listener('/tmp/mysock')
    await listener.serve(handle)

run(main)

备注

UNIX 套接字监听器不会删除它创建的套接字, 因此您可能需要手动删除它们。

发送和接收文件描述符

Sending and receiving file descriptors

UNIX 套接字可以用于将打开的文件描述符(套接字和文件)传递给另一个进程。接收端可以使用 os.fdopensocket.socket 来分别获取可用的文件或套接字对象。

以下是一个示例, 客户端连接到 UNIX 套接字服务器并接收服务器上打开的文件的描述符, 读取文件内容, 然后将其打印到标准输出。

客户端代码:

import os

from anyio import connect_unix, run


async def main():
    async with await connect_unix('/tmp/mysock') as client:
        _, fds = await client.receive_fds(0, 1)
        with os.fdopen(fds[0]) as file:
            print(file.read())

run(main)

服务器代码:

from pathlib import Path

from anyio import create_unix_listener, run


async def handle(client):
    async with client:
        with path.open('r') as file:
            await client.send_fds(b'this message is ignored', [file])


async def main():
    listener = await create_unix_listener('/tmp/mysock')
    await listener.serve(handle)

path = Path('/tmp/examplefile')
path.write_text('Test file')
run(main)

使用 UDP 套接字

Working with UDP sockets

UDP(用户数据报协议)是一种通过网络发送数据包的方式, 不具有连接、重试或错误纠正等特性。

例如, 如果你想创建一个 UDP "hello" 服务, 该服务只读取一个数据包, 然后向发送方发送一个数据包, 内容前面加上 "Hello, ", 你可以这样做:

import socket

from anyio import create_udp_socket, run


async def main():
    async with await create_udp_socket(
        family=socket.AF_INET, local_port=1234
    ) as udp:
        async for packet, (host, port) in udp:
            await udp.sendto(b'Hello, ' + packet, host, port)

run(main)

备注

如果你在本地机器上测试, 或者不知道使用哪个协议族, 可以考虑在上述示例中将 family=socket.AF_INET 替换为 local_host='localhost'

如果你的用例涉及向单个目的地发送大量数据包, 你仍然可以将 UDP 套接字“连接”到特定的主机和端口, 以避免每次发送数据时都需要传递地址和端口:

from anyio import create_connected_udp_socket, run


async def main():
    async with await create_connected_udp_socket(
            remote_host='hostname', remote_port=1234) as udp:
        await udp.send(b'Hi there!\n')

run(main)

使用 UNIX 数据报套接字

Working with UNIX datagram sockets

UNIX 数据报套接字是 UNIX 域套接字的一个子集, 区别在于, 虽然 UNIX 套接字实现了可靠的连续字节流通信(类似于 TCP), 但 UNIX 数据报套接字实现了数据包的通信(类似于 UDP)。

UNIX 数据报套接字的 API 模式类似于 UDP 套接字, 只是将主机/端口组合替换为文件系统路径——下面是使用 UNIX 数据报套接字编写的 UDP "hello" 服务示例:

from anyio import create_unix_datagram_socket, run


async def main():
    async with await create_unix_datagram_socket(
        local_path='/tmp/mysock'
    ) as unix_dg:
        async for packet, path in unix_dg:
            await unix_dg.sendto(b'Hello, ' + packet, path)

run(main)

备注

如果没有设置 local_path, UNIX 数据报套接字将绑定到一个没有名称的地址, 通常无法从其他 UNIX 数据报套接字接收数据报。

类似于 UDP 套接字, 如果你的用例涉及向单个目的地发送大量数据包, 你可以将 UNIX 数据报套接字“连接”到特定路径, 以避免每次发送数据时都需要传递路径:

from anyio import create_connected_unix_datagram_socket, run


async def main():
    async with await create_connected_unix_datagram_socket(
        remote_path='/dev/log'
    ) as unix_dg:
        await unix_dg.send(b'Hi there!\n')

run(main)