12.5. 信頼性 -- シーケンス、ACK、再送¶
フレーミング層はCRCによってデータの破損を検出します。信頼性層は、パケットが無傷で届かなかった場合に再送をネゴシエートすることで、「検出された破損」を「アプリケーションが壊れたデータを決して目にしない」状態へと変えます。
12.5.1. シーケンス番号¶
各パケットのヘッダには1バイトのシーケンス番号が含まれており、通信方向ごとに別々に管理されます。送信側は送信前にカウンタをインクリメントし、受信側は受信した各パケットのシーケンスが直前の値に1を加えたもの(256を法とする)であることを確認します。
受信側には、整然と順序どおりに並んだパケットの代わりに、次の3つのいずれかが現れる可能性があります。
想定どおりのシーケンス番号で、CRCも正しい場合。パケットは次の層へ引き渡されます。
想定どおりのシーケンス番号だが、CRCが不正な場合。受信側はパケットを破棄し、(ACKがネゴシエートされていれば)再送を要求するNAKを送信します。
想定より1つ大きいシーケンス番号で、CRCは正しい場合。受信側は直前のパケットが失われたと判断し、欠落したシーケンスを参照するNAKを送信して、新しいパケットを保留しておきます。
重複したケース(最終的に届いたオリジナルの後に再送が到着した場合)は、想定カウンタとの照合によって処理されます。シーケンスが想定より後ろにある場合、そのパケットは重複であり、受信側は明らかに送信側が最初に受け取れなかったACKを再送した上で、パケットを破棄します。
12.5.2. ACKとNAK¶
パケットヘッダ内の2つのフラグビットが、信頼性に関するやり取り自体を担います。
送信パケットに
ACK_REQを立てると、「確認応答を返してほしい」という意味になります。データパケットは通常これを立てますが、ステータスのping(ピング)や一度きりのイベントでは立てない場合があります。パケットに
ACKを立てると、「このパケットはヘッダ内のシーケンス番号に対する確認応答である」という意味になります。これ自体はペイロードを持ちません。NAKを立てると、「このパケットは直前のパケットを拒否する」という意味になります。通常はCRCの不正やシーケンス番号の欠落が原因です。ヘッダは送信側に対して、どのシーケンスを再送すべきかを指し示します。
送信側はstop-and-wait(停止待機)ループで動作します。確認応答を要求するパケットを1つ送信し、対応するACK(またはNAK)を待ってから次を送ります。同時に1つだけ送信するというモデルにより、送信側の状態が抑えられ -- 最小のカメラでも数百バイト程度に収まり -- スループットを最適化したパイプというより、2つのエンドポイント間の制御チャネルというプロトコルの役割に合致します。NAKを受け取ると、送信側は RTX フラグを立てて同じパケットを再送し、受信側に再試行であることを伝えます。
12.5.3. 再送のタイミング¶
再送タイムアウトの間にACKもNAKも届かない場合、送信側は処理中のパケットを自発的に再送します。タイムアウトのデフォルトは 500 ms で、連続する再試行ごとに2倍になります(1秒、2秒、…)。設定された再試行回数 -- デフォルトは3回 -- を超えると、送信側は処理を諦め、トランスポートエラーをアプリケーションに報告します。
タイムアウトを2倍にしていくのは、標準的な指数バックオフのパターンです。最初のタイムアウトを短くすることで失われたパケットを素早く捕捉できます。2倍にしていくことで、数百ミリ秒の間ビジー状態になったホストが、負荷を増大させる重複の嵐を引き起こすことを防ぎます。
12.5.4. 信頼性の設定¶
アプリケーションがデータの損失を許容できる場合、両端は合意の上で信頼性層の一部を無効にできます。
protocol.init(ack=False)はパケットごとのACKを無効にします。送信側は送信したら忘れ、受信側は届いたものをそのまま引き渡します。古くなったサンプルが許容されるセンサーデータのストリーミングに適しています。protocol.init(seq=False)はシーケンス番号の追跡を無効にし、これは同時にACKの無効化も意味します。完全に信頼できるトランスポートでのみ有用です。protocol.init(crc=False)はCRC検証を無効にしますが、フレーミングの他の部分はそのまま残します。トランスポート自体がCRCエラーが発生しないほど堅牢な場合にのみ行う価値があります。
デフォルト -- すべて有効 -- は、あらゆるホストとカメラ間のデバッグセッションにおける適切な出発点です。アプリケーションが本番環境に入れば、トレードオフはそのデータとトランスポートに固有のものになります。
12.5.5. ステータスコード¶
トランスポートエラーがアプリケーションコードまで伝播すると、それはステータスコードとして届きます。プロトコルライブラリは10種類を定義しています。
SUCCESS-- 操作が完了しました。FAILED-- 原因不明の理由でコマンドが失敗しました。INVALID-- 受信側がコマンドまたはその引数のいずれかを拒否しました。TIMEOUT-- 再試行タイマーが満了しました。BUSY-- カメラがビジー状態です(通常はチャネルがロックされている)。CHECKSUM-- ヘッダまたはペイロードのCRCが一致しませんでした。SEQUENCE-- シーケンス番号が、層が回復できる範囲を超えて順序が乱れていました。OVERFLOW-- ペイロードがネゴシエートされた最大値を超えました。FRAGMENT-- 複数フラグメントのメッセージが、欠落した部分を伴って到着しました。UNKNOWN-- 本当に予期しない状況に対する防御的な総括的コードです。
channel_read() を呼び出すホストコードは、これらをPythonの例外として受け取ります。カスタムエラー処理を選択したカメラ側のアプリケーションコードは、バックエンドコールバックからの戻り値として受け取ります。ほとんどのカメラアプリではステータスコードを一切見る必要はありません -- ライブラリが再試行を処理し、本当に回復不能な障害(例えばトランスポート自体が失われた場合)だけがアプリケーションに到達します。
破損を検出するフレーミングと、そこから回復する信頼性が整えば、ワイヤレベルの作業は完了です。アプリケーションコードはフレーム化され、順序づけられ、無傷のパケットを目にします。その中のバイト列は、上位のチャネルが意味させたいものを自由に表現できます。