12.7. 通道回调¶
传递给 protocol.register() 的后端对象是一个 Python 类。协议库不会询问该类实现了哪些方法,而是检查实例并连接它所找到的方法。正是这种内省机制使得后端接口非常灵活:最小可用的后端只有两个方法,最完整的后端有十二个方法,应用程序每次按一个方法逐项启用各项能力。
12.7.1. 内省规则¶
protocol.register() 运行时,库会遍历一个固定的可调用名称列表,并绑定它在后端实例上找到的每一个:
向类中添加
read会开启CHANNEL_FLAG_READ。只有设置了该标志,主机对channel_read()的调用才会到达后端。添加
write会开启CHANNEL_FLAG_WRITE,启用channel_write()。添加
lock和unlock会开启CHANNEL_FLAG_LOCK,使主机能够锁定通道以进行多数据包原子读取。添加
poll让主机能够廉价地询问“是否有数据就绪?”,而无需强制执行一次完整的读取。
缺少方法不是错误——协议库只是让相应的能力保持禁用状态。一个仅有 size 和 read 的后端是完全有效的;它是一个只读数据通道。
12.7.2. 只读传感器通道¶
一个传感器通道在每次主机询问时发布一个新读数,并拒绝主机写入,它使用了四个回调:
import protocol
import struct
class TempChannel:
def __init__(self, read_sensor):
self._read_sensor = read_sensor
self._buf = b''
self._fresh = False
def poll(self):
# Tell the host whether a reading is waiting.
return self._fresh
def size(self):
# Sample fresh data on every host-side size query.
value = self._read_sensor()
self._buf = struct.pack('<f', value)
self._fresh = True
return len(self._buf)
def read(self, offset, size):
end = offset + size
if end >= len(self._buf):
self._fresh = False
return self._buf[offset:end]
protocol.register(name='temp', backend=TempChannel(read_temperature))
逐一说明每个方法的作用:
poll返回新鲜度标志。主机在读取前调用它,当它返回False时完全跳过读取。这样就省去了“尚无新数据”时的往返开销。size按需重新生成缓冲区并报告其长度。在此处进行采样意味着后端不需要后台任务——每次测量都由一次主机调用来驱动。read返回缓冲区的一个切片。当缓冲区大于协商的最大有效载荷时,协议库可能会多次调用它;offset参数用于遍历各个分片。没有
write意味着主机写入会在分帧层就被拒绝,根本不会涉及后端。
12.7.3. 完整的回调集合¶
供参考,库会在后端上查找的每一个方法:
方法 |
返回值 |
用途 |
|---|---|---|
|
对象 |
当通道首次绑定到主机时可选的一次性初始化。成功时返回任何非 |
|
bool |
当有数据可用时返回 |
|
bool |
获取通道以进行原子的多数据包传输。 |
|
bool |
释放先前的 |
|
int |
当前可从通道读取的字节数。 |
|
tuple |
最多四个整数,用于描述数据结构(例如图像高度、宽度、字节数)。主机用它来解包带类型的缓冲区。 |
|
bytes |
从 offset 开始返回最多 size 个字节。当有效载荷超过协商的最大值时,每个分片调用一次。 |
|
bytes |
|
|
int |
主机在 offset 处写入了 data。 |
|
int |
读/写模型之外的应用程序自定义操作码。返回负值表示错误。 |
|
对象 |
丢弃所有缓冲的数据。当主机想要重置通道时调用。 |
|
bool |
仅对表示物理传输的后端(内置的 USB 通道)有意义。应用通道不需要它。 |
这就是整个后端接口。十二个方法名,全部可选,协议库根据存在哪些方法来决定每个通道能做什么。