使用 trio.socket 进行低级网络编程

Low-level networking with :mod:`trio.socket`

trio.socket 模块提供了 Trio 的基本底层网络 API。如果您正在通过 IPv4/IPv6/Unix 域套接字进行普通的流式连接操作, 那么您可能希望使用上面描述的高层 API。如果您想使用 UDP, 或使用像 AF_BLUETOOTH 这样的特殊地址族, 或者需要直接访问系统网络 API 的所有奇怪部分, 那么您来对地方了。

The trio.socket module provides Trio's basic low-level networking API. If you're doing ordinary things with stream-oriented connections over IPv4/IPv6/Unix domain sockets, then you probably want to stick to the high-level API described above. If you want to use UDP, or exotic address families like AF_BLUETOOTH, or otherwise get direct access to all the quirky bits of your system's networking API, then you're in the right place.

顶级导出

Top-level exports

通常, trio.socket 模块暴露的 API 与标准库的 socket 模块相似。大多数常量 (如 SOL_SOCKET ) 和简单的工具函数 (如 inet_aton() ) 都只是重新导出了, 保持不变。但也有一些不同之处, 下面将进行描述。

首先, Trio 提供了所有返回套接字对象的标准库函数的类比;它们的接口完全相同, 只是它们被修改为返回 Trio 套接字对象:

trio.socket.socket(family=-1, type=-1, proto=-1, fileno=None)

Create a new Trio socket, like socket.socket.

This function's behavior can be customized using set_custom_socket_factory().

trio.socket.socketpair(family=None, type=SocketKind.SOCK_STREAM, proto=0)

Like socket.socketpair(), but returns a pair of Trio socket objects.

trio.socket.fromfd(fd, family, type, proto=0)

Like socket.fromfd(), but returns a Trio socket object.

trio.socket.fromshare(data)

类似于 socket.fromshare(), 但返回一个 Trio 套接字对象。

此外, 还有一个新函数可以直接将标准库的套接字转换为 Trio 套接字:

trio.socket.from_stdlib_socket(sock)

Convert a standard library socket.socket object into a Trio socket object.

返回类型:

SocketType

socket.socket 不同, trio.socket.socket() 是一个函数, 而不是一个类;如果您想检查一个对象是否是 Trio 套接字, 请使用 isinstance(obj, trio.socket.SocketType)

对于名称查找, Trio 提供了标准的函数, 但做了一些修改:

await trio.socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)

Look up a numeric address given a name.

Arguments and return values are identical to socket.getaddrinfo(), except that this version is async.

Also, trio.socket.getaddrinfo() correctly uses IDNA 2008 to process non-ASCII domain names. (socket.getaddrinfo() uses IDNA 2003, which can give the wrong result in some cases and cause you to connect to a different host than the one you intended; see bpo-17305.)

This function's behavior can be customized using set_custom_hostname_resolver().

返回类型:

list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]

await trio.socket.getnameinfo(sockaddr, flags)

Look up a name given a numeric address.

Arguments and return values are identical to socket.getnameinfo(), except that this version is async.

This function's behavior can be customized using set_custom_hostname_resolver().

返回类型:

tuple[str, str]

await trio.socket.getprotobyname(name)

Look up a protocol number by name. (Rarely used.)

Like socket.getprotobyname(), but async.

返回类型:

int

Trio 特意不包含一些过时的, 冗余的或损坏的功能:

_, service_name = await getnameinfo(('127.0.0.1', port), NI_NUMERICHOST)
await getaddrinfo(None, service_name)

Generally, the API exposed by trio.socket mirrors that of the standard library socket module. Most constants (like SOL_SOCKET) and simple utilities (like inet_aton()) are simply re-exported unchanged. But there are also some differences, which are described here.

First, Trio provides analogues to all the standard library functions that return socket objects; their interface is identical, except that they're modified to return Trio socket objects instead:

trio.socket.socket(family=-1, type=-1, proto=-1, fileno=None)

Create a new Trio socket, like socket.socket.

This function's behavior can be customized using set_custom_socket_factory().

trio.socket.socketpair(family=None, type=SocketKind.SOCK_STREAM, proto=0)

Like socket.socketpair(), but returns a pair of Trio socket objects.

trio.socket.fromfd(fd, family, type, proto=0)

Like socket.fromfd(), but returns a Trio socket object.

trio.socket.fromshare(data)

Like socket.fromshare(), but returns a Trio socket object.

In addition, there is a new function to directly convert a standard library socket into a Trio socket:

trio.socket.from_stdlib_socket(sock)

Convert a standard library socket.socket object into a Trio socket object.

返回类型:

SocketType

Unlike socket.socket, trio.socket.socket() is a function, not a class; if you want to check whether an object is a Trio socket, use isinstance(obj, trio.socket.SocketType).

For name lookup, Trio provides the standard functions, but with some changes:

await trio.socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)

Look up a numeric address given a name.

Arguments and return values are identical to socket.getaddrinfo(), except that this version is async.

Also, trio.socket.getaddrinfo() correctly uses IDNA 2008 to process non-ASCII domain names. (socket.getaddrinfo() uses IDNA 2003, which can give the wrong result in some cases and cause you to connect to a different host than the one you intended; see bpo-17305.)

This function's behavior can be customized using set_custom_hostname_resolver().

返回类型:

list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]

await trio.socket.getnameinfo(sockaddr, flags)

Look up a name given a numeric address.

Arguments and return values are identical to socket.getnameinfo(), except that this version is async.

This function's behavior can be customized using set_custom_hostname_resolver().

返回类型:

tuple[str, str]

await trio.socket.getprotobyname(name)

Look up a protocol number by name. (Rarely used.)

Like socket.getprotobyname(), but async.

返回类型:

int

Trio intentionally DOES NOT include some obsolete, redundant, or broken features:

_, service_name = await getnameinfo(('127.0.0.1', port), NI_NUMERICHOST)
await getaddrinfo(None, service_name)

套接字对象

Socket objects

class trio.socket.SocketType

备注

trio.socket.SocketType 是一个抽象类, 不能直接实例化;您可以通过调用构造函数, 如 trio.socket.socket(), 来获得具体的套接字对象。然而, 您可以使用它来检查一个对象是否是 Trio 套接字, 方法是 isinstance(obj, trio.socket.SocketType)

Trio 套接字对象总体上与 标准库套接字对象 非常相似, 但有一些重要的区别:

首先, 也是最显著的, 所有内容都被改造成了“Trio 风格”: 阻塞方法变为异步方法, 并且以下属性 支持:

  • setblocking():Trio 套接字始终表现得像阻塞套接字;如果您需要同时从多个套接字读取/写入, 应该创建多个任务。

  • settimeout():请改用 取消

  • makefile():Python 的类文件 API 是同步的, 因此无法在异步套接字上实现。

  • sendall():可能支持, 但您最好使用更高层次的 SocketStream, 特别是它的 send_all() 方法, 这个方法还会执行额外的错误检查。

此外, 以下方法与 socket.socket 中的相似, 但有一些 Trio 特有的怪癖:

await connect()

将套接字连接到远程地址。

类似于 socket.socket.connect(), 但它是异步的。

警告

由于底层操作系统 API 的限制, 一旦连接尝试开始, 可能无法正确取消该连接。如果 connect() 被取消, 且无法中止连接尝试, 则它将:

  1. 强制关闭套接字以防止意外重用

  2. 引发 Cancelled

简而言之:如果 connect() 被取消, 则套接字处于未知状态——可能是打开的, 也可能是关闭的。唯一合理的做法是关闭它。

is_readable()

检查套接字是否可读。

sendfile()

尚未实现!

我们还跟踪一个额外的状态位, 因为它对 trio.SocketStream 很有用:

did_shutdown_SHUT_WR

如果您调用了 sock.shutdown(SHUT_WR)sock.shutdown(SHUT_RDWR), 则此 bool 属性为 True, 否则为 False。

以下方法与 socket.socket 中的对应方法相同, 唯一不同的是它们是异步的, 并且那些接受地址参数的方法需要预先解析的地址:

所有未在上面提到的方法和属性都与 socket.socket 中的对应项相同:

class trio.socket.SocketType

备注

trio.socket.SocketType is an abstract class and cannot be instantiated directly; you get concrete socket objects by calling constructors like trio.socket.socket(). However, you can use it to check if an object is a Trio socket via isinstance(obj, trio.socket.SocketType).

Trio socket objects are overall very similar to the standard library socket objects, with a few important differences:

First, and most obviously, everything is made "Trio-style": blocking methods become async methods, and the following attributes are not supported:

  • setblocking(): Trio sockets always act like blocking sockets; if you need to read/write from multiple sockets at once, then create multiple tasks.

  • settimeout(): see 取消和超时 instead.

  • makefile(): Python's file-like API is synchronous, so it can't be implemented on top of an async socket.

  • sendall(): Could be supported, but you're better off using the higher-level SocketStream, and specifically its send_all() method, which also does additional error checking.

In addition, the following methods are similar to the equivalents in socket.socket, but have some Trio-specific quirks:

await connect()

Connect the socket to a remote address.

Similar to socket.socket.connect(), except async.

警告

Due to limitations of the underlying operating system APIs, it is not always possible to properly cancel a connection attempt once it has begun. If connect() is cancelled, and is unable to abort the connection attempt, then it will:

  1. forcibly close the socket to prevent accidental reuse

  2. raise Cancelled.

tl;dr: if connect() is cancelled then the socket is left in an unknown state – possibly open, and possibly closed. The only reasonable thing to do is to close it.

is_readable()

Check whether the socket is readable or not.

sendfile()

Not implemented yet!

We also keep track of an extra bit of state, because it turns out to be useful for trio.SocketStream:

did_shutdown_SHUT_WR

This bool attribute is True if you've called sock.shutdown(SHUT_WR) or sock.shutdown(SHUT_RDWR), and False otherwise.

The following methods are identical to their equivalents in socket.socket, except async, and the ones that take address arguments require pre-resolved addresses:

All methods and attributes not mentioned above are identical to their equivalents in socket.socket: