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. 完整的回呼函式集#

供參考,以下是函式庫會在後端上尋找的每一個方法:

方法

回傳值

用途

init(self)

object

當通道首次繫結到主機時,選擇性的一次性初始化。成功時回傳任何非 None 的值。

poll(self)

bool

當有資料可用時回傳 True。

lock(self)

bool

取得通道以進行不可分割的多封包傳輸。

unlock(self)

bool

釋放先前的 lock。

size(self)

int

目前可從通道讀取的位元組數。

shape(self)

tuple

最多四個整數,用以描述資料結構(例如影像高度、寬度、位元組計數)。供主機用來解包具型別的緩衝區。

read(self, offset, size)

bytes

從 offset 開始回傳最多 size 個位元組。當酬載超過協商出的最大值時,每個片段呼叫一次。

readp(self, offset, size)

bytes

read 的零複製(zero-copy)變體:緩衝區的記憶體必須在整個傳輸期間保持有效。

write(self, offset, data)

int

主機在 offset 處寫入了 data。data 是指向協定層接收緩衝區的 bytearray 檢視——在回傳前請先複製出你想保留的內容。

ioctl(self, cmd, length, arg)

int

讀取/寫入模型之外、由應用程式定義的運算碼(opcode)。回傳負值表示錯誤。

flush(self)

object

丟棄任何已緩衝的資料。當主機想要重設通道時呼叫。

is_active(self)

bool

只在代表實體傳輸的後端上(內建的 USB 通道)才有意義。應用程式通道不需要它。

這就是整個後端介面。十二個方法名稱,全部都是選擇性的,協定函式庫會根據存在哪些方法來決定每個通道能做什麼。