12.5. 可靠性 -- 序號、ACK 與重傳¶
封包封裝層(framing layer)透過 CRC 偵測資料損毀。可靠性層則會在封包未能完整送達時協商重傳,把「偵測到損毀」轉變為「應用程式永遠看不到損壞的資料」。
12.5.1. 序號¶
每個封包標頭都帶有一個位元組的序號,且每個傳輸方向各自獨立。傳送端在傳送前會遞增計數器;接收端則檢查每個收到封包的序號是否為前一個序號加一(以 256 取模)。
接收端可能會收到三種情況,而非乾淨且依序排列的封包:
預期的序號,且 CRC 有效。此封包會被遞交給上一層。
預期的序號,但 CRC 錯誤。接收端丟棄此封包,並(若已協商使用 ACK)送出 NAK 要求重傳。
比預期值高一的序號,且 CRC 有效。接收端得知前一個封包遺失了;它會送出一個指向遺失序號的 NAK,並暫存這個新封包。
重複的情況(在原封包終於送達後又收到一份重傳)是透過比對預期計數器來處理的:如果序號落後於預期值,這個封包就是重複的,接收端會在送出傳送端顯然第一次沒收到的那個 ACK 後將其丟棄。
12.5.2. ACK 與 NAK¶
封包標頭中的兩個旗標位元承載著可靠性流量本身:
在送出的封包上設定
ACK_REQ代表「我希望收到回覆確認」。資料封包通常會設定此旗標;狀態探測(ping)與一次性事件則可能不設定。封包上設定
ACK代表「此封包是對標頭中序號的確認回覆」。它本身不帶任何酬載。設定
NAK代表「此封包拒絕了先前的某個封包」——通常是因為 CRC 錯誤或序號出現缺口。標頭會指出傳送端應重傳哪一個序號。
傳送端執行一個停止並等待(stop-and-wait)迴圈:它傳送一個需要確認的封包,然後等待對應的 ACK(或 NAK)後才傳送下一個。這種單一封包在途(single-in-flight)的模型讓傳送端的狀態維持有界——在最小的相機(cam)上只需幾百個位元組——並符合此協定作為兩個端點之間控制通道的角色,而非為吞吐量最佳化的管道。收到 NAK 時,傳送端會設定 RTX 旗標重傳同一個封包,讓接收端知道這是一次重試。
12.5.3. 重傳時序¶
如果在重傳逾時時間內既沒收到 ACK 也沒收到 NAK,傳送端會自行重傳在途的封包。逾時時間預設為 500 ms,並在每次連續重試時加倍(1 秒、2 秒……)。在達到設定的重試次數後——預設為三次——傳送端便放棄,並向應用程式回報傳輸錯誤。
將逾時時間加倍是標準的指數退避(exponential backoff)模式。較短的首次逾時能迅速捕捉到遺失的封包;加倍機制則意味著一個忙碌數百毫秒的主機不會觸發一連串會加重負載的重複封包。
12.5.4. 設定可靠性¶
在雙方同意下,當應用程式能夠承受資料遺失時,兩端都可以關閉可靠性層的部分功能:
protocol.init(ack=False)會停用每個封包的 ACK。傳送端發出後就不再理會;接收端則遞交所有收到的內容。適用於串流感測器資料,因為過時的取樣是可接受的。protocol.init(seq=False)會關閉序號追蹤,這也意味著一併關閉 ACK。僅在完全可靠的傳輸上才有用。protocol.init(crc=False)會關閉 CRC 驗證,但保留其餘的封裝功能。只有在傳輸本身夠穩健、不會發生 CRC 錯誤時才值得這麼做。
預設值——全部開啟——是任何主機對相機除錯工作的正確起點。一旦應用程式進入生產階段,這些取捨就會變得與其資料及傳輸方式息息相關。
12.5.5. 狀態碼¶
當傳輸錯誤確實向上傳遞到應用程式碼時,它會以狀態碼的形式出現。協定函式庫定義了十種:
SUCCESS-- 操作已完成。FAILED-- 指令因不明原因失敗。INVALID-- 接收端拒絕了該指令或其某個引數。TIMEOUT-- 重試計時器逾時。BUSY-- 相機正忙碌中(通常是通道被鎖定)。CHECKSUM-- 標頭或酬載的 CRC 不相符。SEQUENCE-- 序號錯亂的程度超出該層所能復原的範圍。OVERFLOW-- 酬載超過了協商的最大值。FRAGMENT-- 一則多片段訊息送達時有片段遺失。UNKNOWN-- 針對真正非預期情況的防禦性萬用項。
呼叫 channel_read() 的主機程式碼會將這些視為 Python 例外;而選擇採用自訂錯誤處理的相機端應用程式碼,則會將它們視為後端回呼函式的回傳值。大多數相機應用程式根本不需要查看這些狀態碼——函式庫會處理重試,只有真正無法復原的失敗(例如傳輸本身已不存在)才會到達應用程式。
有了封裝層來偵測損毀、可靠性層來從中復原,線路層級的工作就完成了。應用程式碼看到的是已封裝、依序排列、完整無缺的封包;其中的位元組則可自由表示上層通道想要的任何意義。