12.7. 通道回呼函式¶
傳遞給 protocol.register() 的後端物件是一個 Python 類別。協定函式庫不會詢問該類別實作了哪些方法,而是檢視實例並接上它所找到的方法。正是這種自我檢視(introspection)讓後端介面具有彈性:最精簡的可用後端只需兩個方法,最完整的則有十二個,而應用程式可以一次一個方法地選擇加入各項功能。
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回傳緩衝區的一個切片。當緩衝區大於協商出的最大酬載(payload)時,協定函式庫可能會多次呼叫它;offset引數會逐一走訪這些片段。沒有
write表示主機寫入會在框架(framing)層就被拒絕,根本不會涉及後端。
12.7.3. 完整的回呼函式集¶
供參考,以下是函式庫會在後端上尋找的每一個方法:
方法 |
回傳值 |
用途 |
|---|---|---|
|
object |
當通道首次繫結到主機時,選擇性的一次性初始化。成功時回傳任何非 |
|
bool |
當有資料可用時回傳 |
|
bool |
取得通道以進行不可分割的多封包傳輸。 |
|
bool |
釋放先前的 |
|
int |
目前可從通道讀取的位元組數。 |
|
tuple |
最多四個整數,用以描述資料結構(例如影像高度、寬度、位元組計數)。供主機用來解包具型別的緩衝區。 |
|
bytes |
從 offset 開始回傳最多 size 個位元組。當酬載超過協商出的最大值時,每個片段呼叫一次。 |
|
bytes |
|
|
int |
主機在 offset 處寫入了 data。 |
|
int |
讀取/寫入模型之外、由應用程式定義的運算碼(opcode)。回傳負值表示錯誤。 |
|
object |
丟棄任何已緩衝的資料。當主機想要重設通道時呼叫。 |
|
bool |
只在代表實體傳輸的後端上(內建的 USB 通道)才有意義。應用程式通道不需要它。 |
這就是整個後端介面。十二個方法名稱,全部都是選擇性的,協定函式庫會根據存在哪些方法來決定每個通道能做什麼。