必要條件
本教學課程是以 FIDL 伺服器教學課程為基礎。如需完整的 FIDL 教學課程,請參閱總覽。
總覽
本教學課程會實作 FIDL 通訊協定的用戶端,並透過在上一個教學課程中建立的伺服器執行該用戶端。本教學課程中的用戶端是非同步的。另外還有適用於同步用戶端的替代教學課程。
用戶端範例的結構
本教學課程隨附的範例程式碼位於 Fuchsia 結帳頁面中的 //examples/fidl/cpp/client
。其中包含用戶端元件和其包含的套件。如要進一步瞭解如何建構元件,請參閱「建構元件」。
為了啟動及執行用戶端元件,//examples/fidl/cpp/client/BUILD.gn
中定義的三個目標:
用戶端的原始可執行檔。這會產生具有指定輸出名稱的二進位檔,且可在 Fuchsia 上執行:
executable("bin") { output_name = "fidl_echo_cpp_client" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp", # This library is used to log messages. "//sdk/lib/syslog/cpp", # This library provides an asynchronous event loop implementation. "//zircon/system/ulib/async-loop:async-loop-cpp", # This library is used to consume capabilities, e.g. protocols, # from the component's incoming directory. "//sdk/lib/component/incoming/cpp", ] }
設定為執行用戶端執行檔的元件。元件是指軟體在 Fuchsia 上執行的單位。元件可透過其資訊清單檔案來描述。在這種情況下,
meta/client.cml
會將echo-client
設為可執行元件,並在:bin
中執行fidl_echo_cpp_client
。fuchsia_component("echo-client") { component_name = "echo_client" manifest = "meta/client.cml" deps = [ ":bin" ] }
伺服器元件資訊清單位於
//examples/fidl/cpp/client/meta/client.cml
。資訊清單中的二進位檔名稱必須與BUILD.gn
中定義的executable
輸出名稱相符。{ include: [ "syslog/client.shard.cml" ], // Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/fidl_echo_cpp_client", }, // Capabilities used by this component. use: [ { protocol: "fuchsia.examples.Echo" }, ], }
元件隨後會放入套件中,也就是 Fuchsia 上的軟體發布單位。
# C++ async client and server example package fuchsia_package("echo-cpp-client") { deps = [ ":echo-client", "//examples/fidl/cpp/server:echo-server", "//examples/fidl/echo-realm:echo_realm", ] }
建立客戶
將用戶端新增至建構設定。這個步驟只需要執行一次:
fx set core.x64 --with //examples/fidl/cpp/client
建構用戶端:
fx build examples/fidl/cpp/client
連線至通訊協定
int main(int argc, const char** argv) {
// Connect to the |fuchsia.examples/Echo| 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<fuchsia_examples::Echo>();
if (!client_end.is_ok()) {
FX_LOGS(ERROR) << "Synchronous error when connecting to the |Echo| protocol: "
<< client_end.status_string();
return -1;
}
// ...
同時,元件管理員會將要求轉送至伺服器元件。伺服器教學課程中實作的伺服器處理常式會使用伺服器端點呼叫,並將管道繫結至伺服器實作。
請特別注意,此程式碼假設元件的命名空間已包含 Echo
通訊協定的執行個體。
初始化事件迴圈
非同步用戶端需要 async_dispatcher_t*
,才能以非同步方式監控來自管道的訊息。async::Loop
提供由事件迴圈支援的調度工具實作。
// As in the server, the code sets up an async loop so that the client can
// listen for incoming responses from the server without blocking.
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
調度器可用來執行一部分非同步程式碼。系統會先用於執行 EchoString
方法,並在收到回應時結束。然後,它會在呼叫 SendString
後執行以監聽事件,並在收到 OnString
事件時結束。在這兩個執行個體之間呼叫 ResetQuit()
可讓用戶端重複使用迴圈。
初始化用戶端
如要向伺服器發出 Echo
要求,請使用上一步的用戶端端點、迴圈調度器和事件處理常式委派來初始化用戶端:
// Define the event handler implementation for the client.
//
// The event handler delegate should be an object that implements the
// |fidl::AsyncEventHandler<Echo>| virtual class, which has methods
// corresponding to the events offered by the protocol. By default those
// methods are no-ops.
class EventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> {
public:
void OnString(fidl::Event<::fuchsia_examples::Echo::OnString>& event) override {
FX_LOGS(INFO) << "(Natural types) got event: " << event.response();
loop_.Quit();
}
// One may also override the |on_fidl_error| method, which is called
// when the client encounters an error and is going to teardown.
void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; }
explicit EventHandler(async::Loop& loop) : loop_(loop) {}
private:
async::Loop& loop_;
};
// Create an instance of the event handler.
EventHandler event_handler{loop};
// Create a client to the Echo protocol, passing our |event_handler| created
// earlier. The |event_handler| must live for at least as long as the
// |client|. The |client| holds a reference to the |event_handler| so it is a
// bug for the |event_handler| to be destroyed before the |client|.
fidl::Client client(std::move(*client_end), dispatcher, &event_handler);
撥打 FIDL 呼叫
執行 FIDL 呼叫的方法會公開在反參照運算子後方,因此 FIDL 呼叫看起來像 client->EchoString(...)
。
非同步 EchoString
呼叫會接受要求物件,並接受透過呼叫結果叫用的回呼,指出成功或失敗:
client->EchoString({"hello"}).ThenExactlyOnce(
[&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
// Check if the FIDL call succeeded or not.
if (!result.is_ok()) {
// If the call failed, log the error, and crash the program.
// Production code should do more graceful error handling depending
// on the situation.
FX_LOGS(ERROR) << "EchoString failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
// Dereference (->) the result object to access the response payload.
FX_LOGS(INFO) << "(Natural types) got response: " << result->response();
loop.Quit();
});
// Run the dispatch loop, until we receive a reply, in which case the callback
// above will quit the loop, breaking us out of the |Run| function.
loop.Run();
loop.ResetQuit();
您也可以使用自然結構和資料表支援的指定初始化樣式雙括號語法:
client->EchoString({{.value = "hello"}})
.ThenExactlyOnce(
// ... callback ...
單向 SendString
呼叫沒有回覆,因此不需要回呼。傳回的結果代表傳送要求時發生任何錯誤。
// Make a SendString one way call with natural types.
fit::result<::fidl::Error> result = client->SendString({"hello"});
if (!result.is_ok()) {
FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
使用有線網域物件發出呼叫
上述教學課程說明如何使用自然網域物件進行用戶端呼叫:每個呼叫都會耗用以自然網域物件代表的要求訊息,並傳回同樣在自然網域物件中的返回回覆。針對效能和堆積分配進行最佳化時,可使用網域物件發出呼叫。方法是在呼叫時使用的解除參照運算子之前插入 .wire()
,例如 client.wire()->EchoString(...)
。
使用以下線路類型建立 EchoString
雙向通話:
client.wire()->EchoString("hello").ThenExactlyOnce(
[&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
if (!result.ok()) {
FX_LOGS(ERROR) << "EchoString failed: " << result.error();
ZX_PANIC("%s", result.error().FormatDescription().c_str());
return;
}
fidl::WireResponse<fuchsia_examples::Echo::EchoString>& response = result.value();
std::string reply(response.response.data(), response.response.size());
FX_LOGS(INFO) << "(Wire types) got response: " << reply;
loop.Quit();
});
使用以下電匯類型建立 SendString
單向通話:
fidl::Status wire_status = client.wire()->SendString("hello");
if (!wire_status.ok()) {
FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
有線用戶端呼叫使用的相關類別和函式,與自然用戶端呼叫中使用的類別相似。呼叫其他類別或函式時,線路對應項目通常會加上 Wire
前置字串。指標與參照和引數結構也有差異:
接受自然網域物件的
EchoString
方法可接受單一引數,做為要求網域物件:client->EchoString({{.value = "hello"}}) .ThenExactlyOnce(
如果要求酬載是結構,
EchoString
方法會將要求主體中的結構體欄位清單扁平化為個別引數 (這裡是一個fidl::StringView
引數):client.wire()->EchoString("hello").ThenExactlyOnce(
非同步自然呼叫中的回呼接受
fidl::Result<Method>&
:client->EchoString({"hello"}).ThenExactlyOnce( [&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) { // Check if the FIDL call succeeded or not. if (!result.is_ok()) { // If the call failed, log the error, and crash the program. // Production code should do more graceful error handling depending // on the situation. FX_LOGS(ERROR) << "EchoString failed: " << result.error_value(); ZX_PANIC("%s", result.error_value().FormatDescription().c_str()); } // Dereference (->) the result object to access the response payload. FX_LOGS(INFO) << "(Natural types) got response: " << result->response(); loop.Quit(); }); // Run the dispatch loop, until we receive a reply, in which case the callback // above will quit the loop, breaking us out of the |Run| function. loop.Run(); loop.ResetQuit();
- 如要檢查成功或錯誤,請使用
is_ok()
或is_error()
方法。 - 如要在之後存取回應酬載,請使用
value()
或->
。 - 您可以移出結果或酬載,因為這些類型都會實作階層物件擁有權。
非同步線路呼叫的回呼接受
fidl::WireUnownedResult<Method>&
:client.wire()->EchoString("hello").ThenExactlyOnce( [&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) { if (!result.ok()) { FX_LOGS(ERROR) << "EchoString failed: " << result.error(); ZX_PANIC("%s", result.error().FormatDescription().c_str()); return; } fidl::WireResponse<fuchsia_examples::Echo::EchoString>& response = result.value(); std::string reply(response.response.data(), response.response.size()); FX_LOGS(INFO) << "(Wire types) got response: " << reply; loop.Quit(); });
- 如要檢查是否成功,請使用
ok()
方法。 - 如要在之後存取回應酬載,請使用
value()
或->
。 - 您必須在回呼中同步使用結果。結果類型為「未擁有」,代表只會借用 FIDL 執行階段在其他其他位置分配的回應。
- 如要檢查成功或錯誤,請使用
單向呼叫也會在自然案例中採用整個要求網域物件,並在電線案例中將要求結構欄位扁平化為單獨的引數:
// Make a SendString one way call with natural types. fit::result<::fidl::Error> result = client->SendString({"hello"}); fidl::Status wire_status = client.wire()->SendString("hello");
執行用戶端
如要讓用戶端和伺服器使用 Echo
通訊協定進行通訊,元件架構必須將 fuchsia.examples.Echo
能力從伺服器轉送至用戶端。
設定您的版本以納入包含 echo 領域、伺服器和用戶端的套件:
fx set core.x64 --with //examples/fidl/cpp/client
建構 Fuchsia 映像檔:
fx build
執行
echo_realm
元件。這會建立用戶端和伺服器元件執行個體,並轉送功能:ffx component run /core/ffx-laboratory:echo-client fuchsia-pkg://fuchsia.com/echo-cpp-client#meta/echo_realm.cm
啟動
echo_client
執行個體:ffx component start /core/ffx-laboratory:echo_realm/echo_client
伺服器元件會在用戶端嘗試連線至 Echo
通訊協定時啟動。您應該會在裝置記錄 (ffx log
) 中看到類似以下的輸出內容:
[echo_server][][I] Running C++ echo server with natural types
[echo_server][][I] Incoming connection for fuchsia.examples.Echo
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got event: hello
[echo_client][][I] (Wire types) got response: hello
[echo_client][][I] (Natural types) got event: hello
[echo_server][][I] Client disconnected
終止領域元件以停止執行並清除元件執行個體:
ffx component destroy /core/ffx-laboratory:echo_realm
僅連接網域物件
fidl::Client
支援使用自然網域物件和線路物件進行呼叫。如果只需要使用線路網域物件,您可以建立 WireClient
,藉此公開對等的方法呼叫介面,做為在 fidl::Client
上呼叫 client.wire()
所取得的子集。
WireClient
的建立方式與 Client
相同:
fidl::WireClient client(std::move(*client_end), dispatcher, &event_handler);
fidl::Client
一律會以自然網域物件的形式,向使用者顯示接收的事件。另一方面,fidl::WireClient
會以傳輸網域物件的形式公開接收的事件。為此,傳遞至 WireClient
的事件處理常式必須實作 fidl::WireAsyncEventHandler<Protocol>
。
實作自然事件處理常式:
class EventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> { public: void OnString(fidl::Event<::fuchsia_examples::Echo::OnString>& event) override { FX_LOGS(INFO) << "(Natural types) got event: " << event.response(); loop_.Quit(); } // ... };
實作線路事件處理常式:
class EventHandler : public fidl::WireAsyncEventHandler<fuchsia_examples::Echo> { public: void OnString(fidl::WireEvent<fuchsia_examples::Echo::OnString>* event) override { FX_LOGS(INFO) << "(Natural types) got event: " << event->response.get(); loop_.Quit(); } // ... };
同步通話
WireClient
物件也允許同步呼叫,這種呼叫會在收到回應並傳回回應物件之前封鎖。您可以使用 .sync()
存取子來選取這些元件。(例如 client.sync()->EchoString()
)。
// Make a synchronous EchoString call, which blocks until it receives the response,
// then returns a WireResult object for the response.
fidl::WireResult result_sync = client.sync()->EchoString("hello");
ZX_ASSERT(result_sync.ok());
std::string_view response = result_sync->response.get();
FX_LOGS(INFO) << "Got synchronous response: " << response;
在同步呼叫中,系統會傳回「結果物件」,以同步方式告知呼叫成功或失敗。
使用有線用戶端的完整程式碼範例位於 Fuchsia 結帳頁面://examples/fidl/cpp/client/wire
。