許多RPC系統支持掛電話,但有一個額外的挑戰:如果你有一個信號流,以及客戶端觀察流中的每個信號,然後事情就變得複雜,如果正在產生新的信號,這一點很重要客戶端比RPC更快地讀取它們。您需要爲每個客戶端保留一個緩衝區。但如果客戶死亡並停止提出請求會怎麼樣?現在,您需要某種超時後清除它。
與大多數其他RPC系統不同,Cap'n Proto支持即時生成新對象。因此,您可以將您的信號流表示爲一系列對象。例如:
struct MyPayload { ... }
interface MyInterface {
subscribe @0() -> (firstSignal :Signal(MyPayload));
# Subscribe to signals from this interface.
}
interface Signal(Type) {
# One signal in a stream of signals. Has a payload, and lets you
# wait for the next signal.
get @0() -> (value :Type);
# Gets the payload value of this signal. (Returns immediately.)
waitForNext @1() -> (nextSignal :Signal(Type));
# Waits for the next signal in the sequence, returning a new
# `Signal` object representing it.
}
這極大地簡化了,因爲頭兒原服務器端狀態管理,儘快將自動調用每個對象的析構函數,因爲所有的客戶都表示,他們用它做(通過破壞客戶端參考,又名「放棄」它)。如果客戶端斷開連接,則其所有引用都將被隱式刪除。
回調
因爲頭兒原允許在兩個方向上的RPC調用(客戶端 - >服務器和服務器 - >客戶端),你可以實現一個「信號」或者發佈/使用回調訂閱機制:
struct MyPayload { ... }
interface MyInterface {
subscribe @0 (cb :Callback(MyPayload)) -> (handle :Handle);
}
interface Callback(Type) {
call @0 (value :Type);
}
interface Handle {}
客戶端調用subscribe()
並傳遞迴調對象cb
。然後,服務器可以隨時在發出信號時回撥給客戶端。
請注意subscribe()
返回一個Handle
,這是一個沒有方法的對象。這樣做的目的是檢測客戶何時退訂。如果客戶端丟棄handle
,則會通知服務器(服務器端對象的析構函數將運行),然後服務器可以註銷回調。這也處理客戶端斷開連接的情況 - 所有對象引用都隱式地斷開連接。
乍一看,由於其簡單性,該解決方案可能看起來比鏈式對象解決方案好得多。但是,它有一個問題,即現在您的對象引用指向兩個方向,這可能導致循環。在客戶端代碼中,必須小心確保回調實現不「擁有」保持註冊的句柄,否則它永遠不會被清理(連接關閉時除外)。在等待服務器取消註冊回調的同時,您還必須確保在刪除句柄後短時間內仍可以調用回調。這些問題並沒有出現在對象鏈解決方案中,這可能會使解決方案的實施更加清晰。
其他RPC系統
我討論上面,因爲我是作家,是因爲它提供了比大多數RPC系統更多的選擇頭兒原。
如果你使用gRPC,你可以使用它的「流媒體」功能來支持類似信號的東西。流式RPC可以隨着時間的推移返回多個響應。
我不確定節儉。我最後一次嘗試它時,請求必須是FIFO,這意味着長時間運行的RPC是不可能的。然而,那是很久以前的事了,也許從那以後它就變了。
你能否告訴我如果我不想在第一次接收到信號後取消註冊回調?你的意思是我應該在某處存儲'Handle'嗎? –
@VictorPolevoy客戶應該把'Handle'保存在內存中,是的。雖然可能不會存儲到磁盤,因爲整個操作都與特定的實時網絡連接有關。如果連接死亡,那麼回調將不再起作用。客戶端需要重新連接並註冊新的回調。 –
有沒有一種方法可以通過提供capnp數據緩衝區來解析從套接字接收的來自外部的套接字? –