一般建議
本節會說明以下主題的技巧、最佳做法和一般建議: 在 Fuchsia 介面定義中定義通訊協定 語言:
另請參閱 FIDL 樣式指南。
通訊協定與物件
FIDL 是定義處理序間通訊通訊協定的語言。雖然 語法類似於物件導向介面的定義 比起物件系統,考慮因素更類似於網路通訊協定。適用對象 例如,如要設計優質的通訊協定,您必須考量頻寬 延遲時間和流量控制你也應考慮到 而是結合了邏輯群組 (FIFO) 和 FIFO 將通訊協定分為兩個較小的通訊協定 針對透過兩個不同通訊協定發出的要求,可以按照 互相交流
著重於類型
設計 FIDL 通訊協定時,建議您先設計資料 您的通訊協定將採用的結構例如 FIDL 通訊協定 網路可能包含各種 IP 類型的資料結構 和圖形的 FIDL 通訊協定都可能包含資料 各種幾何概念的結構您應該會看到 類型名稱,並大致瞭解通訊協定操縱的概念 以及這些概念的結構
語言中立性
許多不同的語言都有 FIDL 後端。請盡量避免 對任何特定目標語言而言,過度特殊的 FIDL 定義。 隨著時間的推移,您的 FIDL 通訊協定可能會有許多不同的語言 甚至目前可能尚未支援的部分語言。FIDL 是 黏合系統,讓 Fuchsia 能夠支援 和執行階段如果您對自己喜愛的語言特別感興趣, 破壞核心價值主張。
序數
通訊協定包含多種方法,每個方法都會由系統自動指派 不重複的 64 位元識別碼,稱為「序數」伺服器會使用序數值 ,判斷應分派的通訊協定方法。
編譯器會雜湊處理程式庫、通訊協定和
方法名稱。在極少數情況下,同一通訊協定中的常態可能會相互衝突。如果
這時,您可以使用 Selector
屬性來變更
方法,讓編譯器能夠進行雜湊。以下範例將使用
名稱「C」而不是方法名稱「B」來計算雜湊值:
protocol A {
@selector("C")
B(struct {
s string;
b bool;
});
};
選取器也可用來維持與電線的回溯相容性 格式。
診斷
有時候,為了偵錯或診斷 計畫。這些資料的形式是以統計資料和指標的形式 (例如錯誤數量、呼叫次數、 等)、適用於開發、元件的健康狀態或類似資訊。
您可能會想在正式環境的測試通訊協定或偵錯方法中公開這項資訊 因此效能相當卓越但 Fuchsia 則另外提供一種會顯示這類資訊的機制: 檢查檢查這些事項,以利判斷公開方式 這類資料如需使用檢查功能,不應使用 FIDL 方法/通訊協定 公開實用的程式診斷資訊,協助開發人員在測試中偵錯,而程式會使用該程式 工具,或透過當機報告或指標擷取 (只要其他程式未使用) 以便做出執行階段決策
如果執行階段會根據其他人的診斷資訊做出決策,則應使用 FIDL 計畫。程式之間的通訊一律不應使用檢查,這是最可行的系統 。
判斷是否使用檢查或 FIDL 的啟發式做法如下:
其他程式是否在實際工作環境中使用這些資料?
- 是:使用 FIDL。
當機報告會使用資料,還是用於指標?
- 是:使用「檢查」功能。
測試或開發人員工具會使用這些資料嗎?是否會用於實際工作環境?
- 是:使用 FIDL。
- 否:請擇一使用。
程式庫結構
將 FIDL 宣告分組至 FIDL 程式庫有兩個特定目標:
- 協助 FIDL 開發人員 (使用 FIDL 程式庫) 瀏覽 API 途徑。
- 為 FIDL 中的階層範圍 FIDL 宣告提供結構 程式庫
請仔細思考如何將類型和通訊協定定義區分為 程式庫將這些定義拆解為程式庫的做法非常龐大 FIDL 資料庫是 您和通訊協定的依附元件與分佈情形
FIDL 編譯器要求程式庫之間的依附元件圖表是 DAG。 這表示您無法跨程式庫邊界建立循環依附元件。 不過,您可以在程式庫中建立 (部分) 循環依附元件。
如要決定是否要將程式庫分解為較小的程式庫,請考慮使用 下列問題:
確認資料庫的客戶會細分為 想要使用程式庫中的部分功能或宣告嗎?如果 因此,請考慮將程式庫拆分為分別指定 角色。
程式庫是否對應於某個產業概念 (通常 瞭解自己的結構?在這種情況下,建議您調整程式庫結構,使其符合 業界標準架構舉例來說,藍牙會歸類為 運用
fuchsia.bluetooth.le
和fuchsia.bluetooth.gatt
來達成前述目的 業界普遍理解了這些概念。同樣地fuchsia.net.http
會對應至業界標準 HTTP 網路 因此效能相當卓越還有許多其他程式庫依附這個程式庫嗎?如果是的話,請確認這些 連入依附元件確實需要依附整個程式庫 有一件事可能會排除 程式庫,接收大部分傳入的依附元件。
理想情況下,我們會為 Fuchsia 整體產生 FIDL 程式庫結構 則是全球最佳選擇然而,剛果的法律指出 設計系統 [...] 僅限於製作 這些機構的通訊結構。」我們應該 要打掃康威定律需要投入較多時間
存取權控管是以通訊協定精細程度分類
在決定定義通訊協定的程式庫時,不要考量這一點 存取權控管注意事項一般來說,存取權控管是 通訊協定精細程度定義通訊協定的程式庫沒有任何有意義 無法用來判斷是否可以 允許或拒絕要求
舉例來說,程序可能會存取 fuchsia.logger.LogSink
,或是程序
指定的用戶端結尾是否為 fuchsia.media.StreamSource
通訊協定的用戶端。但 FIDL
不設計,無法用於表達 fuchsia.logger
的存取權
,或禁止存取 fuchsia.ldsvc
程式庫
fuchsia
命名空間
平台來源樹狀結構中定義的 FIDL 程式庫 (指
fuchsia.googlesource.com) 必須在
fuchsia
頂層命名空間 (例如fuchsia.ui
)。
為 true:
- 這個程式庫會定義 FIDL 語言本身或其內容
合格測試套件,在此情況下,頂層命名空間必須為
fidl
。 - 這個程式庫僅供內部測試使用,並未包含在 SDK 中
或在實際工作環境中,頂層命名空間必須是
test
。
頂層命名空間 fuchsia
命名空間中的 FIDL 程式庫強行
建議只使用四個元素,例如 fuchsia.<api-namespace>
、
fuchsia.<api-namespace>.<name>
或fuchsia.<api-namespace>.<name>.<subname>
。
由 API 委員會 (API Council) 協助選擇適當的 api-namespace
成員。
例如:平台來源樹狀結構中,針對用途定義的 FIDL 程式庫
向應用程式公開硬體功能的能力都必須位於
fuchsia.hardware
命名空間。例如用於公開乙太網路的通訊協定
裝置的名稱可能是 fuchsia.hardware.ethernet.Device
。較高層級
以這些 FIDL 通訊協定為基礎,
fuchsia.hardware
命名空間。舉例來說,更適合在聯播網上
通訊協定低於 fuchsia.net
,而非 fuchsia.hardware
。
避免建立過多巢狀結構
偏好包含三個元件的程式庫名稱 (例如 fuchsia.hardware.network
),
避免程式庫名稱含有超過 4 個元件 (例如
fuchsia.apps.foo.bar.baz
)。如果您使用超過四個元件,請
都可能有特定的原因
程式庫依附元件
建議您在程式庫中引入更具體的
較不明確的程式庫命名例如:fuchsia.foo.bar
可能依附於 fuchsia.foo
,但 fuchsia.foo
不應依附
fuchsia.foo.bar
。這個模式會隨著時間的推移而提高
可以新增更多程式庫名稱,但名稱有限
較不具體名稱的程式庫數量
可匯入程式庫
擴展到將 FIDL 宣告歸入 FIDL 的第二個目標
我們預計改進 FIDL 以提供瀏覽權限規則,
元素可用於匯入程式庫 (「子項程式庫」),例如public
或
private
修飾符。
internal
程式庫元件名稱旨在特別處理
會指出瀏覽權限規則的本機限制。例如公開的
可能只有 fuchsia.net.dhcp.internal.foo
程式庫中的宣告內容可供顯示
其父項 fuchsia.net.dhcp
或其同層級,例如
fuchsia.net.dhcp.internal.bar
。
使用多字詞程式庫元件
如果程式庫名稱的元件會彙整多個字詞 (例如
fuchsia.modular.storymodel
)。
如果圖書館,圖書館作者可以一次結合多個字詞
名稱違反巢狀規則,或是兩個字詞的優先順序
另一點則是思考程式庫的位置
版本字串
如果您需要管理程式庫,則必須使用單一版本號碼
後置字串,例如fuchsia.io2
或 fuchsia.something.something4.
版本號碼
不應包含多個部分,例如fuchsia.io2.1
是不可接受的,
而不是 fuchsia.io3
。任何程式庫元件都可能有版本,
強烈建議不要使用多個版本化元件,例如
fuchsia.hardware.cpu2.ctrl
,但不是 fuchsia.hardware.cpu2.ctrl4
。
版本號碼只能指出程式庫較新版本,而非
明顯不同的網域以 fuchsia.input
程式庫為例
用於較低層級的裝置處理作業,而 fuchsia.ui.input{2,3}
則使用
能與風景優美的軟體元件互動
轉譯 UI只著重版本管理,這樣的做法會比較清楚
fuchsia.ui.scenic.input
和 fuchsia.ui.scenic.input2
以便區分
其他 fuchsia.input
網域。
類型
如同「一般建議」一節所述請務必特別注意 系統會使用您在通訊協定定義中所用的類型
保持一致
同一個概念請使用一致的類型。例如使用 uint32
或
特定概念在程式庫中一致的 int32
。如果發生以下情況:
為概念建立 struct
,以一致的方式使用該結構
代表概念
在理想情況下,型別也會在所有程式庫邊界之間一致。
查看相關程式庫,瞭解類似概念並保持一致
程式庫如果程式庫之間有許多共通概念,請考慮
將這些概念的類型定義分解為通用程式庫。適用對象
舉例來說,fuchsia.mem
和 fuchsia.math
包含許多
分別代表記憶和數學概念。
偏好語意類型
建立結構體以命名常用概念,即使概念可能是 模型舉例來說,IPv4 位址 屬於網路程式庫的概念,應使用結構體命名 進而以基元表示:
type Ipv4Address = struct {
octets array<uint8, 4>;
};
在著重效能的目標語言中,結構以行表示, 降低使用結構體為重要概念命名的成本。
zx.Time
的時間基準定義明確
zx.Time
類型只會以單調的方式測量
從單一特徵函數
特定裝置的時間範圍。
使用 zx.Time
會假設這個時間基數,因此不用拼寫
。
謹慎使用匿名類型
匿名型別非常實用,非常適合用來在說明文字中清楚地描述 API。我們要用 如果事先知道 已命名類型的子元素固然與該具名類型相關聯,因此 在所含內容以外的地方使用時,這個錯誤可能很實用或沒有幫助 已命名的容器映像檔
舉例來說,我們會使用聯集變化版本來匯總幾個項目, 聯集變體本身使用起來相當少見 限定詞的先顯內容,只有在其特定的情況下, 相關單位會如何運用資料,並讓他們覺得自己 獲得充分告知,且能夠針對該使用方式表示同意因此,針對聯集變體使用匿名型別是適當的做法, 建議
在理想情況下,型別應該一對一對應至 API 的重要概念,而不是兩個
應有相同的定義不過,有時可以同時達成
尤其是在類型命名的情況下 這樣做
不同的概念1,與用做 API 介面之外還別具意義
元素。考慮使用名為 type EntityId = struct { id
uint64; };
和 type OtherEntityId = struct { id uint64; };
的執行個體 ID
但採用的類型定義不同,差別只在於名稱定義。
使用匿名型別會建立多種類型,且彼此不相容 其他。因此,如果使用多個匿名類型來表示同一個 這個概念會造成過度複雜的 API,並避免一般處理 多數譯文語言
因此,使用匿名類型時,您必須避免使用多種匿名類型 代表同一個概念舉例來說,您不得在 隨著 API 發展可能促成多種匿名類型 代表同一個概念
建議使用虛擬記憶體物件 (VMO)
虛擬記憶體物件 (VMO) 是一種核心物件,代表連續的
虛擬記憶體區域和邏輯大小使用此類型轉移
FIDL 訊息中的記憶體,然後使用 ZX_PROP_VMO_CONTENT_SIZE
屬性追蹤
物件包含的資料量。
指定向量和字串的邊界
所有 vector
和 string
宣告都應指定長度限制。
聲明通常可分為以下兩種類別:
- 資料本身設有限制。例如,包含
檔案系統名稱元件的長度不得超過
fuchsia.io.MAX_FILENAME
。 - 「越多越好」以外的限制沒有任何限制。在這些情況下,您必須
應使用內建常數
MAX
。
使用 MAX
時,請先評估訊息接收端是否會
真的需要處理長度不限的序列
序列代表濫用
請注意,所有宣告都會隱含的訊息數量上限為訊息上限
傳送超過 zx::channel
時的長度。如果有
任意長度的序列,只使用 MAX
可能無法處理這些用途
因為嘗試提供極長序列的客戶可能會遇到
訊息長度上限。
為處理具有任意大型序列的用途,請考慮將 由其中一個分頁模式組成多個訊息 或考慮將資料移出訊息本身, 複製到 VMO 中
FIDL 方案:大小限制
FIDL 向量和字串可能有指定限制的大小限制 代表該類型可包含的成員數量就向量而言 儲存在向量中的元素數量,對於字串則是指 字串內含的位元組數。
強烈建議使用大小限制,因為這會設定上限 無限制的型別。
鍵/值儲存庫的實用作業是依序疊代,也就是 指定鍵,即可傳回 (通常為分頁) 出現在 。
原因
在 FIDL 中,最好的做法是使用疊代器,而疊代器一般會實作為 執行這項疊代作業的不同通訊協定使用不同的 通訊協定,因此獨立管道有許多優點,包括 將透過應用程式執行的其他作業之疊代提取要求解交錯 主要通訊協定。
通訊協定 P
管道連線的用戶端和伺服器端可
以 FIDL 資料類型表示,即 client_end:P
和 server_end:P
。
。這些類型統稱為「通訊協定結束」。
代表將 FIDL 用戶端與其連線的另一個 (非 @discoverable
) 方法
對應的伺服器:複寫現有的 FIDL 連線!
通訊協定結束是一般 FIDL 概念的特定執行個體:資源 類型。資源類型是為了包含需要的 FIDL 控制代碼 類型的使用方式額外限制。類型必須一律為 因為基礎資源會由其他能力管理工具進行中介 (通常是 Zircon 核心)。透過簡單的記憶體內複製這類資源 答案是不可能的,如果沒有經理,就不可能。為了防止重複 FIDL 中的所有資源類型一律僅限移動。
最後,Iterator
通訊協定本身的 Get()
方法會使用
傳回酬載的大小限制。這會限制
以單一提取式傳輸流量進行傳輸,允許以一定程度的資源用量
控管功能也會建立自然的分頁界線:而非大型傾印
因此伺服器只需準備小批資料
時間。
實作
FIDL、CML 和領域介面定義如下:
FIDL
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library examples.keyvaluestore.additerator; /// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That /// is, it must start with a letter, end with a letter or number, contain only letters, numbers, /// periods, and slashes, and be between 4 and 64 characters long. type Item = struct { key string:128; value vector<byte>:64000; }; /// An enumeration of things that may go wrong when trying to write a value to our store. type WriteError = flexible enum { UNKNOWN = 0; INVALID_KEY = 1; INVALID_VALUE = 2; ALREADY_EXISTS = 3; }; /// An enumeration of things that may go wrong when trying to create an iterator. type IterateConnectionError = flexible enum { /// The starting key was not found. UNKNOWN_START_AT = 1; }; /// A key-value store which supports insertion and iteration. @discoverable open protocol Store { /// Writes an item to the store. flexible WriteItem(struct { attempt Item; }) -> () error WriteError; /// Iterates over the items in the store, using lexicographic ordering over the keys. /// /// The [`iterator`] is [pipelined][pipelining] to the server, such that the client can /// immediately send requests over the new connection. /// /// [pipelining]: https://fuchsia.dev/fuchsia-src/development/api/fidl?hl=en#request-pipelining flexible Iterate(resource struct { /// If present, requests to start the iteration at this item. starting_at string:<128, optional>; /// The [`Iterator`] server endpoint. The client creates both ends of the channel and /// retains the `client_end` locally to use for pulling iteration pages, while sending the /// `server_end` off to be fulfilled by the server. iterator server_end:Iterator; }) -> () error IterateConnectionError; }; /// An iterator for the key-value store. Note that this protocol makes no guarantee of atomicity - /// the values may change between pulls from the iterator. Unlike the `Store` protocol above, this /// protocol is not `@discoverable`: it is not independently published by the component that /// implements it, but rather must have one of its two protocol ends transmitted over an existing /// FIDL connection. /// /// As is often the case with iterators, the client indicates that they are done with an instance of /// the iterator by simply closing their end of the connection. /// /// Since the iterator is associated only with the Iterate method, it is declared as closed rather /// than open. This is because changes to how iteration works are more likely to require replacing /// the Iterate method completely (which is fine because that method is flexible) rather than /// evolving the Iterator protocol. closed protocol Iterator { /// Gets the next batch of keys. /// /// The client pulls keys rather than having the server proactively push them, to implement /// [flow control][flow-control] over the messages. /// /// [flow-control]: /// https://fuchsia.dev/fuchsia-src/development/api/fidl?hl=en#prefer_pull_to_push strict Get() -> (struct { /// A list of keys. If the iterator has reached the end of iteration, the list will be /// empty. The client is expected to then close the connection. entries vector<string:128>:10; }); };
CML
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/client_bin", }, use: [ { protocol: "examples.keyvaluestore.additerator.Store" }, ], config: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A key to iterate from, after all items in `write_items` have been written. iterate_from: { type: "string", max_size: 64, }, }, }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.keyvaluestore.additerator.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.additerator.Store", from: "self", }, ], }
領域
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.keyvaluestore.additerator.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
用戶端和伺服器實作項目現在可以使用任何支援的語言編寫:
Rust
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{Context as _, Error}; use config::Config; use fuchsia_component::client::connect_to_protocol; use std::{thread, time}; use fidl::endpoints::create_proxy; use fidl_examples_keyvaluestore_additerator::{Item, IteratorMarker, StoreMarker}; use futures::join; #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Load the structured config values passed to this component at startup. let config = Config::take_from_startup_handle(); // Use the Component Framework runtime to connect to the newly spun up server component. We wrap // our retained client end in a proxy object that lets us asynchronously send `Store` requests // across the channel. let store = connect_to_protocol::<StoreMarker>()?; println!("Outgoing connection enabled"); // This client's structured config has one parameter, a vector of strings. Each string is the // path to a resource file whose filename is a key and whose contents are a value. We iterate // over them and try to write each key-value pair to the remote store. for key in config.write_items.into_iter() { let path = format!("/pkg/data/{}.txt", key); let value = std::fs::read_to_string(path.clone()) .with_context(|| format!("Failed to load {path}"))?; match store.write_item(&Item { key: key, value: value.into_bytes() }).await? { Ok(_) => println!("WriteItem Success"), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } } if !config.iterate_from.is_empty() { // This helper creates a channel, and returns two protocol ends: the `client_end` is already // conveniently bound to the correct FIDL protocol, `Iterator`, while the `server_end` is // unbound and ready to be sent over the wire. let (iterator, server_end) = create_proxy::<IteratorMarker>()?; // There is no need to wait for the iterator to connect before sending the first `Get()` // request - since we already hold the `client_end` of the connection, we can start queuing // requests on it immediately. let connect_to_iterator = store.iterate(Some(config.iterate_from.as_str()), server_end); let first_get = iterator.get(); // Wait until both the connection and the first request resolve - an error in either case // triggers an immediate resolution of the combined future. let (connection, first_page) = join!(connect_to_iterator, first_get); // Handle any connection error. If this has occurred, it is impossible for the first `Get()` // call to have resolved successfully, so check this error first. if let Err(err) = connection.context("Could not connect to Iterator")? { println!("Iterator Connection Error: {}", err.into_primitive()); } else { println!("Iterator Connection Success"); // Consecutively repeat the `Get()` request if the previous response was not empty. let mut entries = first_page.context("Could not get page from Iterator")?; while !&entries.is_empty() { for entry in entries.iter() { println!("Iterator Entry: {}", entry); } entries = iterator.get().await.context("Could not get page from Iterator")?; } } } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. thread::sleep(time::Duration::from_secs(2)); Ok(()) }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{Context as _, Error}; use fuchsia_component::server::ServiceFs; use futures::prelude::*; use lazy_static::lazy_static; use regex::Regex; use fidl_examples_keyvaluestore_additerator::{ Item, IterateConnectionError, IteratorRequest, IteratorRequestStream, StoreRequest, StoreRequestStream, WriteError, }; use fuchsia_async as fasync; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::ops::Bound::*; use std::sync::{Arc, Mutex}; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile"); } /// Handler for the `WriteItem` method. fn write_item(store: &mut BTreeMap<String, Vec<u8>>, attempt: Item) -> Result<(), WriteError> { // Validate the key. if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) { println!("Write error: INVALID_KEY, For key: {}", attempt.key); return Err(WriteError::InvalidKey); } // Validate the value. if attempt.value.is_empty() { println!("Write error: INVALID_VALUE, For key: {}", attempt.key); return Err(WriteError::InvalidValue); } // Write to the store, validating that the key did not already exist. match store.entry(attempt.key) { Entry::Occupied(entry) => { println!("Write error: ALREADY_EXISTS, For key: {}", entry.key()); Err(WriteError::AlreadyExists) } Entry::Vacant(entry) => { println!("Wrote value at key: {}", entry.key()); entry.insert(attempt.value); Ok(()) } } } /// Handler for the `Iterate` method, which deals with validating that the requested start position /// exists, and then sets up the asynchronous side channel for the actual iteration to occur over. fn iterate( store: Arc<Mutex<BTreeMap<String, Vec<u8>>>>, starting_at: Option<String>, stream: IteratorRequestStream, ) -> Result<(), IterateConnectionError> { // Validate that the starting key, if supplied, actually exists. if let Some(start_key) = starting_at.clone() { if !store.lock().unwrap().contains_key(&start_key) { return Err(IterateConnectionError::UnknownStartAt); } } // Spawn a detached task. This allows the method call to return while the iteration continues in // a separate, unawaited task. fasync::Task::spawn(async move { // Serve the iteration requests. Note that access to the underlying store is behind a // contended `Mutex`, meaning that the iteration is not atomic: page contents could shift, // change, or disappear entirely between `Get()` requests. stream .map(|result| result.context("failed request")) .try_fold( match starting_at { Some(start_key) => Included(start_key), None => Unbounded, }, |mut lower_bound, request| async { match request { IteratorRequest::Get { responder } => { println!("Iterator page request received"); // The `page_size` should be kept in sync with the size constraint on // the iterator's response, as defined in the FIDL protocol. static PAGE_SIZE: usize = 10; // An iterator, beginning at `lower_bound` and tracking the pagination's // progress through iteration as each page is pulled by a client-sent // `Get()` request. let held_store = store.lock().unwrap(); let mut entries = held_store.range((lower_bound.clone(), Unbounded)); let mut current_page = vec![]; for _ in 0..PAGE_SIZE { match entries.next() { Some(entry) => { current_page.push(entry.0.clone()); } None => break, } } // Update the `lower_bound` - either inclusive of the next item in the // iteration, or exclusive of the last seen item if the iteration has // finished. This `lower_bound` will be passed to the next request // handler as its starting point. lower_bound = match entries.next() { Some(next) => Included(next.0.clone()), None => match current_page.last() { Some(tail) => Excluded(tail.clone()), None => lower_bound, }, }; // Send the page. At the end of this scope, the `held_store` lock gets // dropped, and therefore released. responder.send(¤t_page).context("error sending reply")?; println!("Iterator page sent"); } } Ok(lower_bound) }, ) .await .ok(); }) .detach(); Ok(()) } /// Creates a new instance of the server. Each server has its own bespoke, per-connection instance /// of the key-value store. async fn run_server(stream: StoreRequestStream) -> Result<(), Error> { // Create a new in-memory key-value store. The store will live for the lifetime of the // connection between the server and this particular client. // // Note that we now use an `Arc<Mutex<BTreeMap>>`, replacing the previous `RefCell<HashMap>`. // The `BTreeMap` is used because we want an ordered map, to better facilitate iteration. The // `Arc<Mutex<...>>` is used because there are now multiple async tasks accessing the: one main // task which handles communication over the protocol, and one additional task per iterator // protocol. `Arc<Mutex<...>>` is the simplest way to synchronize concurrent access between // these racing tasks. let store = &Arc::new(Mutex::new(BTreeMap::<String, Vec<u8>>::new())); // Serve all requests on the protocol sequentially - a new request is not handled until its // predecessor has been processed. stream .map(|result| result.context("failed request")) .try_for_each(|request| async { // Match based on the method being invoked. match request { StoreRequest::WriteItem { attempt, responder } => { println!("WriteItem request received"); // The `responder` parameter is a special struct that manages the outgoing reply // to this method call. Calling `send` on the responder exactly once will send // the reply. responder .send(write_item(&mut store.clone().lock().unwrap(), attempt)) .context("error sending reply")?; println!("WriteItem response sent"); } StoreRequest::Iterate { starting_at, iterator, responder } => { println!("Iterate request received"); // The `iterate` handler does a quick check to see that the request is valid, // then spins up a separate worker task to serve the newly minted `Iterator` // protocol instance, allowing this call to return immediately and continue the // request stream with other work. responder .send(iterate(store.clone(), starting_at, iterator.into_stream()?)) .context("error sending reply")?; println!("Iterate response sent"); } // StoreRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }) .await } // A helper enum that allows us to treat a `Store` service instance as a value. enum IncomingService { Store(StoreRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Store` protocol - this will allow the client to see the // server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Store); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++ (自然)
用戶端
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
伺服器
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++ (有線)
用戶端
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
伺服器
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
用戶端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
伺服器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
字串編碼、字串內容與長度範圍
FIDL string
的編碼方式為 UTF-8,
使用 1、2、3 或 4 個位元組的可變寬編碼
Unicode 碼點。
繫結會對字串強制執行有效的 UTF-8,因此字串不會 適用於任意二進位資料詳情請見 我應該使用字串還是向量?
由於長度繫結宣告的目的,在於
可計算的 FIDL 訊息總位元組大小上限,string
個邊界
請指定該欄位的「位元組數」上限。為了安全起見,
通常會希望將預算設為 (4 bytes · code points in
string)
。(如果您確定文字內容只使用程式碼
位於單位元組 ASCII 範圍內,就像電話號碼或點數
但每個碼點只要 1 個位元組即可)。
字串中有幾個碼點?這個問題可能很複雜 尤其是在使用者產生的字串內容方面 必須是 Unicode 碼點與目標 使用者可能會誤認為「字元」
例如:
á
會顯示為單一使用者感知的「字元」 碼點:
1. LATIN SMALL LETTER A (U+0061)
2. COMBINING ACUTE ACCENT (U+0301)
在 Unicode 術語中,這類使用者感知的「字元」稱為 圖表叢集。
單一圖表叢集可包含任意數量的碼點。您可以考慮使用 以下加長版示例:
á🇨🇦b👮🏽♀️
如果您的系統和字型支援,畫面上應會顯示四個圖形叢集 以上:
1. 'a' with acute accent
2. emoji of Canadian flag
3. 'b'
4. emoji of a female police officer with a medium skin tone
這四個圖譜叢集會編碼為十個程式碼點:
1. LATIN SMALL LETTER A (U+0061)
2. COMBINING ACUTE ACCENT (U+0301)
3. REGIONAL INDICATOR SYMBOL LETTER C (U+1F1E8)
4. REGIONAL INDICATOR SYMBOL LETTER A (U+1F1E6)
5. LATIN SMALL LETTER B (U+0062)
6. POLICE OFFICER (U+1F46E)
7. EMOJI MODIFIER FITZPATRICK TYPE-4 (U+1F3FD)
8. ZERO WIDTH JOINER (U+200D)
9. FEMALE SIGN (U+2640)
10. VARIATION SELECTOR-16 (U+FE0F)
在 UTF-8 中,這個字串會佔用 28 個位元組。
在這個範例中,如果應用程式的 UI 顯示了
文字輸入框,允許「N」任意圖譜叢集 (使用者的想法
並打算將這些使用者輸入的字串
FIDL,您必須在4·N
FIDL string
欄位。
那麼多該有多好?視資料而定。如果您正在處理 相當限制的用途 (例如人為人姓名、郵寄地址、信用卡 數字) 時,您可以假設每個石墨色叢集各有 1 到 2 個碼點。如果 您正在建構一款即時通訊用戶端,並在用戶端使用表情符號功能,每個 API 4 到 5 個程式碼點 石墨色星團可能更安全。無論如何,您的輸入驗證 UI 都應 提供清楚的視覺回饋,避免使用者因用完預算而感到驚訝 您可以啟用「攝影棚效果」選項
整數類型
請選取適合您用途的整數類型,並保持一致
加以運用如果值最適合視為資料位元組,請使用 byte
。
如果負值沒有意義,請使用無正負號的類型。原則上,
如果不確定,請為小數量使用 32 位元值,在
規模較大的政策
如果可以提供更多狀態,請避免使用布林值
新增布林值欄位時,如果欄位可以
,以代表日後的其他狀態。例如布林值
is_gif
欄位更適合用來表示
type FileType = strict enum {
UNKNOWN = 0;
GIF = 1;
};
視需要使用 JPEG = 2
擴充列舉。
該如何表示錯誤?
請依據用途選取適當的錯誤類型,並保持一致 即可。
使用 error
語法清楚說明並傳達
並利用量身訂做的目標語言
繫結。
(使用選填的值但發生錯誤) 列舉模式已淘汰)。
使用錯誤語法
方法可採用選用的 error <type>
指定碼來表明
傳回值,或發生錯誤並產生 <type>
。範例如下:
// Only erroneous statuses are listed
type MyErrorCode = strict enum {
MISSING_FOO = 1; // avoid using 0
NO_BAR = 2;
};
protocol Frobinator {
Frobinate() -> (struct {
value SuccessValue;
}) error MyErrorCode;
};
使用這個模式時,您可以使用 int32
、uint32
或列舉
代表傳回的錯誤類型。在大多數情況下,傳回一個
列舉是建議採用的做法。
建議您為所有通訊協定的所有方法設定單一錯誤類型。
偏好網域特定列舉
定義及控制網域時,請使用用途建構的列舉錯誤類型。適用對象
例如,在建構通訊協定時定義列舉,並傳遞
錯誤的語意是唯一的設計限制。如
enum 區段,則最好避免使用 0
值。
在某些情況下,可能適合先使用空白的彈性列舉:
type MyEmptyErrorCode = flexible enum {};
protocol Frobinator2 {
Frobinate() -> (struct {
value SuccessValue;
}) error MyEmptyErrorCode;
BadFrobinate() -> (struct {
value SuccessValue;
}) error flexible enum {}; // avoid anonymous enum
};
彈性列舉有預設的不明成員。一個 因此是空白的彈性列舉,因此是類型預留位置,提供自訂用途 創新能力使用這種模式時,建議您定義 通訊協定 (或程式庫) 中的多種方法重複使用的類型, 而非使用匿名列舉使用匿名列舉即可建立 但彼此並不相容,因此會導致 的複雜 API,避免大多數目標語言發生一般錯誤處理作業。
如要遵循定義明確的網域,請使用網域專屬的列舉錯誤類型 規格 (假設 HTTP 錯誤代碼),而列舉則是符合人體工學 ,表示規格所指定原始值。
具體而言,使用 zx.Status
類型處理與核心物件相關的錯誤,或
IO。舉例來說,fuchsia.process
會使用 zx.Status
,因為程式庫是
而且大多擔心會操控核心物件再舉一個例子
fuchsia.io
會廣泛使用 zx.Status
,因為程式庫擔心
IO。
使用選用值搭配錯誤列舉
在過去,定義方法時以 這兩個代碼,一個自選值和錯誤代碼。請參閱以下範例:
type MyStatusCode = strict enum {
OK = 0; // The success value should be 0,
MISSING_FOO = 1; // with erroneous status next.
NO_BAR = 2;
};
protocol Frobinator3 {
Frobinate() -> (struct {
value box<SuccessValue>;
err MyStatusCode;
});
};
但此模式現已淘汰,並改用 error 語法: 在信封中內嵌較小的值和低階值,已過時 對聯集的支援現在已普及。
避免訊息和說明有誤
在某些特殊情況下,通訊協定可能會針對
status
或列舉值以外的錯誤 (如果潛在錯誤範圍)
條件龐大且描述明確的錯誤訊息,有助於解決
用戶端。不過,加入字串邀請時仍然有困難。例如:
用戶端可能會嘗試剖析字串來瞭解情況,
字串的確切格式會成為通訊協定的一部分,
尤其是在字串是
本地化。
安全性注意事項: 同樣地,向用戶端回報堆疊追蹤或例外狀況訊息 無意間洩漏保密資訊。
本地化字串和錯誤訊息
如要建構做為 UI 後端的服務,請使用結構化 輸入訊息,並將算繪到 UI 層。
如果所有訊息都很簡單且未經參數化,請使用 enum
來回報錯誤
報表和一般 UI 字串如需取得更詳細的訊息,含參數
例如名稱、數字和地點,使用 table
或 xunion
,以及傳遞
將參數視為字串或數值欄位
您可能會想生成英文訊息,並在服務中提供 然後以字串形式傳送至 UI,UI 只會收到字串,然後彈出 通知或錯誤對話方塊
不過,這個方法較簡單,有一些嚴重的缺點:
- 您的服務是否知道國家/地區使用哪種語言代碼 (語言和地區) 使用這項服務?您必須在每次要求中傳遞語言代碼 (請參閱 範例),或追蹤每個連線的狀態 用戶端,並以正確的語言提供訊息。
- 服務的開發環境是否支援
本地化?如果您是在 C++ 中編寫程式碼,即可輕鬆存取
ICU 程式庫和
雖然
MessageFormat
,但如果您使用的是 Rust,則目前系統大多支援程式庫 - 任何錯誤訊息中都必須包含已知有哪些參數 而非服務嗎?
- 您的服務是否只提供單一 UI 實作項目?這項服務是否 您知道 UI 必須顯示多少空間嗎?
- 錯誤僅會以文字顯示嗎?您可能也需要使用錯誤相關快訊圖示 音效或文字轉語音提示
- 使用者介面運作期間,使用者是否能變更顯示語言代碼?如果 在這種情況下,預先本地化的字串可能難以更新至新版本 語言代碼,特別是如果它們是某些非冪等作業的結果。
除非您正在建構緊密結合 若是單一 UI 實作,建議您不要公開向使用者顯示的 UI FIDL 服務中的字串。
我是否應該定義結構以封裝方法參數 (或回應)?
定義方法時,您必須決定是否傳遞參數 或者將參數封裝在結構體中精益求精 我們需要考量多項因素請思考以下問題來提供協助 引導決策:
是否有有意義的封裝界線?如果一組參數 可能只能以單元的形式傳遞這些文件 方法,建議您將這些參數封裝在結構體中。 (希望您在問題時 開始設計您的通訊協定,因為您遵循了「一般建議」 並且盡早聚焦於這些類型)。
除了呼叫的方法以外,結構體是否實用?如果 建議分開傳送參數。
您是否在許多方法中重複同一組參數?如果是, 不妨將這些參數歸入一或多個結構您可能會 再思考重複狀況是否指出這些參數 這些屬性代表通訊協定中的一些重要概念。
是否有許多選用或選用的參數 通常會有預設值?如果是,請考慮使用結構體來減少 樣板,適用於呼叫者
有一組一律為空值或非空值的參數 時間?如果有的話,請考慮將這些參數分組為可為空值的結構,以 強制對通訊協定本身執行不具變革性的影響舉例來說, 上方定義的
FrobinateResult
結構包含一律為空值的值 當error
不是MyError.OK
時,也會同時運作。
我該使用 string
還是 vector<byte>
?
在 FIDL 中,string
資料必須是有效的 UTF-8,也就是說字串可以代表
Unicode 碼點的序列,但無法代表任意二進位資料。於
而 vector<byte>
或 array<byte, N>
可以代表任意二進位檔
不代表萬國碼 (Unicode)
使用 string
處理文字資料:
使用
string
表示套件名稱,因為必須提供套件名稱 必須是有效的 UTF-8 字串 (含某些排除字元)。使用
string
表示套件中的檔案名稱 (因為檔案名稱的關係) 套件內的項目必須是有效的 UTF-8 字串 (有特定排除 字元)。使用
string
表示媒體轉碼器名稱,因為媒體轉碼器名稱 從有效 UTF-8 字串的固定詞彙中選取。使用
string
代表 HTTP 方法,因為 HTTP 方法由 固定選取一律有效的 UTF-8 字元。
使用 vector<byte>
或 array<byte, N>
處理小型非文字資料:
針對 HTTP 標頭欄位使用
vector<byte>
,因為 HTTP 標頭欄位不含 因此不得以 UTF-8 表示。請使用
array<byte, 6>
做為 MAC 位址,因為 MAC 位址是二進位資料。針對 UUID 使用
array<byte, 16>
,因為 UUID 是 (幾乎!) 任意二進位檔 資料。
針對 blob 使用共用記憶體基元:
- 請使用
zx.Handle:VMO
處理圖片和 (大) protobufs 讓資料完全緩衝處理起來才有意義 - 請將
zx.Handle:SOCKET
用於音訊和視訊串流,因為資料可能會傳入 或何時適合處理資料 廣告。
我該使用 vector
還是 array
?
vector
是可變長度的序列,以非線條的方式表示
線格式。array
是固定長度的序列,以內嵌方式表示
編碼。
使用 vector
做為長度不定的資料:
- 使用
vector
做為記錄訊息中的標記,因為記錄訊息介於 零和五個標記
使用 array
處理固定長度的資料:
- 如為 MAC 位址,請使用
array
,因為 MAC 位址一律為六位元組。
我該使用 struct
還是 table
?
結構體和資料表都代表具有多個已命名欄位的物件。 差別在於結構體採用線路格式的固定版面配置 而在未破壞二進位檔相容性的情況下無法修改。相對地 表格具有彈性的版面配置,這表示欄位「可以」 隨著時間新增至資料表,而且不會影響二進位檔相容性。
針對著重效能的通訊協定元素或通訊協定元素,使用結構體 避免日後出現變化舉例來說,您可以使用結構體 代表 MAC 位址,因為 MAC 位址的結構非常不大 日後變更。
如果通訊協定元素日後可能會變更,請使用表格。適用對象 例如,使用表格呈現相機裝置的中繼資料資訊 因為中繼資料中的欄位可能會隨著時間變動
如何表示常數?
表示常數有三種方式,具體取決於 常數:
- 使用
const
代表特殊值,例如 PI 或 MAX_NAME_LEN。 - 如果值是集合的元素 (例如重複),請使用
enum
媒體播放器模式:OFF、SINGLE_TRACK 或 ALL_TRACKS。 - 使用
bits
用於形成一組標記的常數,例如功能 介麵包括 WLAN、SYNTH 和 LOOPBACK。
const
如要以符號方式使用某個值,請使用 const
而不是每次都輸入值典型範例為 PI,這是
通常編碼為 const
,因為不必自行輸入
每次要使用這個值時:3.141592653589
。
或者,您可以在值可能會改變時使用 const
,但這需要
否則在整個過程中會以一致的方式使用字元數上限
可以在指定欄位中提供MAX_NAME_LEN)。變更者:
使用 const
,您就能集中處理該數字的定義,因此不會
因此整個程式碼中
最終都會出現不同的值
另一個選擇 const
的原因是,可同時用來限制
呼叫訊息,再用程式碼加入程式碼例如:
const MAX_BATCH_SIZE int32 = 128;
protocol Sender {
Emit(struct {
batch vector<uint8>:MAX_BATCH_SIZE;
});
};
然後,您可以在程式碼中使用常數 MAX_BATCH_SIZE
組合批次。
列舉
如果列舉值有繫結和控制,請使用列舉
Fuchsia 專案。舉例來說,Fuchsia 專案會定義指標事件
因此決定了 PointerEventPhase
列舉的值。
在某些情況下,即使 Fuchsia 專案本身,您仍應使用列舉 無法控制列舉值組合 (如果我們可合理預期 想註冊新值的人會將修補程式提交給 Fuchsia 來登錄其值舉例來說,紋理格式需要 Fuchsia 圖形驅動程式能理解 這表示新的紋理格式 由開發這些驅動程式的開發人員加入 格式是由圖形硬體供應商控制。以下為計數器範例 不要使用列舉來代表 HTTP 方法,因為我們無法合理預期 使用新的 HTTP 方法將修補程式提交至平台來源樹狀結構的人。
針對優先不受限的集合,如果 string
建議可動態擴充這個集合例如,使用 string
表示媒體轉碼器名稱,因為中介商或許可以
能以新的媒體轉碼器名稱合理命名。
如果一組列舉值是由外部實體控制,請使用
整數 (適當大小) 或 string
。舉例來說,您可以將
部分尺寸) 來表示 USB HID ID,因為整組 USB HID 是
ID 是由產業聯盟控管。同樣地,使用 string
表示 MIME 類型,因為 MIME 類型受到控制 (至少理論上)
由 IANA 註冊機構提供說明
我們建議開發人員盡可能避免使用 0
做為列舉值。
許多目標語言會使用 0
做為整數的預設值,因此可以
不易分辨 0
值是否為刻意設定,或
而是預設設定。舉例來說,
fuchsia.module.StoryState
定義了三個值:RUNNING
含有 1
值。
STOPPING
的值為 2
,STOPPED
值為 3
。
以下兩種情況適合使用 0
值:
- 列舉有自然預設值、初始或不明狀態;
- 列舉會定義 含錯誤列舉的選用值 。
位元
如果您的通訊協定有位元欄位,請使用 bits
值表示其值 (
詳情請參閱 RFC-0025
: "位元旗標")。
例如:
type InfoFeaturesHex = strict bits : uint32 {
WLAN = 0x00000001; // If present, this device represents WLAN hardware
SYNTH = 0x00000002; // If present, this device is synthetic (not backed by h/w)
LOOPBACK = 0x00000004; // If present, this device receives all messages it sends
};
這表示 InfoFeatures
位元欄位是由未簽署的 32 位元支援
然後定義要使用的三個位元。
您也可以使用 0b
表示以二進位格式表示的值 (而不是十六進位值)
標記法:
type InfoFeaturesBits = strict bits : uint32 {
WLAN = 0b00000001; // If present, this device represents WLAN hardware
SYNTH = 0b00000010; // If present, this device is synthetic (not backed by h/w)
LOOPBACK = 0b00000100; // If present, this device receives all messages it sends
};
這與先前的範例相同。
我該使用「resource
」嗎?
FIDL 編譯器會強制要求任何已包含
resource
會特別標示。
如果 flexible
類型不含資源,但可能會在未來,
您應預先新增 resource
修飾符,以免造成困難
轉換功能這種情況相當罕見:經驗顯示
訊息不含資源,而且在通訊協定中傳遞資源時,
並預先規劃
我應該在類型上使用 strict
還是 flexible
?
將類型標示為 flexible
即可處理資料
但建議用於目前 FIDL 結構定義的類型
日後可能會新增或移除成員 (例如設定、中繼資料或錯誤)。這項服務
一律可以在 strict
之間進行柔性轉換
和 flexible
代表現有類型
是 時尚 在類型允許時,一律指定此修飾符。Fuchsia 專案 會以 Linter 檢查強制執行這個樣式。
使用 strict
或 flexible
不會對效能造成任何重大影響。
處理權利
本節說明指派 Google Cloud 版權限制的最佳做法 FIDL 中的帳號代碼。
請參閱 FIDL 繫結規格 或 RFC-0028 詳細說明如何在繫結中使用權限。
如要進一步瞭解 Zircon 權利定義,請參閱「核心權限」。 FIDL 會使用 rights.fidl 解決權利限制問題。
一律指定帳號代碼的權利
所有帳號代碼皆應明確說明其本身的權利, 相關單位會如何運用資料,並讓他們覺得自己 獲得充分告知,且能夠針對該使用方式表示同意這項規定會迫使客戶預先決定哪些權利。 而不要根據觀察到的行為來做決定擁有明確權利 也有助於確保 API 介面的可稽核性。
使用接收者所需的最低權限
決定該提供哪些權利時,請盡量避免
實現所需的功能所需的權利。舉例來說
知道只需要 zx.Rights.READ
和 zx.Rights.WRITE
。
那麼只要指定這兩項權利即可
請勿根據推測需求添加權利。如果還需要加入權利 日後如要新增來源,只要先選取來源並新增即可 直到最後使用為止
節制使用 zx.Rights.SAME_RIGHTS
zx.Rights.SAME_RIGHTS
非常適合用於轉送帳號代碼的通訊協定
但在大部分情況下,您應使用一組特定的權利
。值得一提的原因是 zx.Rights.SAME_RIGHTS
會告訴
可略過權利檢查,因此會停用
帳號代碼或許能提供上述資訊。此外,「zx.Rights.SAME_RIGHTS
」擁有權利
動態設定;也就是說,處理程序可能會獲得較多或更大的權限
但真正需要的東西
值得注意的是,zx.Rights.SAME_RIGHTS
與預設值不同
為特定類型設定的權利,例如:zx.DEFAULT_CHANNEL_RIGHTS
。前者略過
後者需要指定物件類型的所有一般權利
。
優質設計模式
本節說明在許多 FIDL 中重複的優質設計模式 通訊協定
我應該在方法和事件上使用 strict
或 flexible
嗎?
將方法或事件標示為 flexible
即可
方便您在不同元件
因此某些元件認為
方法或事件,而其他通訊方法或事件則不存在。由於
通常建議您追求不斷進化的通訊協定
除非有充分理由,否則針對方法和事件選擇 flexible
strict
。
建立方法 flexible
並不會對單向方法或事件造成負擔。適用對象
雙向方法,選擇 flexible
時會增加少量額外負擔 (16 個位元組
更少) 回應訊息,並可能額外傳達給訊息的時間
編碼器-解碼器架構整體而言,雙向方法的彈性成本應微小
可能也不考慮所有使用情境
方法和事件必須對strict
正確設定通訊協定行為,也就是傳回
接收方回應的重點在於
應中止並關閉連線。
這個做法在設計前饋類神經網路 Dataflow考慮這個支援模式的記錄器通訊協定 ,您可以安全地處理含有個人識別資訊 (PII) 的記錄。使用 以資訊提供轉送模式新增紀錄,讓用戶端可啟動 將作業循序漸進,不必等待封包往返時間,然後直接清除 終止中待處理的作業
open protocol Logger {
flexible AddRecord(struct {
message string;
});
strict EnablePIIMode();
flexible DisablePIIMode();
flexible Flush() -> ();
};
這裡的所有方法都是 flexible
,但 EnablePIIMode
除外。請考慮
就會發生此情況:
AddRecord
:伺服器只是無法將資料新增至記錄輸出。 傳送應用程式的行為正常,但記錄記錄減少 很實用這種做法雖然不方便,但安全。EnablePIIMode
:伺服器無法啟用 PII 模式,因此可能會失敗 採取安全預防措施和洩漏 PII這是嚴重的問題,因此 最好在伺服器無法辨識這個方法時關閉管道。DisablePIIMode
:伺服器會採取不必要的安全預防措施, 不需要 PII 記錄的訊息即可。這對某些人來說可能很不便 但對系統來說是安全的。Flush
:伺服器無法按照要求清除記錄,也就是 雖說不定 但仍然安全
如果要設計這種通訊協定具有完全彈性,還有另一個方法就是
將 EnablePIIMode
設為雙向方法 (flexible EnablePIIMode() -> ();
),這樣即可
,用戶端可瞭解伺服器是否沒有 方法。請注意
因此能為客戶提供更多彈性如此一來
您可以選擇是否對伺服器無法識別
關閉連線或選擇不記錄 PII,EnablePIIMode
,
而使用 strict
則一律會自動關閉通訊協定。不過,這個
不會中斷前饋流程
請注意,嚴格程度是以寄件者為依據。假設您有幾個
第 1 版中的 strict A();
方法,然後在版本中將其變更為 flexible A();
,然後在版本 3 中刪除該資料。如果以第 1 版建立的用戶端嘗試呼叫
在以 3
版本建立的伺服器上的 A()
中,該方法會被視為嚴格
因為第 1 版的用戶端認為方法很嚴格,而位於
第 3 版
。
是 時尚 以便一律指定這個修飾符Fuchsia 專案採用 Linter 檢查。
我該使用 open
、ajar
或 closed
嗎?
將通訊協定標示為 open
後
方便您輕鬆處理移除方法或事件,並在不同元件
是用不同版本建構而成 因此,每個元件都有不同的
顯示有哪些方法和事件存在因為彈性
一般而言,建議選擇 open
通訊協定。
決定要使用 ajar
或 closed
時,應根據以下項目的預期限制:
包括通訊協定的演變使用 closed
或 ajar
無法防範通訊協定
不過,這會要求延長推出時間
為確保所有用戶端
和伺服器議定什麼方法的特性靈活使用 flexible
適用於新增及移除方法和事件,取決於
用戶端或伺服器
ajar
適用於使用動態饋給轉送的通訊協定
Dataflow,但預計只會有進化
只能使用單向方法舉例來說,這適用於撕裂
代表交易的唯一雙向通訊協定
方法為修訂作業,要求執行時必須嚴格遵循
交易可能會有所改變
ajar protocol BazTransaction {
flexible Add(resource struct {
bars vector<client_end:Bar>;
});
strict Commit() -> (struct {
args Args;
});
};
open protocol Foo3 {
flexible StartBazTransaction(resource struct {
transaction server_end:BazTransaction;
});
};
如果重要通訊協定是嚴重的方法,closed
就相當實用
導致關閉管道的問題,不會在
可能代表不良狀態對於
極不可能改變,或者至少要牽涉到
因此,發布週期極長,
原本就預計在推出週期中變更 strict
方法。
通訊協定要求管道
通訊協定要求是最普及且最廣泛使用的設計模式之一 直線路。系統不會傳回支援通訊協定的管道,而是 用戶端會傳送管道,並要求伺服器將實作項目繫結 專屬於該管道的通訊協定
// GOOD:
protocol GoodFoo {
GetBar(resource struct {
name string;
bar server_end:Bar;
});
};
// BAD:
protocol BadFoo {
GetBar(struct {
name string;
}) -> (resource struct {
bar client_end:Bar;
});
};
這個模式非常實用,因為用戶端不必等待往返作業
,再開始使用 Bar
通訊協定。不過用戶端可以將
立即傳送給「Bar
」的訊息。這些訊息會由核心進行緩衝處理
當 Bar
的實作繫結至通訊協定後,最終會進行處理
請求。相對地,如果伺服器傳回 Bar
通訊協定的執行個體
用戶端必須等到整個往返後,才能將訊息排入佇列
Bar
。
如果要求可能失敗,請考慮透過回覆來延長這類模式 說明作業是否成功:
protocol CodecProvider {
TryToCreateCodec(resource struct {
params CodecParams;
codec server_end:Codec;
}) -> (struct {
succeed bool;
});
};
為了處理失敗情況,用戶端會等候回應,並 動作。另一個做法是 伺服器在通訊協定開始時傳送的事件:
protocol Codec2 {
-> OnReady();
};
protocol CodecProvider2 {
TryToCreateCodec(resource struct {
params CodecParams;
codec server_end:Codec2;
});
};
為了處理失敗情況,用戶端會等待 OnReady
事件,
如果 Codec2
管道在活動開始前就關閉,則系統會採取其他動作。
不過,如果請求可能成功,則不論結果是否成功 可能有害,因為此信號可讓用戶端 通常應以相同方式處理不同的故障模式 舉例來說,用戶端應將失敗的服務視為 建立連線的方式與無法存取的服務相同 太大的影響不論是何種情況,服務都無法使用,用戶端也會 應該產生錯誤,或尋找其他方式來完成工作。
流量控制
核心會緩衝 FIDL 訊息。如果一個端點 訊息就會累積到 佔用記憶體,使系統更難復原。 反之,設計完善的通訊協定應該針對 符合耗用訊息的速率,稱為流量流 控管。
流量控制是一個廣泛又複雜的主題,也有許多有效 設計模式本節將探討一些較常用的流量控制 但並非詳盡無遺資料模式會按照 偏好設定。如果其中一種模式適用於特定用途 。但如果不是的話,通訊協定可以自由使用替代流量控制 下方未列出的機制。
偏好拉動
在未經過謹慎設計的情況下,伺服器會將資料推送至用戶端的通訊協定 通常無法控制流量最佳流量控制的其中一種方法是 以便用戶端從伺服器提取其中一個位址或範圍提取模型有 內建的流量控制設定。因為用戶端會自然限制發生連線的 伺服器會產生資料,避免遭受從 伺服器
使用等待中的等待功能延遲回應
如要實作提取式通訊協定,最簡單的方法就是「寄回回呼」使用 操作,請使用懸掛取得模式:
protocol FooProvider {
WatchFoo(struct {
args Args;
}) -> (resource struct {
foo client_end:Foo;
});
};
在這個模式中,用戶端會傳送 WatchFoo
訊息,但伺服器不會傳送
回覆郵件,直到有新資訊要傳送給客戶為止。用戶端耗用的資源
foo
並立即傳送一連串等待回應。每個用戶端和伺服器都會執行
一個資料項目一個工作單位,意味著兩者無法領先。
懸掛式取得模式在轉移的資料項目組合時可以正常運作 大小有限,伺服器端狀態簡單,但無法正常運作 在用戶端和伺服器需要同步處理工作時。
例如,伺服器可能會針對某些可變動的可變動項目實作懸掛取得模式
使用「骯髒」狀態 foo
每個用戶端的位元。解碼器會將這個位元初始化
是,請清除每個 WatchFoo
回應,並為每個 foo
變更進行設定。
伺服器只有在設定骯髒位元時,伺服器才會回應 WatchFoo
訊息。
透過確認功能限制推送作業
如要在使用推送功能的通訊協定中控制流量,其中一種方法是 「確認模式」,此時呼叫端會提供確認 回應呼叫端用於流量控制的回應。舉個例子 通用事件監聽器通訊協定:
protocol Listener {
OnBar(struct {
args Args;
}) -> ();
};
監聽器應在發生以下情況時,立即傳送空白的回應訊息:
接收 OnBar
訊息。回應不會向
呼叫。反之,回應允許呼叫端觀察
通話對象正在接收訊息呼叫端應調節速率
會根據受呼叫者的耗用速率產生訊息。適用對象
舉例來說,呼叫端可能只安排一封 (或固定數量) 訊息。
生效中 (即等待確認)。
FIDL 方案:確認模式
「確認模式」是簡易的方法控管方法 原本就是單向呼叫比起讓 方法 相反地,這個呼叫將轉換為雙向的通話,卻沒有回應。 別名為「確認」。這項確認的唯一理由是 表示已收到郵件的寄件備份,寄件者可以使用它 再做出決定
收取確認費用的費用會增加至管道聊天室。這個模式 如果用戶端等待應用程式回應, 再繼續進行下一次呼叫。
傳送非計量付費的單向呼叫會產生簡單的設計,不過 請留意,如果伺服器處理速度慢慢許多 該怎麼辦?舉例來說,用戶端可能會載入繪圖 是由文字檔中的數萬行所組成,試著傳送 依循順序如何減輕客戶壓力,避免 伺服器不堪負荷嗎?
使用確認模式,並透過單向呼叫 AddLine(...);
然後AddLine(...) -> ();
向客戶提供意見
這樣用戶端就能視需要節流輸出內容。在本
例如,用戶端等待回應後,再傳送下一個
訊息等候,但較複雜的設計仍可傳送訊息
而且只在他們更不常收到非同步 AAR 時,才進行節流
比預期中許多
首先,我們必須定義介面定義並測試控管工具。FIDL CML 和領域介面定義會設定一個 Scaffold 導入方式可以使用:
FIDL
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library examples.canvas.addlinemetered; /// A point in 2D space. type Point = struct { x int64; y int64; }; /// A line in 2D space. alias Line = array<Point, 2>; /// A bounding box in 2D space. This is the result of "drawing" operations on our canvas, and what /// the server reports back to the client. These bounds are sufficient to contain all of the /// lines (inclusive) on a canvas at a given time. type BoundingBox = struct { top_left Point; bottom_right Point; }; /// Manages a single instance of a canvas. Each session of this protocol is responsible for a new /// canvas. @discoverable open protocol Instance { /// Add a line to the canvas. /// /// This method can be considered an improvement over the one-way case from a flow control /// perspective, as it is now much more difficult for a well-behaved client to "get ahead" of /// the server and overwhelm. This is because the client now waits for each request to be acked /// by the server before proceeding. This change represents a trade-off: we get much greater /// synchronization of message flow between the client and the server, at the cost of worse /// performance at the limit due to the extra wait imposed by each ack. flexible AddLine(struct { line Line; }) -> (); /// Update the client with the latest drawing state. The server makes no guarantees about how /// often this event occurs - it could occur multiple times per board state, for example. flexible -> OnDrawn(BoundingBox); };
CML
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/client_bin", }, use: [ { protocol: "examples.canvas.addlinemetered.Instance" }, ], config: { // A script for the client to follow. Entries in the script may take one of two forms: a // pair of signed-integer coordinates like "-2,15:4,5", or the string "WAIT". The former // calls `AddLine(...)`, while the latter pauses execution until the next `->OnDrawn(...)` // event is received. // // TODO(https://fxbug.dev/42178362): It would absolve individual language implementations of a great // deal of string parsing if we were able to use a vector of `union { Point; WaitEnum}` // here. script: { type: "vector", max_count: 100, element: { type: "string", max_size: 64, }, }, }, }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.canvas.addlinemetered.Instance" }, ], expose: [ { protocol: "examples.canvas.addlinemetered.Instance", from: "self", }, ], }
領域
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.canvas.addlinemetered.Instance", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
用戶端和伺服器實作項目現在可以使用任何支援的語言編寫:
Rust
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{format_err, Context as _, Error}; use config::Config; use fidl_examples_canvas_addlinemetered::{InstanceEvent, InstanceMarker, Point}; use fuchsia_component::client::connect_to_protocol; use futures::TryStreamExt; use std::{thread, time}; #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Load the structured config values passed to this component at startup. let config = Config::take_from_startup_handle(); // Use the Component Framework runtime to connect to the newly spun up server component. We wrap // our retained client end in a proxy object that lets us asynchronously send Instance requests // across the channel. let instance = connect_to_protocol::<InstanceMarker>()?; println!("Outgoing connection enabled"); for action in config.script.into_iter() { // If the next action in the script is to "WAIT", block until an OnDrawn event is received // from the server. if action == "WAIT" { let mut event_stream = instance.take_event_stream(); loop { match event_stream .try_next() .await .context("Error getting event response from proxy")? .ok_or_else(|| format_err!("Proxy sent no events"))? { InstanceEvent::OnDrawn { top_left, bottom_right } => { println!( "OnDrawn event received: top_left: {:?}, bottom_right: {:?}", top_left, bottom_right ); break; } InstanceEvent::_UnknownEvent { ordinal, .. } => { println!("Received an unknown event with ordinal {ordinal}"); } } } continue; } // If the action is not a "WAIT", we need to draw a line instead. Parse the string input, // making two points out of it. let mut points = action .split(":") .map(|point| { let integers = point .split(",") .map(|integer| integer.parse::<i64>().unwrap()) .collect::<Vec<i64>>(); Point { x: integers[0], y: integers[1] } }) .collect::<Vec<Point>>(); // Assemble a line from the two points. let from = points.pop().ok_or(format_err!("line requires 2 points, but has 0"))?; let to = points.pop().ok_or(format_err!("line requires 2 points, but has 1"))?; let line = [from, to]; // Draw a line to the canvas by calling the server, using the two points we just parsed // above as arguments. println!("AddLine request sent: {:?}", line); // By awaiting on the reply, we prevent the client from sending another request before the // server is ready to handle, thereby syncing the flow rate between the two parties over // this method. instance.add_line(&line).await.context("Error sending request")?; println!("AddLine response received"); } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. thread::sleep(time::Duration::from_secs(2)); Ok(()) }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{Context as _, Error}; use fidl::endpoints::RequestStream as _; use fidl_examples_canvas_addlinemetered::{ BoundingBox, InstanceRequest, InstanceRequestStream, Point, }; use fuchsia_async::{Time, Timer}; use fuchsia_component::server::ServiceFs; use fuchsia_zircon::{self as zx}; use futures::future::join; use futures::prelude::*; use std::sync::{Arc, Mutex}; // A struct that stores the two things we care about for this example: the bounding box the lines // that have been added thus far, and bit to track whether or not there have been changes since the // last `OnDrawn` event. #[derive(Debug)] struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. changed: bool, bounding_box: BoundingBox, } impl CanvasState { /// Handler for the `AddLine` method. fn add_line(&mut self, line: [Point; 2]) { // Update the bounding box to account for the new lines we've just "added" to the canvas. let bounds = &mut self.bounding_box; for point in line { if point.x < bounds.top_left.x { bounds.top_left.x = point.x; } if point.y > bounds.top_left.y { bounds.top_left.y = point.y; } if point.x > bounds.bottom_right.x { bounds.bottom_right.x = point.x; } if point.y < bounds.bottom_right.y { bounds.bottom_right.y = point.y; } } // Mark the state as "dirty", so that an update is sent back to the client on the next tick. self.changed = true } } /// Creates a new instance of the server, paired to a single client across a zircon channel. async fn run_server(stream: InstanceRequestStream) -> Result<(), Error> { // Create a new in-memory state store for the state of the canvas. The store will live for the // lifetime of the connection between the server and this particular client. let state = Arc::new(Mutex::new(CanvasState { changed: true, bounding_box: BoundingBox { top_left: Point { x: 0, y: 0 }, bottom_right: Point { x: 0, y: 0 }, }, })); // Take ownership of the control_handle from the stream, which will allow us to push events from // a different async task. let control_handle = stream.control_handle(); // A separate watcher task periodically "draws" the canvas, and notifies the client of the new // state. We'll need a cloned reference to the canvas state to be accessible from the new // task. let state_ref = state.clone(); let update_sender = || async move { loop { // Our server sends one update per second. Timer::new(Time::after(zx::Duration::from_seconds(1))).await; let mut state = state_ref.lock().unwrap(); if !state.changed { continue; } // After acquiring the lock, this is where we would draw the actual lines. Since this is // just an example, we'll avoid doing the actual rendering, and simply send the bounding // box to the client instead. let bounds = state.bounding_box; match control_handle.send_on_drawn(&bounds.top_left, &bounds.bottom_right) { Ok(_) => println!( "OnDrawn event sent: top_left: {:?}, bottom_right: {:?}", bounds.top_left, bounds.bottom_right ), Err(_) => return, } // Reset the change tracker. state.changed = false } }; // Handle requests on the protocol sequentially - a new request is not handled until its // predecessor has been processed. let state_ref = &state; let request_handler = stream.map(|result| result.context("failed request")).try_for_each(|request| async move { // Match based on the method being invoked. match request { InstanceRequest::AddLine { line, responder } => { println!("AddLine request received: {:?}", line); state_ref.lock().unwrap().add_line(line); // Because this is now a two-way method, we must use the generated `responder` // to send an in this case empty reply back to the client. This is the mechanic // which syncs the flow rate between the client and server on this method, // thereby preventing the client from "flooding" the server with unacknowledged // work. responder.send().context("Error responding")?; println!("AddLine response sent"); } // InstanceRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }); // This await does not complete, and thus the function does not return, unless the server errors // out. The stream will await indefinitely, thereby creating a long-lived server. Here, we first // wait for the updater task to realize the connection has died, then bubble up the error. join(request_handler, update_sender()).await.0 } // A helper enum that allows us to treat a `Instance` service instance as a value. enum IncomingService { Instance(InstanceRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Instance` protocol - this will allow the client to see // the server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Instance); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Instance(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++ (自然)
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.addlinemetered/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <charconv> #include <examples/fidl/new/canvas/add_line_metered/cpp_natural/client/config.h> // The |EventHandler| is a derived class that we pass into the |fidl::WireClient| to handle incoming // events asynchronously. class EventHandler : public fidl::AsyncEventHandler<examples_canvas_addlinemetered::Instance> { public: // Handler for |OnDrawn| events sent from the server. void OnDrawn(fidl::Event<examples_canvas_addlinemetered::Instance::OnDrawn>& event) override { auto top_left = event.top_left(); auto bottom_right = event.bottom_right(); FX_LOGS(INFO) << "OnDrawn event received: top_left: Point { x: " << top_left.x() << ", y: " << top_left.y() << " }, bottom_right: Point { x: " << bottom_right.x() << ", y: " << bottom_right.y() << " }"; loop_.Quit(); } void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; } void handle_unknown_event( fidl::UnknownEventMetadata<examples_canvas_addlinemetered::Instance> metadata) override { FX_LOGS(WARNING) << "Received an unknown event with ordinal " << metadata.event_ordinal; } explicit EventHandler(async::Loop& loop) : loop_(loop) {} private: async::Loop& loop_; }; // A helper function that takes a coordinate in string form, like "123,-456", and parses it into a // a struct of the form |{ in64 x; int64 y; }|. ::examples_canvas_addlinemetered::Point ParsePoint(std::string_view input) { int64_t x = 0; int64_t y = 0; size_t index = input.find(','); if (index != std::string::npos) { std::from_chars(input.data(), input.data() + index, x); std::from_chars(input.data() + index + 1, input.data() + input.length(), y); } return ::examples_canvas_addlinemetered::Point(x, y); } // A helper function that takes a coordinate pair in string form, like "1,2:-3,-4", and parses it // into an array of 2 |Point| structs. ::std::array<::examples_canvas_addlinemetered::Point, 2> ParseLine(const std::string& action) { auto input = std::string_view(action); size_t index = input.find(':'); if (index != std::string::npos) { return {ParsePoint(input.substr(0, index)), ParsePoint(input.substr(index + 1))}; } return {}; } int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop and dispatcher. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a // |zx::result| and it must be checked for errors. zx::result client_end = component::Connect<examples_canvas_addlinemetered::Instance>(); if (!client_end.is_ok()) { FX_LOGS(ERROR) << "Synchronous error when connecting to the |Instance| protocol: " << client_end.status_string(); return -1; } // Create an instance of the event handler. EventHandler event_handler(loop); // Create an asynchronous client using the newly-established connection. fidl::Client client(std::move(*client_end), dispatcher, &event_handler); FX_LOGS(INFO) << "Outgoing connection enabled"; for (const auto& action : conf.script()) { // If the next action in the script is to "WAIT", block until an |OnDrawn| event is received // from the server. if (action == "WAIT") { loop.Run(); loop.ResetQuit(); continue; } // Draw a line to the canvas by calling the server, using the two points we just parsed // above as arguments. auto line = ParseLine(action); FX_LOGS(INFO) << "AddLine request sent: [Point { x: " << line[1].x() << ", y: " << line[1].y() << " }, Point { x: " << line[0].x() << ", y: " << line[0].y() << " }]"; client->AddLine(line).ThenExactlyOnce( [&](fidl::Result<examples_canvas_addlinemetered::Instance::AddLine>& result) { // Check if the FIDL call succeeded or not. if (!result.is_ok()) { // Check that our two-way call succeeded, and handle the error appropriately. In the // case of this example, there is nothing we can do to recover here, except to log an // error and exit the program. FX_LOGS(ERROR) << "Could not send AddLine request: " << result.error_value().FormatDescription(); } FX_LOGS(INFO) << "AddLine response received"; // Quit the loop, thereby handing control back to the outer loop of actions being iterated // over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. sleep(2); return 0; }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.addlinemetered/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async/cpp/task.h> #include <lib/component/outgoing/cpp/outgoing_directory.h> #include <lib/fidl/cpp/wire/channel.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <src/lib/fxl/macros.h> #include <src/lib/fxl/memory/weak_ptr.h> // A struct that stores the two things we care about for this example: the set of lines, and the // bounding box that contains them. struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. bool changed = true; examples_canvas_addlinemetered::BoundingBox bounding_box; }; // An implementation of the |Instance| protocol. class InstanceImpl final : public fidl::Server<examples_canvas_addlinemetered::Instance> { public: // Bind this implementation to a channel. InstanceImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<examples_canvas_addlinemetered::Instance> server_end) : binding_(fidl::BindServer( dispatcher, std::move(server_end), this, [this](InstanceImpl* impl, fidl::UnbindInfo info, fidl::ServerEnd<examples_canvas_addlinemetered::Instance> server_end) { if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; })), weak_factory_(this) { // Start the update timer on startup. Our server sends one update per second ScheduleOnDrawnEvent(dispatcher, zx::sec(1)); } void AddLine(AddLineRequest& request, AddLineCompleter::Sync& completer) override { auto points = request.line(); FX_LOGS(INFO) << "AddLine request received: [Point { x: " << points[1].x() << ", y: " << points[1].y() << " }, Point { x: " << points[0].x() << ", y: " << points[0].y() << " }]"; // Update the bounding box to account for the new line we've just "added" to the canvas. auto& bounds = state_.bounding_box; for (const auto& point : request.line()) { if (point.x() < bounds.top_left().x()) { bounds.top_left().x() = point.x(); } if (point.y() > bounds.top_left().y()) { bounds.top_left().y() = point.y(); } if (point.x() > bounds.bottom_right().x()) { bounds.bottom_right().x() = point.x(); } if (point.y() < bounds.bottom_right().y()) { bounds.bottom_right().y() = point.y(); } } // Mark the state as "dirty", so that an update is sent back to the client on the next |OnDrawn| // event. state_.changed = true; // Because this is now a two-way method, we must use the generated |completer| to send an in // this case empty reply back to the client. This is the mechanic which syncs the flow rate // between the client and server on this method, thereby preventing the client from "flooding" // the server with unacknowledged work. completer.Reply(); FX_LOGS(INFO) << "AddLine response sent"; } void handle_unknown_method( fidl::UnknownMethodMetadata<examples_canvas_addlinemetered::Instance> metadata, fidl::UnknownMethodCompleter::Sync& completer) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal; } private: // Each scheduled update waits for the allotted amount of time, sends an update if something has // changed, and schedules the next update. void ScheduleOnDrawnEvent(async_dispatcher_t* dispatcher, zx::duration after) { async::PostDelayedTask( dispatcher, [&, dispatcher, after, weak = weak_factory_.GetWeakPtr()] { // Halt execution if the binding has been deallocated already. if (!weak) { return; } // Schedule the next update if the binding still exists. weak->ScheduleOnDrawnEvent(dispatcher, after); // No need to send an update if nothing has changed since the last one. if (!weak->state_.changed) { return; } // This is where we would draw the actual lines. Since this is just an example, we'll // avoid doing the actual rendering, and simply send the bounding box to the client // instead. auto result = fidl::SendEvent(binding_)->OnDrawn(state_.bounding_box); if (!result.is_ok()) { return; } auto top_left = state_.bounding_box.top_left(); auto bottom_right = state_.bounding_box.bottom_right(); FX_LOGS(INFO) << "OnDrawn event sent: top_left: Point { x: " << top_left.x() << ", y: " << top_left.y() << " }, bottom_right: Point { x: " << bottom_right.x() << ", y: " << bottom_right.y() << " }"; // Reset the change tracker. state_.changed = false; }, after); } fidl::ServerBindingRef<examples_canvas_addlinemetered::Instance> binding_; CanvasState state_ = CanvasState{}; // Generates weak references to this object, which are appropriate to pass into asynchronous // callbacks that need to access this object. The references are automatically invalidated // if this object is destroyed. fxl::WeakPtrFactory<InstanceImpl> weak_factory_; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This // directory is where the outgoing FIDL protocols are installed so that they can be provided to // other components. component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher); // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle. // The startup handle is a handle provided to every component by the system, so that they can // serve capabilities (e.g. FIDL protocols) to other components. zx::result result = outgoing.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } // Register a handler for components trying to connect to // |examples.canvas.addlinemetered.Instance|. result = outgoing.AddUnmanagedProtocol<examples_canvas_addlinemetered::Instance>( [dispatcher](fidl::ServerEnd<examples_canvas_addlinemetered::Instance> server_end) { // Create an instance of our InstanceImpl that destroys itself when the connection closes. new InstanceImpl(dispatcher, std::move(server_end)); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Instance protocol: " << result.status_string(); return -1; } // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
C++ (有線)
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.addlinemetered/cpp/wire.h> #include <lib/async-loop/cpp/loop.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <charconv> #include <examples/fidl/new/canvas/add_line_metered/cpp_wire/client/config.h> // The |EventHandler| is a derived class that we pass into the |fidl::WireClient| to handle incoming // events asynchronously. class EventHandler : public fidl::WireAsyncEventHandler<examples_canvas_addlinemetered::Instance> { public: // Handler for |OnDrawn| events sent from the server. void OnDrawn(fidl::WireEvent<examples_canvas_addlinemetered::Instance::OnDrawn>* event) override { auto top_left = event->top_left; auto bottom_right = event->bottom_right; FX_LOGS(INFO) << "OnDrawn event received: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; loop_.Quit(); } void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; } void handle_unknown_event( fidl::UnknownEventMetadata<examples_canvas_addlinemetered::Instance> metadata) override { FX_LOGS(WARNING) << "Received an unknown event with ordinal " << metadata.event_ordinal; } explicit EventHandler(async::Loop& loop) : loop_(loop) {} private: async::Loop& loop_; }; // A helper function that takes a coordinate in string form, like "123,-456", and parses it into a // a struct of the form |{ in64 x; int64 y; }|. ::examples_canvas_addlinemetered::wire::Point ParsePoint(std::string_view input) { int64_t x = 0; int64_t y = 0; size_t index = input.find(','); if (index != std::string::npos) { std::from_chars(input.data(), input.data() + index, x); std::from_chars(input.data() + index + 1, input.data() + input.length(), y); } return ::examples_canvas_addlinemetered::wire::Point{.x = x, .y = y}; } // A helper function that takes a coordinate pair in string form, like "1,2:-3,-4", and parses it // into an array of 2 |Point| structs. ::fidl::Array<::examples_canvas_addlinemetered::wire::Point, 2> ParseLine( const std::string& action) { auto input = std::string_view(action); size_t index = input.find(':'); if (index != std::string::npos) { return {ParsePoint(input.substr(0, index)), ParsePoint(input.substr(index + 1))}; } return {}; } int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop and dispatcher. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a // |zx::result| and it must be checked for errors. zx::result client_end = component::Connect<examples_canvas_addlinemetered::Instance>(); if (!client_end.is_ok()) { FX_LOGS(ERROR) << "Synchronous error when connecting to the |Instance| protocol: " << client_end.status_string(); return -1; } // Create an instance of the event handler. EventHandler event_handler(loop); // Create an asynchronous client using the newly-established connection. fidl::WireClient client(std::move(*client_end), dispatcher, &event_handler); FX_LOGS(INFO) << "Outgoing connection enabled"; for (const auto& action : conf.script()) { // If the next action in the script is to "WAIT", block until an |OnDrawn| event is received // from the server. if (action == "WAIT") { loop.Run(); loop.ResetQuit(); continue; } // Draw a line to the canvas by calling the server, using the two points we just parsed // above as arguments. auto line = ParseLine(action); FX_LOGS(INFO) << "AddLine request sent: [Point { x: " << line[1].x << ", y: " << line[1].y << " }, Point { x: " << line[0].x << ", y: " << line[0].y << " }]"; client->AddLine(line).ThenExactlyOnce( [&](fidl::WireUnownedResult<examples_canvas_addlinemetered::Instance::AddLine>& result) { // Check if the FIDL call succeeded or not. if (!result.ok()) { // Check that our two-way call succeeded, and handle the error appropriately. In the // case of this example, there is nothing we can do to recover here, except to log an // error and exit the program. FX_LOGS(ERROR) << "Could not send AddLine request: " << result.status_string(); } FX_LOGS(INFO) << "AddLine response received"; // Quit the loop, thereby handing control back to the outer loop of actions being iterated // over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. sleep(2); return 0; }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.addlinemetered/cpp/wire.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async/cpp/task.h> #include <lib/component/outgoing/cpp/outgoing_directory.h> #include <lib/fidl/cpp/wire/channel.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <src/lib/fxl/macros.h> #include <src/lib/fxl/memory/weak_ptr.h> // A struct that stores the two things we care about for this example: the set of lines, and the // bounding box that contains them. struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. bool changed = true; examples_canvas_addlinemetered::wire::BoundingBox bounding_box; }; // An implementation of the |Instance| protocol. class InstanceImpl final : public fidl::WireServer<examples_canvas_addlinemetered::Instance> { public: // Bind this implementation to a channel. InstanceImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<examples_canvas_addlinemetered::Instance> server_end) : binding_(fidl::BindServer( dispatcher, std::move(server_end), this, [this](InstanceImpl* impl, fidl::UnbindInfo info, fidl::ServerEnd<examples_canvas_addlinemetered::Instance> server_end) { if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; })), weak_factory_(this) { // Start the update timer on startup. Our server sends one update per second ScheduleOnDrawnEvent(dispatcher, zx::sec(1)); } void AddLine(AddLineRequestView request, AddLineCompleter::Sync& completer) override { auto points = request->line; FX_LOGS(INFO) << "AddLine request received: [Point { x: " << points[1].x << ", y: " << points[1].y << " }, Point { x: " << points[0].x << ", y: " << points[0].y << " }]"; // Update the bounding box to account for the new line we've just "added" to the canvas. auto& bounds = state_.bounding_box; for (const auto& point : request->line) { if (point.x < bounds.top_left.x) { bounds.top_left.x = point.x; } if (point.y > bounds.top_left.y) { bounds.top_left.y = point.y; } if (point.x > bounds.bottom_right.x) { bounds.bottom_right.x = point.x; } if (point.y < bounds.bottom_right.y) { bounds.bottom_right.y = point.y; } } // Mark the state as "dirty", so that an update is sent back to the client on the next |OnDrawn| // event. state_.changed = true; // Because this is now a two-way method, we must use the generated |completer| to send an in // this case empty reply back to the client. This is the mechanic which syncs the flow rate // between the client and server on this method, thereby preventing the client from "flooding" // the server with unacknowledged work. completer.Reply(); FX_LOGS(INFO) << "AddLine response sent"; } void handle_unknown_method( fidl::UnknownMethodMetadata<examples_canvas_addlinemetered::Instance> metadata, fidl::UnknownMethodCompleter::Sync& completer) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal; } private: // Each scheduled update waits for the allotted amount of time, sends an update if something has // changed, and schedules the next update. void ScheduleOnDrawnEvent(async_dispatcher_t* dispatcher, zx::duration after) { async::PostDelayedTask( dispatcher, [&, dispatcher, after, weak = weak_factory_.GetWeakPtr()] { // Halt execution if the binding has been deallocated already. if (!weak) { return; } // Schedule the next update if the binding still exists. weak->ScheduleOnDrawnEvent(dispatcher, after); // No need to send an update if nothing has changed since the last one. if (!weak->state_.changed) { return; } // This is where we would draw the actual lines. Since this is just an example, we'll // avoid doing the actual rendering, and simply send the bounding box to the client // instead. auto top_left = weak->state_.bounding_box.top_left; auto bottom_right = weak->state_.bounding_box.bottom_right; fidl::Status status = fidl::WireSendEvent(weak->binding_)->OnDrawn(top_left, bottom_right); if (!status.ok()) { return; } FX_LOGS(INFO) << "OnDrawn event sent: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; // Reset the change tracker. weak->state_.changed = false; }, after); } fidl::ServerBindingRef<examples_canvas_addlinemetered::Instance> binding_; CanvasState state_ = CanvasState{}; // Generates weak references to this object, which are appropriate to pass into asynchronous // callbacks that need to access this object. The references are automatically invalidated // if this object is destroyed. fxl::WeakPtrFactory<InstanceImpl> weak_factory_; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This // directory is where the outgoing FIDL protocols are installed so that they can be provided to // other components. component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher); // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle. // The startup handle is a handle provided to every component by the system, so that they can // serve capabilities (e.g. FIDL protocols) to other components. zx::result result = outgoing.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } // Register a handler for components trying to connect to // |examples.canvas.addlinemetered.Instance|. result = outgoing.AddUnmanagedProtocol<examples_canvas_addlinemetered::Instance>( [dispatcher](fidl::ServerEnd<examples_canvas_addlinemetered::Instance> server_end) { // Create an instance of our InstanceImpl that destroys itself when the connection closes. new InstanceImpl(dispatcher, std::move(server_end)); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Instance protocol: " << result.status_string(); return -1; } // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
HLCPP
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <lib/async-loop/cpp/loop.h> #include <lib/sys/cpp/component_context.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <charconv> #include <examples/canvas/addlinemetered/cpp/fidl.h> #include <examples/fidl/new/canvas/add_line_metered/hlcpp/client/config.h> #include "lib/fpromise/result.h" // A helper function that takes a coordinate in string form, like "123,-456", and parses it into a // a struct of the form |{ in64 x; int64 y; }|. ::examples::canvas::addlinemetered::Point ParsePoint(std::string_view input) { int64_t x = 0; int64_t y = 0; size_t index = input.find(','); if (index != std::string::npos) { std::from_chars(input.data(), input.data() + index, x); std::from_chars(input.data() + index + 1, input.data() + input.length(), y); } return ::examples::canvas::addlinemetered::Point{.x = x, .y = y}; } // A helper function that takes a coordinate pair in string form, like "1,2:-3,-4", and parses it // into an array of 2 |Point| structs. ::std::array<::examples::canvas::addlinemetered::Point, 2> ParseLine(const std::string& action) { auto input = std::string_view(action); size_t index = input.find(':'); if (index != std::string::npos) { return {ParsePoint(input.substr(0, index)), ParsePoint(input.substr(index + 1))}; } return {}; } int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace, then create an asynchronous client // using the newly-established connection. examples::canvas::addlinemetered::InstancePtr instance_proxy; auto context = sys::ComponentContext::Create(); context->svc()->Connect(instance_proxy.NewRequest(dispatcher)); FX_LOGS(INFO) << "Outgoing connection enabled"; instance_proxy.set_error_handler([&loop](zx_status_t status) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; loop.Quit(); }); // Provide a lambda to handle incoming |OnDrawn| events asynchronously. instance_proxy.events().OnDrawn = [&loop]( ::examples::canvas::addlinemetered::Point top_left, ::examples::canvas::addlinemetered::Point bottom_right) { FX_LOGS(INFO) << "OnDrawn event received: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; loop.Quit(); }; instance_proxy.events().handle_unknown_event = [](uint64_t ordinal) { FX_LOGS(WARNING) << "Received an unknown event with ordinal " << ordinal; }; for (const auto& action : conf.script()) { // If the next action in the script is to "WAIT", block until an |OnDrawn| event is received // from the server. if (action == "WAIT") { loop.Run(); loop.ResetQuit(); continue; } // Draw a line to the canvas by calling the server, using the two points we just parsed // above as arguments. auto line = ParseLine(action); FX_LOGS(INFO) << "AddLine request sent: [Point { x: " << line[1].x << ", y: " << line[1].y << " }, Point { x: " << line[0].x << ", y: " << line[0].y << " }]"; instance_proxy->AddLine(line, [&](fpromise::result<void, fidl::FrameworkErr> result) { if (result.is_error()) { // Check that our flexible two-way call was known to the server and handle the case of an // unknown method appropriately. In the case of this example, there is nothing we can do to // recover here, except to log an error and exit the program. FX_LOGS(ERROR) << "Server does not implement AddLine"; } FX_LOGS(INFO) << "AddLine response received"; // Quit the loop, thereby handing control back to the outer loop of actions being iterated // over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. sleep(2); return 0; }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <lib/async-loop/cpp/loop.h> #include <lib/async-loop/default.h> #include <lib/async/cpp/task.h> #include <lib/fidl/cpp/binding.h> #include <lib/sys/cpp/component_context.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <examples/canvas/addlinemetered/cpp/fidl.h> #include <src/lib/fxl/macros.h> #include <src/lib/fxl/memory/weak_ptr.h> // A struct that stores the two things we care about for this example: the set of lines, and the // bounding box that contains them. struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. bool changed = true; examples::canvas::addlinemetered::BoundingBox bounding_box; }; // An implementation of the |Instance| protocol. class InstanceImpl final : public examples::canvas::addlinemetered::Instance { public: // Bind this implementation to an |InterfaceRequest|. InstanceImpl(async_dispatcher_t* dispatcher, fidl::InterfaceRequest<examples::canvas::addlinemetered::Instance> request) : binding_(fidl::Binding<examples::canvas::addlinemetered::Instance>(this)), weak_factory_(this) { binding_.Bind(std::move(request), dispatcher); // Gracefully handle abrupt shutdowns. binding_.set_error_handler([this](zx_status_t status) mutable { if (status != ZX_ERR_PEER_CLOSED) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; }); // Start the update timer on startup. Our server sends one update per second. ScheduleOnDrawnEvent(dispatcher, zx::sec(1)); } void AddLine(::std::array<::examples::canvas::addlinemetered::Point, 2> line, AddLineCallback callback) override { FX_LOGS(INFO) << "AddLine request received: [Point { x: " << line[1].x << ", y: " << line[1].y << " }, Point { x: " << line[0].x << ", y: " << line[0].y << " }]"; // Update the bounding box to account for the new line we've just "added" to the canvas. auto& bounds = state_.bounding_box; for (const auto& point : line) { if (point.x < bounds.top_left.x) { bounds.top_left.x = point.x; } if (point.y > bounds.top_left.y) { bounds.top_left.y = point.y; } if (point.x > bounds.bottom_right.x) { bounds.bottom_right.x = point.x; } if (point.y < bounds.bottom_right.y) { bounds.bottom_right.y = point.y; } } // Mark the state as "dirty", so that an update is sent back to the client on the next |OnDrawn| // event. state_.changed = true; // Because this is now a two-way method, we must use the generated |callback| to send an in // this case empty reply back to the client. This is the mechanic which syncs the flow rate // between the client and server on this method, thereby preventing the client from "flooding" // the server with unacknowledged work. callback(fpromise::ok()); FX_LOGS(INFO) << "AddLine response sent"; } void handle_unknown_method(uint64_t ordinal, bool method_has_response) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << ordinal; } private: // Each scheduled update waits for the allotted amount of time, sends an update if something has // changed, and schedules the next update. void ScheduleOnDrawnEvent(async_dispatcher_t* dispatcher, zx::duration after) { async::PostDelayedTask( dispatcher, [&, dispatcher, after, weak = weak_factory_.GetWeakPtr()] { // Halt execution if the binding has been deallocated already. if (!weak) { return; } // Schedule the next update if the binding still exists. weak->ScheduleOnDrawnEvent(dispatcher, after); // No need to send an update if nothing has changed since the last one. if (!weak->state_.changed) { return; } // This is where we would draw the actual lines. Since this is just an example, we'll // avoid doing the actual rendering, and simply send the bounding box to the client // instead. auto top_left = state_.bounding_box.top_left; auto bottom_right = state_.bounding_box.bottom_right; binding_.events().OnDrawn(top_left, bottom_right); FX_LOGS(INFO) << "OnDrawn event sent: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; // Reset the change tracker. state_.changed = false; }, after); } fidl::Binding<examples::canvas::addlinemetered::Instance> binding_; CanvasState state_ = CanvasState{}; // Generates weak references to this object, which are appropriate to pass into asynchronous // callbacks that need to access this object. The references are automatically invalidated // if this object is destroyed. fxl::WeakPtrFactory<InstanceImpl> weak_factory_; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. // // Note that unlike the new C++ bindings, HLCPP bindings rely on the async loop being attached to // the current thread via the |kAsyncLoopConfigAttachToCurrentThread| configuration. async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. // This directory is where the outgoing FIDL protocols are installed so that they can be // provided to other components. auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory(); // Register a handler for components trying to connect to // |examples.canvas.addlinemetered.Instance|. context->outgoing()->AddPublicService( fidl::InterfaceRequestHandler<examples::canvas::addlinemetered::Instance>( [dispatcher](fidl::InterfaceRequest<examples::canvas::addlinemetered::Instance> request) { // Create an instance of our |InstanceImpl| that destroys itself when the connection // closes. new InstanceImpl(dispatcher, std::move(request)); })); // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
使用事件推送受限資料
在 FIDL 中,伺服器可傳送用戶端來路不明的訊息,稱為事件。 使用事件的通訊協定需要特別留意流量控制 因為事件機製本身不提供任何流量控制
事件的適用情況是當事件最多出現一次 該管道的整個訂閱期間在這個模式下,通訊協定不會 必須控制事件的任何流程:
protocol DeathWish {
-> OnFatalError(struct {
error_code zx.Status;
});
};
事件的另一個理想用途是當用戶端要求伺服器 就會產生事件,而當伺服器產生的事件總數 使用。這個模式是更複雜的版本 伺服器可以在此模式下回應「get」要求 次數 (而不是一次):
protocol NetworkScanner {
ScanForNetworks();
-> OnNetworkDiscovered(struct {
network string;
});
-> OnScanFinished();
};
使用確認功能限制事件
如果事件數量在時間上沒有任何限制,請考慮 讓用戶端傳送訊息來確認事件。這個模式 更加尷尬的確認聲明 用戶端角色的模式 以及伺服器是否已切換和其他模式一樣,伺服器應會節流 產生事件,以配合用戶端使用事件的速率:
protocol View1 {
-> OnInputEvent(struct {
event InputEvent;
});
NotifyInputEventHandled();
};
與正常確認模式相比,這個模式的一個優點是 用戶端可透過單一訊息輕鬆確認多個事件 因為確認狀態與所確認事件無關。 這個模式可以減少資料量 適用於確認訊息,適合用來處理 事件類型:
protocol View2 {
-> OnInputEvent(struct {
event InputEvent;
seq uint64;
});
-> OnFocusChangedEvent(struct {
event FocusChangedEvent;
seq uint64;
});
NotifyEventsHandled(struct {
last_seq uint64;
});
};
與使用確認機制的節流推送不同,這個模式並不會表示 與 FIDL 語法中的回應之間的關係,因此 但很容易遭到濫用流程控制只會在用戶端正確運作 您必須實作通知訊息的傳送功能
FIDL 方案:節流事件模式
事件是指從伺服器啟動的 FIDL 呼叫。由於這些呼叫沒有 內建用戶端回應,兩者均未控管:伺服器 排出非常大量的這類呼叫,導致用戶端淹沒。單一解決方案 請參閱節流事件模式。這個模式包括 用戶端呼叫 FIDL 方法,做為一或多個確認點 要同步處理的活動。
伺服器應避免傳送更多受限制的事件 ( 此處的語意僅適用於實作通訊協定),直到使用 接聽了下一通來自用戶端的確認呼叫同樣地 如果伺服器傳送的限縮事件數量超過限制,應關閉連線 才能接受。不受這些限制 內建於 FIDL 執行階段,因此需要完全手動實作 用戶端/伺服器實作者的人員,以確保正確行為。
如要提升 Instance
通訊協定的效能,其中一個方法是允許
批次處理行:與其每次都傳送單一 AddLine(...);
請在畫布加入新的一行,等待回覆後
以便下一行程式碼,改為將許多行合併成單一欄位
叫用新 AddLines(...);
呼叫。客戶現在可以決定
最好從大量線段繪製出線條
如果不實作,我們會在以下情況下遇到伺服器和伺服器問題
用戶端完全未同步:用戶端可將
未受限的 AddLines(...);
呼叫,且伺服器同樣可能會使用戶端發生洪水
無法處理的 -> OnDrawn(...);
事件無論是
就是新增簡單的 Ready() -> ();
方法進行同步處理
用途。每當用戶端準備好接收此方法時,就會呼叫這個方法
下一個繪圖更新,且伺服器的回應指出用戶端
才能繼續處理更多要求
現在,我們有一些雙向流量控制。通訊協定現已導入
動態饋給前向模式,允許在部分
同步處理「修訂」呼叫會觸發伺服器中的實際作業。這個
可防止用戶端因工作負荷過大。同樣地,
伺服器無法再傳送不受限的 -> OnDrawn(...);
事件:每個
事件必須遵循來自用戶端 (Ready() -> ();
呼叫) 的信號,
表示已準備好執行更多工作。這就是所謂的受限
事件模式。
具體的導入方式必須手動套用部分規則:用戶端
如收到未發生的 -> OnDrawn(...);
事件,必須關閉連線
透過 Ready() -> ();
方法傳送要求
FIDL、CML 和領域介面定義如下:
FIDL
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library examples.canvas.clientrequesteddraw; /// A point in 2D space. type Point = struct { x int64; y int64; }; /// A line in 2D space. alias Line = array<Point, 2>; /// A bounding box in 2D space. This is the result of "drawing" operations on our canvas, and what /// the server reports back to the client. These bounds are sufficient to contain all of the /// lines (inclusive) on a canvas at a given time. type BoundingBox = struct { top_left Point; bottom_right Point; }; /// Manages a single instance of a canvas. Each session of this protocol is responsible for a new /// canvas. @discoverable open protocol Instance { /// Add multiple lines to the canvas. We are able to reduce protocol chatter and the number of /// requests needed by batching instead of calling the simpler `AddLine(...)` one line at a /// time. flexible AddLines(struct { lines vector<Line>; }); /// Rather than the server randomly performing draws, or trying to guess when to do so, the /// client must explicitly ask for them. This creates a bit of extra chatter with the additional /// method invocation, but allows much greater client-side control of when the canvas is "ready" /// for a view update, thereby eliminating unnecessary draws. /// /// This method also has the benefit of "throttling" the `-> OnDrawn(...)` event - rather than /// allowing a potentially unlimited flood of `-> OnDrawn(...)` calls, we now have the runtime /// enforced semantic that each `-> OnDrawn(...)` call must follow a unique `Ready() -> ()` call /// from the client. An unprompted `-> OnDrawn(...)` is invalid, and should cause the channel to /// immediately close. flexible Ready() -> (); /// Update the client with the latest drawing state. The server makes no guarantees about how /// often this event occurs - it could occur multiple times per board state, for example. flexible -> OnDrawn(BoundingBox); };
CML
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/client_bin", }, use: [ { protocol: "examples.canvas.clientrequesteddraw.Instance" }, ], config: { // A script for the client to follow. Entries in the script may take one of two forms: a // pair of signed-integer coordinates like "-2,15:4,5", or the string "READY". The former // builds a local vector sent via a single `AddLines(...)` call, while the latter sends a // `Ready() -> ()` call pauses execution until the next `->OnDrawn(...)` event is received. // // TODO(https://fxbug.dev/42178362): It would absolve individual language implementations of a great // deal of string parsing if we were able to use a vector of `union { Point; Ready}` here. script: { type: "vector", max_count: 100, element: { type: "string", max_size: 64, }, }, }, }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.canvas.clientrequesteddraw.Instance" }, ], expose: [ { protocol: "examples.canvas.clientrequesteddraw.Instance", from: "self", }, ], }
領域
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.canvas.clientrequesteddraw.Instance", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
用戶端和伺服器實作項目現在可以使用任何支援的語言編寫:
Rust
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{format_err, Context as _, Error}; use config::Config; use fidl_examples_canvas_clientrequesteddraw::{InstanceEvent, InstanceMarker, Point}; use fuchsia_component::client::connect_to_protocol; use futures::TryStreamExt; use std::{thread, time}; #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Load the structured config values passed to this component at startup. let config = Config::take_from_startup_handle(); // Use the Component Framework runtime to connect to the newly spun up server component. We wrap // our retained client end in a proxy object that lets us asynchronously send Instance requests // across the channel. let instance = connect_to_protocol::<InstanceMarker>()?; println!("Outgoing connection enabled"); let mut batched_lines = Vec::<[Point; 2]>::new(); for action in config.script.into_iter() { // If the next action in the script is to "PUSH", send a batch of lines to the server. if action == "PUSH" { instance.add_lines(&batched_lines).context("Could not send lines")?; println!("AddLines request sent"); batched_lines.clear(); continue; } // If the next action in the script is to "WAIT", block until an OnDrawn event is received // from the server. if action == "WAIT" { let mut event_stream = instance.take_event_stream(); loop { match event_stream .try_next() .await .context("Error getting event response from proxy")? .ok_or_else(|| format_err!("Proxy sent no events"))? { InstanceEvent::OnDrawn { top_left, bottom_right } => { println!( "OnDrawn event received: top_left: {:?}, bottom_right: {:?}", top_left, bottom_right ); break; } InstanceEvent::_UnknownEvent { ordinal, .. } => { println!("Received an unknown event with ordinal {ordinal}"); } } } // Now, inform the server that we are ready to receive more updates whenever they are // ready for us. println!("Ready request sent"); instance.ready().await.context("Could not send ready call")?; println!("Ready success"); continue; } // Add a line to the next batch. Parse the string input, making two points out of it. let mut points = action .split(":") .map(|point| { let integers = point .split(",") .map(|integer| integer.parse::<i64>().unwrap()) .collect::<Vec<i64>>(); Point { x: integers[0], y: integers[1] } }) .collect::<Vec<Point>>(); // Assemble a line from the two points. let from = points.pop().ok_or(format_err!("line requires 2 points, but has 0"))?; let to = points.pop().ok_or(format_err!("line requires 2 points, but has 1"))?; let mut line: [Point; 2] = [from, to]; // Batch a line for drawing to the canvas using the two points provided. println!("AddLines batching line: {:?}", &mut line); batched_lines.push(line); } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. thread::sleep(time::Duration::from_secs(2)); Ok(()) }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{anyhow, Context as _, Error}; use fidl::endpoints::RequestStream as _; use fidl_examples_canvas_clientrequesteddraw::{ BoundingBox, InstanceRequest, InstanceRequestStream, Point, }; use fuchsia_async::{Time, Timer}; use fuchsia_component::server::ServiceFs; use fuchsia_zircon::{self as zx}; use futures::future::join; use futures::prelude::*; use std::sync::{Arc, Mutex}; // A struct that stores the two things we care about for this example: the bounding box the lines // that have been added thus far, and bit to track whether or not there have been changes since the // last `OnDrawn` event. #[derive(Debug)] struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. changed: bool, // Tracks whether or not the client has declared itself ready to receive more updated. ready: bool, bounding_box: BoundingBox, } /// Handler for the `AddLines` method. fn add_lines(state: &mut CanvasState, lines: Vec<[Point; 2]>) { // Update the bounding box to account for the new lines we've just "added" to the canvas. let bounds = &mut state.bounding_box; for line in lines { println!("AddLines printing line: {:?}", line); for point in line { if point.x < bounds.top_left.x { bounds.top_left.x = point.x; } if point.y > bounds.top_left.y { bounds.top_left.y = point.y; } if point.x > bounds.bottom_right.x { bounds.bottom_right.x = point.x; } if point.y < bounds.bottom_right.y { bounds.bottom_right.y = point.y; } } } // Mark the state as "dirty", so that an update is sent back to the client on the next tick. state.changed = true } /// Creates a new instance of the server, paired to a single client across a zircon channel. async fn run_server(stream: InstanceRequestStream) -> Result<(), Error> { // Create a new in-memory state store for the state of the canvas. The store will live for the // lifetime of the connection between the server and this particular client. let state = Arc::new(Mutex::new(CanvasState { changed: true, ready: true, bounding_box: BoundingBox { top_left: Point { x: 0, y: 0 }, bottom_right: Point { x: 0, y: 0 }, }, })); // Take ownership of the control_handle from the stream, which will allow us to push events from // a different async task. let control_handle = stream.control_handle(); // A separate watcher task periodically "draws" the canvas, and notifies the client of the new // state. We'll need a cloned reference to the canvas state to be accessible from the new // task. let state_ref = state.clone(); let update_sender = || async move { loop { // Our server sends one update per second, but only if the client has declared that it // is ready to receive one. Timer::new(Time::after(zx::Duration::from_seconds(1))).await; let mut state = state_ref.lock().unwrap(); if !state.changed || !state.ready { continue; } // After acquiring the lock, this is where we would draw the actual lines. Since this is // just an example, we'll avoid doing the actual rendering, and simply send the bounding // box to the client instead. let bounds = state.bounding_box; match control_handle.send_on_drawn(&bounds.top_left, &bounds.bottom_right) { Ok(_) => println!( "OnDrawn event sent: top_left: {:?}, bottom_right: {:?}", bounds.top_left, bounds.bottom_right ), Err(_) => return, } // Reset the change and ready trackers. state.ready = false; state.changed = false; } }; // Handle requests on the protocol sequentially - a new request is not handled until its // predecessor has been processed. let state_ref = &state; let request_handler = stream.map(|result| result.context("failed request")).try_for_each(|request| async move { // Match based on the method being invoked. match request { InstanceRequest::AddLines { lines, .. } => { println!("AddLines request received"); add_lines(&mut state_ref.lock().unwrap(), lines); } InstanceRequest::Ready { responder, .. } => { println!("Ready request received"); // The client must only call `Ready() -> ();` after receiving an `-> OnDrawn();` // event; if two "consecutive" `Ready() -> ();` calls are received, this // interaction has entered an invalid state, and should be aborted immediately. let mut state = state_ref.lock().unwrap(); if state.ready == true { return Err(anyhow!("Invalid back-to-back `Ready` requests received")); } state.ready = true; responder.send().context("Error responding")?; } // InstanceRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }); // This line will only be reached if the server errors out. The stream will await indefinitely, // thereby creating a long-lived server. Here, we first wait for the updater task to realize the // connection has died, then bubble up the error. join(request_handler, update_sender()).await.0 } // A helper enum that allows us to treat a `Instance` service instance as a value. enum IncomingService { Instance(InstanceRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Instance` protocol - this will allow the client to see // the server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Instance); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Instance(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++ (自然)
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.clientrequesteddraw/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <charconv> #include <examples/fidl/new/canvas/client_requested_draw/cpp_natural/client/config.h> // The |EventHandler| is a derived class that we pass into the |fidl::WireClient| to handle incoming // events asynchronously. class EventHandler : public fidl::AsyncEventHandler<examples_canvas_clientrequesteddraw::Instance> { public: // Handler for |OnDrawn| events sent from the server. void OnDrawn( fidl::Event<examples_canvas_clientrequesteddraw::Instance::OnDrawn>& event) override { ::examples_canvas_clientrequesteddraw::Point top_left = event.top_left(); ::examples_canvas_clientrequesteddraw::Point bottom_right = event.bottom_right(); FX_LOGS(INFO) << "OnDrawn event received: top_left: Point { x: " << top_left.x() << ", y: " << top_left.y() << " }, bottom_right: Point { x: " << bottom_right.x() << ", y: " << bottom_right.y() << " }"; loop_.Quit(); } void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; } void handle_unknown_event( fidl::UnknownEventMetadata<examples_canvas_clientrequesteddraw::Instance> metadata) override { FX_LOGS(WARNING) << "Received an unknown event with ordinal " << metadata.event_ordinal; } explicit EventHandler(async::Loop& loop) : loop_(loop) {} private: async::Loop& loop_; }; // A helper function that takes a coordinate in string form, like "123,-456", and parses it into a // a struct of the form |{ in64 x; int64 y; }|. ::examples_canvas_clientrequesteddraw::Point ParsePoint(std::string_view input) { int64_t x = 0; int64_t y = 0; size_t index = input.find(','); if (index != std::string::npos) { std::from_chars(input.data(), input.data() + index, x); std::from_chars(input.data() + index + 1, input.data() + input.length(), y); } return ::examples_canvas_clientrequesteddraw::Point(x, y); } using Line = ::std::array<::examples_canvas_clientrequesteddraw::Point, 2>; // A helper function that takes a coordinate pair in string form, like "1,2:-3,-4", and parses it // into an array of 2 |Point| structs. Line ParseLine(const std::string& action) { auto input = std::string_view(action); size_t index = input.find(':'); if (index != std::string::npos) { return {ParsePoint(input.substr(0, index)), ParsePoint(input.substr(index + 1))}; } return {}; } int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop and dispatcher. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a // |zx::result| and it must be checked for errors. zx::result client_end = component::Connect<examples_canvas_clientrequesteddraw::Instance>(); if (!client_end.is_ok()) { FX_LOGS(ERROR) << "Synchronous error when connecting to the |Instance| protocol: " << client_end.status_string(); return -1; } // Create an instance of the event handler. EventHandler event_handler(loop); // Create an asynchronous client using the newly-established connection. fidl::Client client(std::move(*client_end), dispatcher, &event_handler); FX_LOGS(INFO) << "Outgoing connection enabled"; std::vector<Line> batched_lines; for (const auto& action : conf.script()) { // If the next action in the script is to "PUSH", send a batch of lines to the server. if (action == "PUSH") { fit::result<fidl::Error> result = client->AddLines(batched_lines); if (!result.is_ok()) { // Check that our one-way call was enqueued successfully, and handle the error // appropriately. In the case of this example, there is nothing we can do to recover here, // except to log an error and exit the program. FX_LOGS(ERROR) << "Could not send AddLines request: " << result.error_value(); return -1; } batched_lines.clear(); FX_LOGS(INFO) << "AddLines request sent"; continue; } // If the next action in the script is to "WAIT", block until an |OnDrawn| event is received // from the server. if (action == "WAIT") { loop.Run(); loop.ResetQuit(); // Now, inform the server that we are ready to receive more updates whenever they are // ready for us. FX_LOGS(INFO) << "Ready request sent"; client->Ready().ThenExactlyOnce( [&](fidl::Result<examples_canvas_clientrequesteddraw::Instance::Ready> result) { // Check if the FIDL call succeeded or not. if (result.is_ok()) { FX_LOGS(INFO) << "Ready success"; } else { FX_LOGS(ERROR) << "Could not send Ready request: " << result.error_value(); } // Quit the loop, thereby handing control back to the outer loop of actions being // iterated over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); continue; } // Batch a line for drawing to the canvas using the two points provided. Line line = ParseLine(action); batched_lines.push_back(line); FX_LOGS(INFO) << "AddLines batching line: [Point { x: " << line[1].x() << ", y: " << line[1].y() << " }, Point { x: " << line[0].x() << ", y: " << line[0].y() << " }]"; } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. sleep(2); return 0; }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.clientrequesteddraw/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async/cpp/task.h> #include <lib/component/outgoing/cpp/outgoing_directory.h> #include <lib/fidl/cpp/wire/channel.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <src/lib/fxl/macros.h> #include <src/lib/fxl/memory/weak_ptr.h> // A struct that stores the two things we care about for this example: the set of lines, and the // bounding box that contains them. struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. bool changed = true; // Tracks whether or not the client has declared itself ready to receive more updated. bool ready = true; examples_canvas_clientrequesteddraw::BoundingBox bounding_box; }; // An implementation of the |Instance| protocol. class InstanceImpl final : public fidl::Server<examples_canvas_clientrequesteddraw::Instance> { public: // Bind this implementation to a channel. InstanceImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<examples_canvas_clientrequesteddraw::Instance> server_end) : binding_(dispatcher, std::move(server_end), this, std::mem_fn(&InstanceImpl::OnFidlClosed)), weak_factory_(this) { // Start the update timer on startup. Our server sends one update per second ScheduleOnDrawnEvent(dispatcher, zx::sec(1)); } void OnFidlClosed(fidl::UnbindInfo info) { if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; } void AddLines(AddLinesRequest& request, AddLinesCompleter::Sync& completer) override { FX_LOGS(INFO) << "AddLines request received"; for (const auto& points : request.lines()) { FX_LOGS(INFO) << "AddLines printing line: [Point { x: " << points[1].x() << ", y: " << points[1].y() << " }, Point { x: " << points[0].x() << ", y: " << points[0].y() << " }]"; // Update the bounding box to account for the new line we've just "added" to the canvas. auto& bounds = state_.bounding_box; for (const auto& point : points) { if (point.x() < bounds.top_left().x()) { bounds.top_left().x() = point.x(); } if (point.y() > bounds.top_left().y()) { bounds.top_left().y() = point.y(); } if (point.x() > bounds.bottom_right().x()) { bounds.bottom_right().x() = point.x(); } if (point.y() < bounds.bottom_right().y()) { bounds.bottom_right().y() = point.y(); } } } // Mark the state as "dirty", so that an update is sent back to the client on the next |OnDrawn| // event. state_.changed = true; } void Ready(ReadyCompleter::Sync& completer) override { FX_LOGS(INFO) << "Ready request received"; // The client must only call `Ready() -> ();` after receiving an `-> OnDrawn();` event; if two // "consecutive" `Ready() -> ();` calls are received, this interaction has entered an invalid // state, and should be aborted immediately. if (state_.ready == true) { FX_LOGS(ERROR) << "Invalid back-to-back `Ready` requests received"; } state_.ready = true; completer.Reply(); } void handle_unknown_method( fidl::UnknownMethodMetadata<examples_canvas_clientrequesteddraw::Instance> metadata, fidl::UnknownMethodCompleter::Sync& completer) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal; } private: // Each scheduled update waits for the allotted amount of time, sends an update if something has // changed, and schedules the next update. void ScheduleOnDrawnEvent(async_dispatcher_t* dispatcher, zx::duration after) { async::PostDelayedTask( dispatcher, [&, dispatcher, after, weak = weak_factory_.GetWeakPtr()] { // Halt execution if the binding has been deallocated already. if (!weak) { return; } // Schedule the next update if the binding still exists. weak->ScheduleOnDrawnEvent(dispatcher, after); // No need to send an update if nothing has changed since the last one, or the client has // not yet informed us that it is ready for more updates. if (!weak->state_.changed || !weak->state_.ready) { return; } // This is where we would draw the actual lines. Since this is just an example, we'll // avoid doing the actual rendering, and simply send the bounding box to the client // instead. auto result = fidl::SendEvent(binding_)->OnDrawn(state_.bounding_box); if (!result.is_ok()) { return; } auto top_left = state_.bounding_box.top_left(); auto bottom_right = state_.bounding_box.bottom_right(); FX_LOGS(INFO) << "OnDrawn event sent: top_left: Point { x: " << top_left.x() << ", y: " << top_left.y() << " }, bottom_right: Point { x: " << bottom_right.x() << ", y: " << bottom_right.y() << " }"; // Reset the change and ready trackers. state_.ready = false; state_.changed = false; }, after); } fidl::ServerBinding<examples_canvas_clientrequesteddraw::Instance> binding_; CanvasState state_ = CanvasState{}; // Generates weak references to this object, which are appropriate to pass into asynchronous // callbacks that need to access this object. The references are automatically invalidated // if this object is destroyed. fxl::WeakPtrFactory<InstanceImpl> weak_factory_; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This // directory is where the outgoing FIDL protocols are installed so that they can be provided to // other components. component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher); // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle. // The startup handle is a handle provided to every component by the system, so that they can // serve capabilities (e.g. FIDL protocols) to other components. zx::result result = outgoing.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } // Register a handler for components trying to connect to // |examples.canvas.clientrequesteddraw.Instance|. result = outgoing.AddUnmanagedProtocol<examples_canvas_clientrequesteddraw::Instance>( [dispatcher](fidl::ServerEnd<examples_canvas_clientrequesteddraw::Instance> server_end) { // Create an instance of our InstanceImpl that destroys itself when the connection closes. new InstanceImpl(dispatcher, std::move(server_end)); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Instance protocol: " << result.status_string(); return -1; } // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
C++ (有線)
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.clientrequesteddraw/cpp/wire.h> #include <lib/async-loop/cpp/loop.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <charconv> #include <examples/fidl/new/canvas/client_requested_draw/cpp_wire/client/config.h> // The |EventHandler| is a derived class that we pass into the |fidl::WireClient| to handle incoming // events asynchronously. class EventHandler : public fidl::WireAsyncEventHandler<examples_canvas_clientrequesteddraw::Instance> { public: // Handler for |OnDrawn| events sent from the server. void OnDrawn( fidl::WireEvent<examples_canvas_clientrequesteddraw::Instance::OnDrawn>* event) override { ::examples_canvas_clientrequesteddraw::wire::Point top_left = event->top_left; ::examples_canvas_clientrequesteddraw::wire::Point bottom_right = event->bottom_right; FX_LOGS(INFO) << "OnDrawn event received: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; loop_.Quit(); } void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; } void handle_unknown_event( fidl::UnknownEventMetadata<examples_canvas_clientrequesteddraw::Instance> metadata) override { FX_LOGS(WARNING) << "Received an unknown event with ordinal " << metadata.event_ordinal; } explicit EventHandler(async::Loop& loop) : loop_(loop) {} private: async::Loop& loop_; }; // A helper function that takes a coordinate in string form, like "123,-456", and parses it into a // a struct of the form |{ in64 x; int64 y; }|. ::examples_canvas_clientrequesteddraw::wire::Point ParsePoint(std::string_view input) { int64_t x = 0; int64_t y = 0; size_t index = input.find(','); if (index != std::string::npos) { std::from_chars(input.data(), input.data() + index, x); std::from_chars(input.data() + index + 1, input.data() + input.length(), y); } return ::examples_canvas_clientrequesteddraw::wire::Point{.x = x, .y = y}; } using Line = ::fidl::Array<::examples_canvas_clientrequesteddraw::wire::Point, 2>; // A helper function that takes a coordinate pair in string form, like "1,2:-3,-4", and parses it // into an array of 2 |Point| structs. Line ParseLine(const std::string& action) { auto input = std::string_view(action); size_t index = input.find(':'); if (index != std::string::npos) { return {ParsePoint(input.substr(0, index)), ParsePoint(input.substr(index + 1))}; } return {}; } int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop and dispatcher. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a // |zx::result| and it must be checked for errors. zx::result client_end = component::Connect<examples_canvas_clientrequesteddraw::Instance>(); if (!client_end.is_ok()) { FX_LOGS(ERROR) << "Synchronous error when connecting to the |Instance| protocol: " << client_end.status_string(); return -1; } // Create an instance of the event handler. EventHandler event_handler(loop); // Create an asynchronous client using the newly-established connection. fidl::WireClient client(std::move(*client_end), dispatcher, &event_handler); FX_LOGS(INFO) << "Outgoing connection enabled"; std::vector<Line> batched_lines; for (const auto& action : conf.script()) { // If the next action in the script is to "PUSH", send a batch of lines to the server. if (action == "PUSH") { fidl::Status status = client->AddLines(fidl::VectorView<Line>::FromExternal(batched_lines)); if (!status.ok()) { // Check that our one-way call was enqueued successfully, and handle the error // appropriately. In the case of this example, there is nothing we can do to recover here, // except to log an error and exit the program. FX_LOGS(ERROR) << "Could not send AddLines request: " << status.error(); return -1; } batched_lines.clear(); FX_LOGS(INFO) << "AddLines request sent"; continue; } // If the next action in the script is to "WAIT", block until an |OnDrawn| event is received // from the server. if (action == "WAIT") { loop.Run(); loop.ResetQuit(); // Now, inform the server that we are ready to receive more updates whenever they are // ready for us. FX_LOGS(INFO) << "Ready request sent"; client->Ready().ThenExactlyOnce( [&](fidl::WireUnownedResult<examples_canvas_clientrequesteddraw::Instance::Ready>& result) { // Check if the FIDL call succeeded or not. if (result.ok()) { FX_LOGS(INFO) << "Ready success"; } else { FX_LOGS(ERROR) << "Could not send Ready request: " << result.error(); } // Quit the loop, thereby handing control back to the outer loop of actions being // iterated over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); continue; } // Batch a line for drawing to the canvas using the two points provided. Line line = ParseLine(action); batched_lines.push_back(line); FX_LOGS(INFO) << "AddLines batching line: [Point { x: " << line[1].x << ", y: " << line[1].y << " }, Point { x: " << line[0].x << ", y: " << line[0].y << " }]"; } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. sleep(2); return 0; }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <fidl/examples.canvas.clientrequesteddraw/cpp/wire.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async/cpp/task.h> #include <lib/component/outgoing/cpp/outgoing_directory.h> #include <lib/fidl/cpp/wire/channel.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <src/lib/fxl/macros.h> #include <src/lib/fxl/memory/weak_ptr.h> // A struct that stores the two things we care about for this example: the set of lines, and the // bounding box that contains them. struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. bool changed = true; // Tracks whether or not the client has declared itself ready to receive more updated. bool ready = true; examples_canvas_clientrequesteddraw::wire::BoundingBox bounding_box; }; // An implementation of the |Instance| protocol. class InstanceImpl final : public fidl::WireServer<examples_canvas_clientrequesteddraw::Instance> { public: // Bind this implementation to a channel. InstanceImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<examples_canvas_clientrequesteddraw::Instance> server_end) : binding_(dispatcher, std::move(server_end), this, std::mem_fn(&InstanceImpl::OnFidlClosed)), weak_factory_(this) { // Start the update timer on startup. Our server sends one update per second ScheduleOnDrawnEvent(dispatcher, zx::sec(1)); } void OnFidlClosed(fidl::UnbindInfo info) { if (info.reason() != ::fidl::Reason::kPeerClosedWhileReading) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; } void AddLines(AddLinesRequestView request, AddLinesCompleter::Sync& completer) override { FX_LOGS(INFO) << "AddLines request received"; for (const auto& points : request->lines) { FX_LOGS(INFO) << "AddLines printing line: [Point { x: " << points[1].x << ", y: " << points[1].y << " }, Point { x: " << points[0].x << ", y: " << points[0].y << " }]"; // Update the bounding box to account for the new line we've just "added" to the canvas. auto& bounds = state_.bounding_box; for (const auto& point : points) { if (point.x < bounds.top_left.x) { bounds.top_left.x = point.x; } if (point.y > bounds.top_left.y) { bounds.top_left.y = point.y; } if (point.x > bounds.bottom_right.x) { bounds.bottom_right.x = point.x; } if (point.y < bounds.bottom_right.y) { bounds.bottom_right.y = point.y; } } } // Mark the state as "dirty", so that an update is sent back to the client on the next |OnDrawn| // event. state_.changed = true; } void Ready(ReadyCompleter::Sync& completer) override { FX_LOGS(INFO) << "Ready request received"; // The client must only call `Ready() -> ();` after receiving an `-> OnDrawn();` event; if two // "consecutive" `Ready() -> ();` calls are received, this interaction has entered an invalid // state, and should be aborted immediately. if (state_.ready == true) { FX_LOGS(ERROR) << "Invalid back-to-back `Ready` requests received"; } state_.ready = true; completer.Reply(); } void handle_unknown_method( fidl::UnknownMethodMetadata<examples_canvas_clientrequesteddraw::Instance> metadata, fidl::UnknownMethodCompleter::Sync& completer) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << metadata.method_ordinal; } private: // Each scheduled update waits for the allotted amount of time, sends an update if something has // changed, and schedules the next update. void ScheduleOnDrawnEvent(async_dispatcher_t* dispatcher, zx::duration after) { async::PostDelayedTask( dispatcher, [&, dispatcher, after, weak = weak_factory_.GetWeakPtr()] { // Halt execution if the binding has been deallocated already. if (!weak) { return; } // Schedule the next update if the binding still exists. weak->ScheduleOnDrawnEvent(dispatcher, after); // No need to send an update if nothing has changed since the last one, or the client has // not yet informed us that it is ready for more updates. if (!weak->state_.changed || !weak->state_.ready) { return; } // This is where we would draw the actual lines. Since this is just an example, we'll // avoid doing the actual rendering, and simply send the bounding box to the client // instead. auto top_left = weak->state_.bounding_box.top_left; auto bottom_right = weak->state_.bounding_box.bottom_right; fidl::Status status = fidl::WireSendEvent(weak->binding_)->OnDrawn(top_left, bottom_right); if (!status.ok()) { return; } FX_LOGS(INFO) << "OnDrawn event sent: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; // Reset the change and ready trackers. state_.ready = false; weak->state_.changed = false; }, after); } fidl::ServerBinding<examples_canvas_clientrequesteddraw::Instance> binding_; CanvasState state_ = CanvasState{}; // Generates weak references to this object, which are appropriate to pass into asynchronous // callbacks that need to access this object. The references are automatically invalidated // if this object is destroyed. fxl::WeakPtrFactory<InstanceImpl> weak_factory_; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from the // client. The following initializes the loop, and obtains the dispatcher, which will be used when // binding the server implementation to a channel. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. This // directory is where the outgoing FIDL protocols are installed so that they can be provided to // other components. component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher); // The `ServeFromStartupInfo()` function sets up the outgoing directory with the startup handle. // The startup handle is a handle provided to every component by the system, so that they can // serve capabilities (e.g. FIDL protocols) to other components. zx::result result = outgoing.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } // Register a handler for components trying to connect to // |examples.canvas.clientrequesteddraw.Instance|. result = outgoing.AddUnmanagedProtocol<examples_canvas_clientrequesteddraw::Instance>( [dispatcher](fidl::ServerEnd<examples_canvas_clientrequesteddraw::Instance> server_end) { // Create an instance of our InstanceImpl that destroys itself when the connection closes. new InstanceImpl(dispatcher, std::move(server_end)); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Instance protocol: " << result.status_string(); return -1; } // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
HLCPP
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <lib/async-loop/cpp/loop.h> #include <lib/sys/cpp/component_context.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <charconv> #include <examples/canvas/clientrequesteddraw/cpp/fidl.h> #include <examples/fidl/new/canvas/client_requested_draw/hlcpp/client/config.h> // A helper function that takes a coordinate in string form, like "123,-456", and parses it into a // a struct of the form |{ in64 x; int64 y; }|. ::examples::canvas::clientrequesteddraw::Point ParsePoint(std::string_view input) { int64_t x = 0; int64_t y = 0; size_t index = input.find(','); if (index != std::string::npos) { std::from_chars(input.data(), input.data() + index, x); std::from_chars(input.data() + index + 1, input.data() + input.length(), y); } return ::examples::canvas::clientrequesteddraw::Point{.x = x, .y = y}; } using Line = ::std::array<::examples::canvas::clientrequesteddraw::Point, 2>; // A helper function that takes a coordinate pair in string form, like "1,2:-3,-4", and parses it // into an array of 2 |Point| structs. Line ParseLine(const std::string& action) { auto input = std::string_view(action); size_t index = input.find(':'); if (index != std::string::npos) { return {ParsePoint(input.substr(0, index)), ParsePoint(input.substr(index + 1))}; } return {}; } int main(int argc, const char** argv) { FX_LOGS(INFO) << "Started"; // Retrieve component configuration. auto conf = config::Config::TakeFromStartupHandle(); // Start up an async loop. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Connect to the protocol inside the component's namespace, then create an asynchronous client // using the newly-established connection. examples::canvas::clientrequesteddraw::InstancePtr instance_proxy; auto context = sys::ComponentContext::Create(); context->svc()->Connect(instance_proxy.NewRequest(dispatcher)); FX_LOGS(INFO) << "Outgoing connection enabled"; instance_proxy.set_error_handler([&loop](zx_status_t status) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; loop.Quit(); }); // Provide a lambda to handle incoming |OnDrawn| events asynchronously. instance_proxy.events().OnDrawn = [&loop](::examples::canvas::clientrequesteddraw::Point top_left, ::examples::canvas::clientrequesteddraw::Point bottom_right) { FX_LOGS(INFO) << "OnDrawn event received: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; loop.Quit(); }; instance_proxy.events().handle_unknown_event = [](uint64_t ordinal) { FX_LOGS(WARNING) << "Received an unknown event with ordinal " << ordinal; }; std::vector<Line> batched_lines; for (const auto& action : conf.script()) { // If the next action in the script is to "PUSH", send a batch of lines to the server. if (action == "PUSH") { instance_proxy->AddLines(batched_lines); batched_lines.clear(); FX_LOGS(INFO) << "AddLines request sent"; continue; } // If the next action in the script is to "WAIT", block until an |OnDrawn| event is received // from the server. if (action == "WAIT") { loop.Run(); loop.ResetQuit(); // Now, inform the server that we are ready to receive more updates whenever they are ready // for us. FX_LOGS(INFO) << "Ready request sent"; instance_proxy->Ready([&](fpromise::result<void, fidl::FrameworkErr> result) { if (result.is_error()) { // Check that our flexible two-way call was known to the server and handle the case of an // unknown method appropriately. In the case of this example, there is nothing we can do // to recover here, except to log an error and exit the program. FX_LOGS(ERROR) << "Server does not implement AddLine"; } FX_LOGS(INFO) << "Ready success"; // Quit the loop, thereby handing control back to the outer loop of actions being iterated // over. loop.Quit(); }); // Run the loop until the callback is resolved, at which point we can continue from here. loop.Run(); loop.ResetQuit(); continue; } // Batch a line for drawing to the canvas using the two points provided. Line line = ParseLine(action); batched_lines.push_back(line); FX_LOGS(INFO) << "AddLines batching line: [Point { x: " << line[1].x << ", y: " << line[1].y << " }, Point { x: " << line[0].x << ", y: " << line[0].y << " }]"; } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. sleep(2); return 0; }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <lib/async-loop/cpp/loop.h> #include <lib/async-loop/default.h> #include <lib/async/cpp/task.h> #include <lib/fidl/cpp/binding.h> #include <lib/sys/cpp/component_context.h> #include <lib/syslog/cpp/macros.h> #include <unistd.h> #include <examples/canvas/clientrequesteddraw/cpp/fidl.h> #include <src/lib/fxl/macros.h> #include <src/lib/fxl/memory/weak_ptr.h> // A struct that stores the two things we care about for this example: the set of lines, and the // bounding box that contains them. struct CanvasState { // Tracks whether there has been a change since the last send, to prevent redundant updates. bool changed = true; // Tracks whether or not the client has declared itself ready to receive more updated. bool ready = true; examples::canvas::clientrequesteddraw::BoundingBox bounding_box; }; using Line = ::std::array<::examples::canvas::clientrequesteddraw::Point, 2>; // An implementation of the |Instance| protocol. class InstanceImpl final : public examples::canvas::clientrequesteddraw::Instance { public: // Bind this implementation to an |InterfaceRequest|. InstanceImpl(async_dispatcher_t* dispatcher, fidl::InterfaceRequest<examples::canvas::clientrequesteddraw::Instance> request) : binding_(fidl::Binding<examples::canvas::clientrequesteddraw::Instance>(this)), weak_factory_(this) { binding_.Bind(std::move(request), dispatcher); // Gracefully handle abrupt shutdowns. binding_.set_error_handler([this](zx_status_t status) mutable { if (status != ZX_ERR_PEER_CLOSED) { FX_LOGS(ERROR) << "Shutdown unexpectedly"; } delete this; }); // Start the update timer on startup. Our server sends one update per second. ScheduleOnDrawnEvent(dispatcher, zx::sec(1)); } void AddLines(std::vector<Line> lines) override { FX_LOGS(INFO) << "AddLines request received"; for (const auto& points : lines) { FX_LOGS(INFO) << "AddLines printing line: [Point { x: " << points[1].x << ", y: " << points[1].y << " }, Point { x: " << points[0].x << ", y: " << points[0].y << " }]"; // Update the bounding box to account for the new line we've just "added" to the canvas. auto& bounds = state_.bounding_box; for (const auto& point : points) { if (point.x < bounds.top_left.x) { bounds.top_left.x = point.x; } if (point.y > bounds.top_left.y) { bounds.top_left.y = point.y; } if (point.x > bounds.bottom_right.x) { bounds.bottom_right.x = point.x; } if (point.y < bounds.bottom_right.y) { bounds.bottom_right.y = point.y; } } } // Mark the state as "dirty", so that an update is sent back to the client on the next // |OnDrawn| event. state_.changed = true; } void Ready(ReadyCallback callback) override { FX_LOGS(INFO) << "Ready request received"; // The client must only call `Ready() -> ();` after receiving an `-> OnDrawn();` event; if // two "consecutive" `Ready() -> ();` calls are received, this interaction has entered an // invalid state, and should be aborted immediately. if (state_.ready == true) { FX_LOGS(ERROR) << "Invalid back-to-back `Ready` requests received"; } state_.ready = true; callback(fpromise::ok()); } void handle_unknown_method(uint64_t ordinal, bool method_has_response) override { FX_LOGS(WARNING) << "Received an unknown method with ordinal " << ordinal; } private: // Each scheduled update waits for the allotted amount of time, sends an update if something // has changed, and schedules the next update. void ScheduleOnDrawnEvent(async_dispatcher_t* dispatcher, zx::duration after) { async::PostDelayedTask( dispatcher, [&, dispatcher, after, weak = weak_factory_.GetWeakPtr()] { // Halt execution if the binding has been deallocated already. if (!weak) { return; } // Schedule the next update if the binding still exists. weak->ScheduleOnDrawnEvent(dispatcher, after); // No need to send an update if nothing has changed since the last one, or the client // has not yet informed us that it is ready for more updates. if (!weak->state_.changed || !weak->state_.ready) { return; } // This is where we would draw the actual lines. Since this is just an example, we'll // avoid doing the actual rendering, and simply send the bounding box to the client // instead. auto top_left = state_.bounding_box.top_left; auto bottom_right = state_.bounding_box.bottom_right; binding_.events().OnDrawn(top_left, bottom_right); FX_LOGS(INFO) << "OnDrawn event sent: top_left: Point { x: " << top_left.x << ", y: " << top_left.y << " }, bottom_right: Point { x: " << bottom_right.x << ", y: " << bottom_right.y << " }"; // Reset the change and ready trackers. state_.ready = false; state_.changed = false; }, after); } fidl::Binding<examples::canvas::clientrequesteddraw::Instance> binding_; CanvasState state_ = CanvasState{}; // Generates weak references to this object, which are appropriate to pass into asynchronous // callbacks that need to access this object. The references are automatically invalidated // if this object is destroyed. fxl::WeakPtrFactory<InstanceImpl> weak_factory_; }; int main(int argc, char** argv) { FX_LOGS(INFO) << "Started"; // The event loop is used to asynchronously listen for incoming connections and requests from // the client. The following initializes the loop, and obtains the dispatcher, which will be // used when binding the server implementation to a channel. // // Note that unlike the new C++ bindings, HLCPP bindings rely on the async loop being attached // to the current thread via the |kAsyncLoopConfigAttachToCurrentThread| configuration. async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Create an |OutgoingDirectory| instance. // // The |component::OutgoingDirectory| class serves the outgoing directory for our component. // This directory is where the outgoing FIDL protocols are installed so that they can be // provided to other components. auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory(); // Register a handler for components trying to connect to // |examples.canvas.clientrequesteddraw.Instance|. context->outgoing()->AddPublicService( fidl::InterfaceRequestHandler<examples::canvas::clientrequesteddraw::Instance>( [dispatcher]( fidl::InterfaceRequest<examples::canvas::clientrequesteddraw::Instance> request) { // Create an instance of our |InstanceImpl| that destroys itself when the connection // closes. new InstanceImpl(dispatcher, std::move(request)); })); // Everything is wired up. Sit back and run the loop until an incoming connection wakes us up. FX_LOGS(INFO) << "Listening for incoming connections"; loop.Run(); return 0; }
前饋 Dataflow
部分通訊協定具有動態饋給轉送 Dataflow,能 資料流向主要單向流動,通常是從用戶端到伺服器 通訊協定只會在必要時同步處理兩個端點。前饋層 Dataflow 也會增加處理量,因為背景資訊切換總共較少 執行特定工作所需的程式碼
以前饋 Dataflow 來說的關鍵 是不再需要等待用戶端等待 再傳送後續訊息。適用對象 舉例來說,透過通訊協定要求管道,用戶端不必等待 伺服器才能傳回通訊協定,之後用戶端才能使用 因此效能相當卓越同樣地,由用戶端指派的 ID (請見下方說明) 也能移除 ,以便用戶端等待伺服器指派其所持有狀態的 ID 伺服器
一般而言,動態饋給轉送通訊協定會由用戶端提交序列
不必等待伺服器回應。更新後
提交這些訊息後,用戶端會確實
呼叫含有回覆的方法,例如 Commit
或 Flush
。回覆可能會
訊息空白,或可能含有相關資訊,說明已送出的提交內容
序列成功。在更複雜的通訊協定中,單向訊息
會以指令物件組合的形式表示,而非個別方法呼叫;
請參閱下方的指令聯集模式。
使用前饋 Dataflow 的通訊協定可順利搭配樂觀錯誤 處理策略與其讓伺服器以 狀態值,可幫助用戶端等待每個 訊息,改為只在方法失敗時傳送狀態回覆 不屬於用戶端的控制範圍如果用戶端傳送訊息 已知的用戶端無效 (例如參照無效 用戶端指派的 ID),請關閉連線以指出錯誤。如果 用戶端會傳送用戶端未能得知的訊息 (即用戶端) 提供表示成功或失敗的回應 (要求用戶端必須 同步處理),或記住錯誤並忽略後續相依要求 直到用戶端以某種方式同步以及從錯誤中復原為止。
範例:
protocol Canvas {
Flush() -> (struct {
code zx.Status;
});
Clear();
UploadImage(struct {
image_id uint32;
image Image;
});
PaintImage(struct {
image_id uint32;
x float32;
y float32;
});
DiscardImage(struct {
image_id uint32;
});
PaintSmileyFace(struct {
x float32;
y float32;
});
PaintMoustache(struct {
x float32;
y float32;
});
};
FIDL 方案:大小限制
FIDL 向量和字串可能有指定限制的大小限制 代表該類型可包含的成員數量就向量而言 儲存在向量中的元素數量,對於字串則是指 字串內含的位元組數。
強烈建議使用大小限制,因為這會設定上限 無限制的型別。
鍵/值儲存庫的實用作業是依序疊代,也就是 指定鍵,即可傳回 (通常為分頁) 出現在 。
原因
在 FIDL 中,最好的做法是使用疊代器,而疊代器一般會實作為 執行這項疊代作業的不同通訊協定使用不同的 通訊協定,因此獨立管道有許多優點,包括 將透過應用程式執行的其他作業之疊代提取要求解交錯 主要通訊協定。
通訊協定 P
管道連線的用戶端和伺服器端可
以 FIDL 資料類型表示,即 client_end:P
和 server_end:P
。
。這些類型統稱為「通訊協定結束」。
代表將 FIDL 用戶端與其連線的另一個 (非 @discoverable
) 方法
對應的伺服器:複寫現有的 FIDL 連線!
通訊協定結束是一般 FIDL 概念的特定執行個體:資源 類型。資源類型是為了包含需要的 FIDL 控制代碼 類型的使用方式額外限制。類型必須一律為 因為基礎資源會由其他能力管理工具進行中介 (通常是 Zircon 核心)。透過簡單的記憶體內複製這類資源 答案是不可能的,如果沒有經理,就不可能。為了防止重複 FIDL 中的所有資源類型一律僅限移動。
最後,Iterator
通訊協定本身的 Get()
方法會使用
傳回酬載的大小限制。這會限制
以單一提取式傳輸流量進行傳輸,允許以一定程度的資源用量
控管功能也會建立自然的分頁界線:而非大型傾印
因此伺服器只需準備小批資料
時間。
實作
FIDL、CML 和領域介面定義如下:
FIDL
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library examples.keyvaluestore.additerator; /// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That /// is, it must start with a letter, end with a letter or number, contain only letters, numbers, /// periods, and slashes, and be between 4 and 64 characters long. type Item = struct { key string:128; value vector<byte>:64000; }; /// An enumeration of things that may go wrong when trying to write a value to our store. type WriteError = flexible enum { UNKNOWN = 0; INVALID_KEY = 1; INVALID_VALUE = 2; ALREADY_EXISTS = 3; }; /// An enumeration of things that may go wrong when trying to create an iterator. type IterateConnectionError = flexible enum { /// The starting key was not found. UNKNOWN_START_AT = 1; }; /// A key-value store which supports insertion and iteration. @discoverable open protocol Store { /// Writes an item to the store. flexible WriteItem(struct { attempt Item; }) -> () error WriteError; /// Iterates over the items in the store, using lexicographic ordering over the keys. /// /// The [`iterator`] is [pipelined][pipelining] to the server, such that the client can /// immediately send requests over the new connection. /// /// [pipelining]: https://fuchsia.dev/fuchsia-src/development/api/fidl?hl=en#request-pipelining flexible Iterate(resource struct { /// If present, requests to start the iteration at this item. starting_at string:<128, optional>; /// The [`Iterator`] server endpoint. The client creates both ends of the channel and /// retains the `client_end` locally to use for pulling iteration pages, while sending the /// `server_end` off to be fulfilled by the server. iterator server_end:Iterator; }) -> () error IterateConnectionError; }; /// An iterator for the key-value store. Note that this protocol makes no guarantee of atomicity - /// the values may change between pulls from the iterator. Unlike the `Store` protocol above, this /// protocol is not `@discoverable`: it is not independently published by the component that /// implements it, but rather must have one of its two protocol ends transmitted over an existing /// FIDL connection. /// /// As is often the case with iterators, the client indicates that they are done with an instance of /// the iterator by simply closing their end of the connection. /// /// Since the iterator is associated only with the Iterate method, it is declared as closed rather /// than open. This is because changes to how iteration works are more likely to require replacing /// the Iterate method completely (which is fine because that method is flexible) rather than /// evolving the Iterator protocol. closed protocol Iterator { /// Gets the next batch of keys. /// /// The client pulls keys rather than having the server proactively push them, to implement /// [flow control][flow-control] over the messages. /// /// [flow-control]: /// https://fuchsia.dev/fuchsia-src/development/api/fidl?hl=en#prefer_pull_to_push strict Get() -> (struct { /// A list of keys. If the iterator has reached the end of iteration, the list will be /// empty. The client is expected to then close the connection. entries vector<string:128>:10; }); };
CML
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/client_bin", }, use: [ { protocol: "examples.keyvaluestore.additerator.Store" }, ], config: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A key to iterate from, after all items in `write_items` have been written. iterate_from: { type: "string", max_size: 64, }, }, }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { include: [ "syslog/client.shard.cml" ], program: { runner: "elf", binary: "bin/server_bin", }, capabilities: [ { protocol: "examples.keyvaluestore.additerator.Store" }, ], expose: [ { protocol: "examples.keyvaluestore.additerator.Store", from: "self", }, ], }
領域
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.keyvaluestore.additerator.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
用戶端和伺服器實作項目現在可以使用任何支援的語言編寫:
Rust
用戶端
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{Context as _, Error}; use config::Config; use fuchsia_component::client::connect_to_protocol; use std::{thread, time}; use fidl::endpoints::create_proxy; use fidl_examples_keyvaluestore_additerator::{Item, IteratorMarker, StoreMarker}; use futures::join; #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Load the structured config values passed to this component at startup. let config = Config::take_from_startup_handle(); // Use the Component Framework runtime to connect to the newly spun up server component. We wrap // our retained client end in a proxy object that lets us asynchronously send `Store` requests // across the channel. let store = connect_to_protocol::<StoreMarker>()?; println!("Outgoing connection enabled"); // This client's structured config has one parameter, a vector of strings. Each string is the // path to a resource file whose filename is a key and whose contents are a value. We iterate // over them and try to write each key-value pair to the remote store. for key in config.write_items.into_iter() { let path = format!("/pkg/data/{}.txt", key); let value = std::fs::read_to_string(path.clone()) .with_context(|| format!("Failed to load {path}"))?; match store.write_item(&Item { key: key, value: value.into_bytes() }).await? { Ok(_) => println!("WriteItem Success"), Err(err) => println!("WriteItem Error: {}", err.into_primitive()), } } if !config.iterate_from.is_empty() { // This helper creates a channel, and returns two protocol ends: the `client_end` is already // conveniently bound to the correct FIDL protocol, `Iterator`, while the `server_end` is // unbound and ready to be sent over the wire. let (iterator, server_end) = create_proxy::<IteratorMarker>()?; // There is no need to wait for the iterator to connect before sending the first `Get()` // request - since we already hold the `client_end` of the connection, we can start queuing // requests on it immediately. let connect_to_iterator = store.iterate(Some(config.iterate_from.as_str()), server_end); let first_get = iterator.get(); // Wait until both the connection and the first request resolve - an error in either case // triggers an immediate resolution of the combined future. let (connection, first_page) = join!(connect_to_iterator, first_get); // Handle any connection error. If this has occurred, it is impossible for the first `Get()` // call to have resolved successfully, so check this error first. if let Err(err) = connection.context("Could not connect to Iterator")? { println!("Iterator Connection Error: {}", err.into_primitive()); } else { println!("Iterator Connection Success"); // Consecutively repeat the `Get()` request if the previous response was not empty. let mut entries = first_page.context("Could not get page from Iterator")?; while !&entries.is_empty() { for entry in entries.iter() { println!("Iterator Entry: {}", entry); } entries = iterator.get().await.context("Could not get page from Iterator")?; } } } // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the // referenced bug has been resolved, we can remove the sleep. thread::sleep(time::Duration::from_secs(2)); Ok(()) }
伺服器
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use anyhow::{Context as _, Error}; use fuchsia_component::server::ServiceFs; use futures::prelude::*; use lazy_static::lazy_static; use regex::Regex; use fidl_examples_keyvaluestore_additerator::{ Item, IterateConnectionError, IteratorRequest, IteratorRequestStream, StoreRequest, StoreRequestStream, WriteError, }; use fuchsia_async as fasync; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::ops::Bound::*; use std::sync::{Arc, Mutex}; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile"); } /// Handler for the `WriteItem` method. fn write_item(store: &mut BTreeMap<String, Vec<u8>>, attempt: Item) -> Result<(), WriteError> { // Validate the key. if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) { println!("Write error: INVALID_KEY, For key: {}", attempt.key); return Err(WriteError::InvalidKey); } // Validate the value. if attempt.value.is_empty() { println!("Write error: INVALID_VALUE, For key: {}", attempt.key); return Err(WriteError::InvalidValue); } // Write to the store, validating that the key did not already exist. match store.entry(attempt.key) { Entry::Occupied(entry) => { println!("Write error: ALREADY_EXISTS, For key: {}", entry.key()); Err(WriteError::AlreadyExists) } Entry::Vacant(entry) => { println!("Wrote value at key: {}", entry.key()); entry.insert(attempt.value); Ok(()) } } } /// Handler for the `Iterate` method, which deals with validating that the requested start position /// exists, and then sets up the asynchronous side channel for the actual iteration to occur over. fn iterate( store: Arc<Mutex<BTreeMap<String, Vec<u8>>>>, starting_at: Option<String>, stream: IteratorRequestStream, ) -> Result<(), IterateConnectionError> { // Validate that the starting key, if supplied, actually exists. if let Some(start_key) = starting_at.clone() { if !store.lock().unwrap().contains_key(&start_key) { return Err(IterateConnectionError::UnknownStartAt); } } // Spawn a detached task. This allows the method call to return while the iteration continues in // a separate, unawaited task. fasync::Task::spawn(async move { // Serve the iteration requests. Note that access to the underlying store is behind a // contended `Mutex`, meaning that the iteration is not atomic: page contents could shift, // change, or disappear entirely between `Get()` requests. stream .map(|result| result.context("failed request")) .try_fold( match starting_at { Some(start_key) => Included(start_key), None => Unbounded, }, |mut lower_bound, request| async { match request { IteratorRequest::Get { responder } => { println!("Iterator page request received"); // The `page_size` should be kept in sync with the size constraint on // the iterator's response, as defined in the FIDL protocol. static PAGE_SIZE: usize = 10; // An iterator, beginning at `lower_bound` and tracking the pagination's // progress through iteration as each page is pulled by a client-sent // `Get()` request. let held_store = store.lock().unwrap(); let mut entries = held_store.range((lower_bound.clone(), Unbounded)); let mut current_page = vec![]; for _ in 0..PAGE_SIZE { match entries.next() { Some(entry) => { current_page.push(entry.0.clone()); } None => break, } } // Update the `lower_bound` - either inclusive of the next item in the // iteration, or exclusive of the last seen item if the iteration has // finished. This `lower_bound` will be passed to the next request // handler as its starting point. lower_bound = match entries.next() { Some(next) => Included(next.0.clone()), None => match current_page.last() { Some(tail) => Excluded(tail.clone()), None => lower_bound, }, }; // Send the page. At the end of this scope, the `held_store` lock gets // dropped, and therefore released. responder.send(¤t_page).context("error sending reply")?; println!("Iterator page sent"); } } Ok(lower_bound) }, ) .await .ok(); }) .detach(); Ok(()) } /// Creates a new instance of the server. Each server has its own bespoke, per-connection instance /// of the key-value store. async fn run_server(stream: StoreRequestStream) -> Result<(), Error> { // Create a new in-memory key-value store. The store will live for the lifetime of the // connection between the server and this particular client. // // Note that we now use an `Arc<Mutex<BTreeMap>>`, replacing the previous `RefCell<HashMap>`. // The `BTreeMap` is used because we want an ordered map, to better facilitate iteration. The // `Arc<Mutex<...>>` is used because there are now multiple async tasks accessing the: one main // task which handles communication over the protocol, and one additional task per iterator // protocol. `Arc<Mutex<...>>` is the simplest way to synchronize concurrent access between // these racing tasks. let store = &Arc::new(Mutex::new(BTreeMap::<String, Vec<u8>>::new())); // Serve all requests on the protocol sequentially - a new request is not handled until its // predecessor has been processed. stream .map(|result| result.context("failed request")) .try_for_each(|request| async { // Match based on the method being invoked. match request { StoreRequest::WriteItem { attempt, responder } => { println!("WriteItem request received"); // The `responder` parameter is a special struct that manages the outgoing reply // to this method call. Calling `send` on the responder exactly once will send // the reply. responder .send(write_item(&mut store.clone().lock().unwrap(), attempt)) .context("error sending reply")?; println!("WriteItem response sent"); } StoreRequest::Iterate { starting_at, iterator, responder } => { println!("Iterate request received"); // The `iterate` handler does a quick check to see that the request is valid, // then spins up a separate worker task to serve the newly minted `Iterator` // protocol instance, allowing this call to return immediately and continue the // request stream with other work. responder .send(iterate(store.clone(), starting_at, iterator.into_stream()?)) .context("error sending reply")?; println!("Iterate response sent"); } // StoreRequest::_UnknownMethod { ordinal, .. } => { println!("Received an unknown method with ordinal {ordinal}"); } } Ok(()) }) .await } // A helper enum that allows us to treat a `Store` service instance as a value. enum IncomingService { Store(StoreRequestStream), } #[fuchsia::main] async fn main() -> Result<(), Error> { println!("Started"); // Add a discoverable instance of our `Store` protocol - this will allow the client to see the // server and connect to it. let mut fs = ServiceFs::new_local(); fs.dir("svc").add_fidl_service(IncomingService::Store); fs.take_and_serve_directory_handle()?; println!("Listening for incoming connections"); // The maximum number of concurrent clients that may be served by this process. const MAX_CONCURRENT: usize = 10; // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit. fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| { run_server(stream).unwrap_or_else(|e| println!("{:?}", e)) }) .await; Ok(()) }
C++ (自然)
用戶端
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
伺服器
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++ (有線)
用戶端
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
伺服器
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
用戶端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
伺服器
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
隱私保護設計
通訊協定中的用戶端和伺服器經常可以存取不同的 機密資料。隱私權或安全性問題可能是無意造成 透過通訊協定洩漏過多資料。
設計通訊協定時,請特別留意通訊協定中的欄位 顯示:
- 包含姓名、電子郵件地址等個人識別資訊 或付款資料
- 由使用者提供,因此可能包含個人資訊。範例 納入裝置名稱和留言欄位。
- 充當專屬 ID,可與各廠商、使用者、 或重設裝置。例如序號、MAC 位址、IP 以及全域帳戶 ID
我們會徹底審查這些類型的欄位,以及通訊協定的可用性 這類規則可能會受到限制確認通訊協定不包含 不必要的資訊。
如果 API 用途需要個人或可連結的資料和其他用途 沒有,請考慮使用兩個不同的通訊協定,以便存取 對於敏感用途而言,我們可能會另外控管。
請參考以下假設情況: API 設計選擇:
範例 1 - 週邊裝置控制 API 中的序號
假設有一個週邊裝置控制 API,其中包含 USB 序號 例如周邊裝置序號不含個人資料,但非常 明確建立關聯的固定 ID附上序號 API 會導致許多隱私權疑慮:
- 凡是可存取 API 的用戶端,都可能與不同帳戶相關聯 使用相同的 Fuchsia 裝置。
- 凡是可存取 API 的用戶端,都可能與不同的人物角色建立關聯 帳戶設定
- 不同的軟體供應商可互相交流,瞭解他們是否採用 相同或相同的裝置上的使用者
- 如果週邊裝置在裝置間移動,則任何可存取 API 的用戶端 可將週邊裝置共用的一組裝置和使用者建立關聯。
- 如果販售週邊裝置,可存取 API 的用戶端可能會 週邊裝置的新擁有者和新擁有者
- 部分製造商會將資訊編碼成序號。這可能有助於 擁有 API 存取權的用戶端,可推斷使用者購買 。
在此範例中,序號的用意是允許用戶端偵測 即可再次連線。如要達成這項意圖,就需要 固定 ID,但不需要全域 ID。不同的客戶 不需要收到相同的 ID,相同的用戶端不需要 會在不同的 Fuchsia 裝置上接收相同的 ID,以及這類 ID 不需要在恢復原廠設定事件中保持不變。
在此例中,建議的替代方案是傳送僅 保證在單一裝置上為單一用戶端保持穩定。這個 ID 可能是周邊裝置序號的雜湊值, 裝置 ID 和連線的路徑名稱。
範例 2 - 裝置設定 API 中的裝置名稱
假設提供裝置設定 API,其中包含用於存取位置資訊的手機型號 協助設定裝置在多數情況下,手機的型號是由 原始設備製造商 (OEM),但有些手機回報的機型是由使用者提供的裝置名稱。這個 會導向許多模型字串,且這些字串包含模型的真實名字或假名 使用者。因此,這個 API 可能會透過身分或跨身分連結使用者 裝置。罕見或預先發布的模型字串可能會洩漏機密資訊 即便使用者未提供。
在某些情況下,適合使用模型字串,但限制 哪些用戶端可以存取 API或者,API 可以使用 使用者完全不受控制,例如製造商字串。其他 替代方案是將模型字串與 以及將罕見型號字串換成通用字串。
用戶端指派的 ID
通訊協定允許用戶端操縱多個 伺服器設計物件系統時,一般方法是 為每個物件保留不同狀態的連貫性物件 伺服器但在設計通訊協定時,每個項目都要使用不同的物件 狀態存在幾個缺點
為每個使用核心的邏輯物件分別建立通訊協定執行個體 因為每個執行個體都需要單獨的管道物件。 每個執行個體維護一個個別的 FIFO 訊息佇列。使用 因此,訊息如果分別出現在每個邏輯物件中 則可以互相依順序重新排序 用戶端和伺服器之間順序異常的互動。
用戶端指派的 ID 模式可確保
用戶端會將 uint32
或 uint64
ID 指派給伺服器保留的物件。
用戶端與伺服器之間交換的所有訊息都已程序化
透過單一通訊協定執行個體,以提供一致的 FIFO 順序
觀眾對於整體互動的反應
由用戶端 (而非伺服器) 指派 ID 動態饋給轉送 Dataflow,因為用戶端可以為物件指派 ID 然後立即對該物件執行作業,不必等待伺服器 並附上物件的 ID。在這個模式中,ID 有效 僅在目前連線範圍內,通常是 0 ID 並保留為 100} 紙板安全性注意事項:用戶端不得 視為 ID 可能會導致位址空間的版面配置外洩
用戶端指派的 ID 模式有一些缺點。例如: 客戶的編寫作業較為困難,因為客戶必須自己管理 識別碼開發人員通常會想建立用戶端程式庫 為服務採用物件導向的立面,隱藏複雜的管理 識別碼,本身的反模式 (請參閱用戶端 ID 程式庫)。
發出強烈信號,建議您建立個別的通訊協定執行個體, 代表物件 (而非使用用戶端指派的 ID), 使用核心的物件能力系統 物件。舉例來說,如果您希望用戶端可以與物件互動 但不希望用戶端與其他物件互動 建立單獨的通訊協定執行個體,意味著您可以使用基礎管道 以控制該物件的存取能力
指令聯集
在使用前饋 Dataflow 的通訊協定中,用戶端通常會傳送許多單向資料 郵件傳送至伺服器,然後傳送雙向同步郵件。如果 通訊協定涉及大量郵件, 傳送訊息可能會變得明顯在這些情況下,請考慮使用 指令聯集模式,將多個指令批次處理成單一訊息。
在這個模式中,用戶端會傳送指令的 vector
,而不是傳送
或是為每個指令建立個別訊息這個向量內含所有
,而伺服器會使用 union 標記做為指令的選取器
除了使用方法序數之外,還會傳送:
type PokeCmd = struct {
x int32;
y int32;
};
type ProdCmd = struct {
message string:64;
};
type MyCommand = strict union {
1: poke PokeCmd;
2: prod ProdCmd;
};
protocol HighVolumeSink {
Enqueue(struct {
commands vector<MyCommand>;
});
Commit() -> (struct {
result MyStatus;
});
};
一般而言,用戶端會將指令緩衝於本機的位址空間,並傳送 將這些檔案分批傳送至伺服器用戶端應在伺服器中清除該批次資料 才能達到管道容量限制 (以位元組為單位)
針對訊息量較高的通訊協定,可考慮在
資料層的 zx.Handle:VMO
,以及zx.Handle:FIFO
控制層這類通訊協定會為用戶端帶來較高的實作負擔
和伺服器的可用性,但在需要最大效能時會是不錯的選擇。例如:
區塊裝置通訊協定會使用這個方法來最佳化效能。
分頁
FIDL 訊息通常是透過管道傳送,且每個管道的 大小在多數情況下,訊息大小上限足以傳送 資料量不多,但在某些情況下,傳輸大量資料 (或 甚至不受限) 資料量傳輸龐大或無界限的其中一種方式 使用分頁模式。
分頁寫入作業
有一個簡單的方式將寫入作業分頁寫入伺服器 以傳送多則訊息中的資料,並進行「敲定」方法來 處理您傳送的資料:
protocol Foo1 {
AddBars(resource struct {
bars vector<client_end:Bar>;
});
UseTheBars() -> (struct {
args Args;
});
};
舉例來說,fuchsia.process.Launcher
會使用這個模式來
用戶端會傳送任意數量的環境變數
這個模式是更複雜的版本,可建立通訊協定 代表該交易,通常稱為「撕除通訊協定」:
protocol BarTransaction {
Add(resource struct {
bars vector<client_end:Bar>;
});
Commit() -> (struct {
args Args;
});
};
protocol Foo2 {
StartBarTransaction(resource struct {
transaction server_end:BarTransaction;
});
};
當用戶端需要執行多項作業時,這個做法就能派上用場
同時將寫入作業分成多個訊息,也會失去不可分割性。
請注意,BarTransaction
不需要 Abort
方法。較佳
取消交易的做法則是讓用戶端關閉
BarTransaction
通訊協定。
分頁讀取
要讓伺服器傳送讀取資料的簡單方法就是讓伺服器 使用事件對單一要求發出多個回應:
protocol EventBasedGetter {
GetBars();
-> OnBars(resource struct {
bars vector<client_end:Bar>;
});
-> OnBarsDone();
};
視網域專屬的語意而定,這個模式可能也需要 發出第二個事件,通知您伺服器完成傳送資料的時間。這個方法 這個方法適用於簡單的案例,但有一些資源調度問題。例如: 通訊協定,如果用戶端未控制外部流量和用戶端,則用戶端無法停止 而用戶端不再需要額外的資料 (光是關閉整個 通訊協定)。
更可靠的方法是使用拆除通訊協定來建立疊代器:
protocol BarIterator {
GetNext() -> (resource struct {
bars vector<client_end:Bar>;
});
};
protocol ChannelBasedGetter {
GetBars(resource struct {
iterator server_end:BarIterator;
});
};
呼叫 GetBars
後,用戶端會使用通訊協定要求管道將佇列排入佇列
發出第一個 GetNext
呼叫。之後,用戶端會不斷呼叫
GetNext
用於從伺服器讀取其他資料,繫結
未完成待處理的 GetNext
訊息,提供流量控制。請注意,
Iterator 不需要「done」因為伺服器可
完成後,即可關閉疊代器
另一個將讀取分頁分頁的方法是使用權杖。在這個方法中, 伺服器會將疊代器狀態以不透明權杖的形式儲存在用戶端 而用戶端會在每次讀取部分文字時,將權杖傳回伺服器:
type Token = struct {
opaque array<uint8, 16>;
};
protocol TokenBasedGetter {
/// If token is null, fetch the first N entries. If token is not null,
/// return the N items starting at token. Returns as many entries as it can
/// in results and populates next_token if more entries are available.
GetEntries(struct {
token box<Token>;
}) -> (struct {
entries vector<Entry>;
next_token box<Token>;
});
};
這種模式特別吸引人,因為伺服器可託管所有主機 因此不再需要維護分頁狀態 分頁狀態伺服器應記錄用戶端 保留權杖,並在通訊協定的各個執行個體中重複使用。安全性 注意:無論是哪一種情況,伺服器都必須驗證用戶端提供的權杖。 ,確保用戶端的存取權僅限於其所屬分頁結果,並 未包含涉及其他用戶端的結果。
事件配對關聯
使用用戶端指派的 ID 時,用戶端會識別保存在
伺服器使用只在其背景資訊上有意義的 ID
連線至伺服器然而,部分用途需要關聯物件
。例如,在 fuchsia.ui.scenic
中,用戶端經常會
透過用戶端指派的 ID,在場景圖中顯示節點。不過
從其他程序匯入節點需要將參照連結至該程序
跨程序邊界的節點
事件配對關聯模式會透過以動態饋給轉送的方式解決這個問題
來提供必要的安全防護機制。首先,
要匯出物件的用戶端會建立 zx::eventpair
,並傳送
以及用戶端指派的 ID,
物件的另一個部分接著,用戶端會將另一個扭曲的事件傳送到
用戶端,藉此將事件轉送到伺服器 (擁有自己的用戶端指派)
目前共用物件的識別碼:
protocol Exporter {
ExportThing(resource struct {
client_assigned_id uint32;
export_token zx.Handle:EVENTPAIR;
});
};
protocol Importer {
ImportThing(resource struct {
some_other_client_assigned_id uint32;
import_token zx.Handle:EVENTPAIR;
});
};
為物件建立關聯時,伺服器會呼叫 zx_object_get_info
與
ZX_INFO_HANDLE_BASIC
和下列來源的 koid
和 related_koid
屬性相符:
形成事件物件
取消活動配對
使用拆卸通訊協定交易時,用戶端可以取消長時間執行的作業
用戶端的通訊協定。伺服器應監聽
ZX_CHANNEL_PEER_CLOSED
並取消交易,以免浪費資源。
在沒有專屬管道的作業中,也有類似的用途。
舉例來說,fuchsia.net.http.Loader
通訊協定的 Fetch
方法會
發出 HTTP 要求。伺服器使用 HTTP
HTTP 交易完成後就會回應
時間。用戶端沒有明顯的方式取消要求,且在極短時間內
關閉整個 Loader
通訊協定,可能會取消其他
要求。
如要解決這項問題,事件配對取消模式會要求用戶端
加入 zx::eventpair
中的其中一個切線事件做為
方法。伺服器會監聽 ZX_EVENTPAIR_PEER_CLOSED
並取消
作業。使用 zx::eventpair
的成效優於
使用 zx::event
或其他信號,因為 zx::eventpair
方法
隱含處理用戶端當機或停止運作的情形。
如開序事件,核心會產生 ZX_EVENTPAIR_PEER_CLOSED
就會遭到刪除。
通訊協定空白
有時空白的通訊協定可以提供值。例如
建立物件可能也會收到 server_end:FooController
參數。
呼叫端會提供這個空白通訊協定的實作:
protocol FooController {};
FooController
未包含任何控制已建立項目的方法
物件,但伺服器可以使用 ZX_CHANNEL_PEER_CLOSED
信號
觸發物件刪除作業的通訊協定日後的通訊協定
可以透過控制已建立物件的方法擴充。
控管類似設定的資料
伺服器通常會公開用戶端可修改的設定。如果偏好使用
table
表示這些設定。例如:fuchsia.accessibility
程式庫會定義:
type Settings = table {
1: magnification_enabled bool;
2: magnification_zoom_factor float32;
3: screen_reader_enabled bool;
4: color_inversion_enabled bool;
5: color_correction ColorCorrection;
6: color_adjustment_matrix array<float32, 9>;
};
(為方便閱讀,省略意見)。
有多種方法可讓客戶更改這些設定。
「部分更新」方法公開的 Update
方法
,且只有在這些欄位顯示這些欄位時,才會變更
值。
protocol TheManagerOfSomeSorts {
/// Description how the update modifies the behavior.
///
/// Only fields present in the settings value will be changed.
Update(struct {
settings Settings;
}) -> (struct {
args Args;
});
};
「取代」方法公開的 Replace
方法
設定值,然後將設定變更為新提供的值。
protocol TheManagerOfOtherSorts {
/// Description how the override modifies the behavior.
///
/// This replaces the setting.
Replace(struct {
settings Settings;
}) -> (struct {
args Args;
});
};
應避免的事項:
請避免使用動詞
Set
或Override
進行部分更新或 取代方式,因為提供的語意不明確。避免透過個別方法更新設定包含
SetMagnificationEnabled
。這種多樣化的方法比較困難 但呼叫端很少想更新單一值。請避免移除含有魔法值的設定,例如
-1
。而是應該將 這項設定 (沒有該設定欄位)。
參照聯集變化版本和資料表欄位
參照類型的欄位 (例如參照一個或 資料表中的多個欄位,或是參照特定的聯集變體。
舉例來說,假設某個 API 以 table
形式提供中繼資料,其中包含許多欄位。如果這是
通常能有單一機制
用來向傳送者指明此中繼資料中有哪些欄位
因此能避免傳送多餘的欄位
收件者。在這類情況下,建立成員比對的平行 bits
使用 table
欄位的一對一模型,能做為建構基礎的強大基礎
您的 API:
type MetadataFields = flexible bits {
VERSION = 0b1;
COUNT = 0b10;
};
type Metadata = table {
1: version string;
2: count uint32;
// ...
};
protocol MetadataGetter {
Get(struct {
fields MetadataFields;
}) -> (struct {
metadata Metadata;
});
};
現在,請考慮使用指令聯集。在複雜的情境中
伺服器可能會希望能夠說明自身支援的指令。此時
平行的 enum
,其中成員會一對一與
union
可以做為建構 API 的強大基礎:
type MyCommandVariant = strict enum {
POKE = 1;
PROD = 2;
// ...
};
protocol HighVolumeSinkContinued {
SupportedCommands() -> (struct {
supported_commands vector<MyCommandVariant>;
});
};
請注意,雖然您可能會很想使用 bits
值來代表資料集
這些指令會造成您接下來需要做更困難的選擇
API 不斷演進,因此您需要參照特定指令,並採用 enum
自然流暢如果您已使用 bits
值開始,則您現在要面對
造成兩個不良選擇
加入
enum
,表示現在可透過兩種方式 欄位,也可能是用戶端程式碼中的轉換問題 (從 表示法);或繼續使用
bits
,並遵守僅設定一位元的限制 並對應至已設定的特定位元 很麻煩。
簡單來說,針對「table
」:
將
bits
命名為table
的名稱,以及後置字串Fields
(複數)。每個成員值都應為序數索引的位元,即1 << (ordinal - 1)
。跟
union
的建議一樣,想根據彈性調整 介於bits
和table
之間,亦即 FIDL 只支援彈性 資料表,bits
必須為flexible
。
針對 union
:
為
enum
命名,列出依據union
名稱列出所有變化版本,以及 後置字串Variant
(單數)。每個成員值都應為序數 不同變數的結構比對
union
和enum
之間的彈性,例如union
為strict
,則enum
也必須為strict
。
反模式
本節說明幾種反模式:經常提供 負值。學習辨別這些模式是邁向成功的第一步 才能避免以不當的方式使用資源
推送設定:盡量避免
Fuchsia 平台通常偏好提取語意。語意 元件提供重要範例;技術能力 它會假設是由元件提取,這種方式能讓元件啟動延遲, 根據能力的有向圖推斷元件關閉順序 路徑。
使用 FIDL 通訊協定將設定從單一通訊協定推送到產品的設計 另一個元件很吸引人,因為兩者比較簡單。這個 出現以下情況:元件 A 將政策推送到 B 的元件來處理 B 的業務 邏輯。這會導致平台誤解依附元件關係:A B 的函式不會自動啟動,因此 A 可能會關閉 表示 B 已完成商業邏輯的執行作業而這又能進一步吸引待開發客戶 相關解決辦法含有弱依附元件,同時造成人類反轉 產生所需行為如果政策設定 而非推送狀態
如果可以,請優先使用提取政策的設計,而非推送政策。
用戶端程式庫:請謹慎使用
在理想情況下,用戶端介面中包含以 FIDL 定義的通訊協定, FIDL 編譯器產生的特定語言用戶端程式庫。 雖然這種做法讓 Fuchsia 為 有時此通訊協定太低,無法直接編寫程式。 在這種情況下,提供以手動方式編寫的用戶端程式庫可以 相同基礎通訊協定的介面,但更易於使用。
舉例來說,fuchsia.io
有一個用戶端程式庫 libfdio.so
,當中提供
類似 POSIX 的前端遷移至通訊協定。預期採用 POSIX 形式的用戶端
open
/close
/read
/write
介面可連結至 libfdio.so
並說話
fuchsia.io
通訊協定。此用戶端程式庫
提供價值,因為程式庫會根據現有的程式庫介面進行調整
以及基本的 FIDL 通訊協定
另一種提供正值的用戶端程式庫是架構。A 罩杯
架構是廣泛的用戶端程式庫,可以為大型的
應用程式的部分細節架構通常會提供
以及不同組合的抽象化機制舉例來說,Flutter 是
可做為
fuchsia.ui
通訊協定。
無論通訊協定是否 與相關聯的用戶端程式庫指派一個獨立的軟體工程師團隊 才能瞭解如何直接使用這項通訊協定 不必對用戶端程式庫進行反向工程。當 通訊協定包含用戶端程式庫,也就是通訊協定的部分 鼓勵您建立用戶端程式庫。 因為
用戶端程式庫的主要困難之處在於需要維護 每個目標語言,這通常代表缺少用戶端程式庫 (或 。用戶端程式庫 導致每個用戶端與 使用相同方式隨著伺服器數量不斷增加 並在用戶端偏離了 用戶端程式庫使用的模式
如要在 Fuchsia SDK 中加入用戶端程式庫,應提供 程式庫實作。
服務中心:請謹慎使用
服務中心是一種 Discoverable
通訊協定,可讓您
其他通訊協定的數量 (通常具有明確名稱):
// BAD
@discoverable
protocol ServiceHub {
GetFoo(resource struct {
foo server_end:Foo;
});
GetBar(resource struct {
bar server_end:Bar;
});
GetBaz(resource struct {
baz server_end:Baz;
});
GetQux(resource struct {
qux server_end:Qux;
});
};
尤其是在無狀態的情況下,ServiceHub
通訊協定不會提供太多內容
值大於直接讓個別通訊協定服務可供存取:
@discoverable
protocol Foo {};
@discoverable
protocol Bar {};
@discoverable
protocol Baz {};
@discoverable
protocol Qux {};
無論採用哪一種方式,用戶端都可以與列舉服務建立連線。 若是後者,則用戶端可透過一般的 探索服務使用一般 機制可讓核心平台對探索作業套用適當的政策。
不過,服務中心在某些情況下非常實用。舉例來說, 通訊協定為有狀態或經由某些程序取得 就如同正常服務探索一般,通訊協定可以藉由轉移程序來提供價值 提供給取得的服務再舉一個例子,如果 取得服務時會採用額外參數,那麼通訊協定可以 以提供特定價值 免費 Google Cloud 服務
過度以物件為導向的設計:否
某些程式庫會為 但這種方法有一些缺點:
不同通訊協定執行個體之間的訊息順序未定義。 透過單一通訊協定傳送的訊息會依 FIFO 順序處理 (在 但透過不同管道傳送的訊息當 用戶端和伺服器之間的互動會分散在許多管道上 可能會導致非預期的錯誤 再次訂購。
每個通訊協定執行個體在核心資源方面的費用, 佇列和排程雖然 Fuchsia 您可 而費用會合乎整個系統中 大量物件,為系統中的每個邏輯物件建立模型 會對系統造成巨大負擔
由於應用程式擁有 錯誤和拆解狀態隨著通訊協定的數量逐漸增加 操作方法。使用單一通訊協定時 執行個體,用戶端和伺服器都能乾淨地關閉 。如果使用多個通訊協定執行個體 可以得知互動部分關閉或 雙方的關閉狀態檢視不一致。
- 跨通訊協定邊界的協調比單一界線複雜 因為必須允許多個通訊協定 不同的通訊協定可能會用於 他們可能不完全信任彼此
不過,如要將功能區分為多個 通訊協定:
提供個別的通訊協定對安全性有益, 用戶端可能只能存取其中一種通訊協定 受制於其與伺服器的互動。
將個別的通訊協定與不同的執行緒搭配使用,也更容易使用。適用對象 例如,有一種通訊協定 可能會繫結至不同執行緒 可能繫結至另一個執行緒。
用戶端和伺服器會針對通訊協定中的每種方法,支付小額費用。 建議您設定一個包含所有可能方法的大型通訊協定 比起擁有多個小型通訊協定的 程序初期需要較小型的通訊協定
有時候,伺服器因素保存的狀態會與方法時很簡潔 界定範圍在這種情況下,請考慮將通訊協定分解為更小的 向相同邊界的通訊協定提供不同通訊協定的通訊協定 與獨立狀態互動
避免過度使用物件方向 識別碼,為通訊協定中的邏輯物件建立模型。這種模式 透過單一物件與可能大量的邏輯物件互動 因此效能相當卓越
使用神奇值指定不存在:否
我們通常會指示伺服器設定某些狀態,但允許 同時移除狀態以下指令使用魔術值指示移除動作:
// BAD
protocol View3 {
ReplaceHitRegion(struct {
id uint64;
// Set to { -1, -1 } to remove.
size fuchsia.math.Rect;
});
ReplaceName(struct {
// Set to "" to remove.
name string;
});
};
不過,FIDL 為許多資料類型提供選用功能。使用選用性產出 更加慣用的介面
protocol View4 {
ReplaceHitRegion(struct {
id uint64;
size box<fuchsia.math.Rect>;
});
ReplaceName(struct {
name string:optional;
});
};
-
FIDL 型別系統是結構型別系統 進入 ABI,名稱不重要,只有型別的結構 因此 FIDL 類型系統在 也能使用 Google Cloud CLI 或 Compute Engine API ↩