必要條件
本教學課程的設計是以 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. "//sdk/lib/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
連線至通訊協定
在主函式中,用戶端元件會在其fuchsia.examples/Echo
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());
  }
使用線路網域物件撥打電話
上述教學課程會使用自然網域物件建立用戶端呼叫:每個呼叫都會使用自然網域物件代表要求訊息,並以自然網域物件傳回回覆。在針對效能和堆積配置進行最佳化時,您可以使用線路網域物件進行呼叫。如要執行這項操作,請在呼叫時使用的解參運算子 (client.wire()->EchoString(...)) 前插入 .wire()。
使用線路類型建立 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()或->。
- 您必須在回呼中同步使用結果。結果類型為「unowned」,表示它只會借用 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 能力從伺服器路由至用戶端。在本教學課程中,我們會使用
- 設定建構作業,以便納入提供的套件,其中包含回音領域、伺服器和用戶端: - fx set core.x64 --with //examples/fidl/cpp/client
- 建構 Fuchsia 映像檔: - fx build
- 執行 - echo_realm元件。這會建立用戶端和伺服器元件例項,並將功能路由:- ffx component run /core/ffx-laboratory:echo-realm 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 中。