實作同步 C++ FIDL 用戶端

必要條件

本教學課程的設計是以 FIDL 伺服器教學課程為基礎。如需 FIDL 完整教學課程,請參閱總覽

總覽

本教學課程會實作 FIDL 通訊協定的用戶端,並針對先前教學課程中建立的伺服器執行用戶端。本教學課程中的用戶端是同步的。我們也提供其他教學課程,適用於非同步用戶端。

用戶端範例的結構

本教學課程附帶的範例程式碼位於 Fuchsia 檢出的 //examples/fidl/cpp/client_sync 中。其中包含用戶端元件和內含的套件。如要進一步瞭解如何建構元件,請參閱「建構元件」。

如要啟動及執行用戶端元件,//examples/fidl/cpp/client_sync/BUILD.gn 中定義了三個目標:

  1. 用戶端的原始執行檔。這會產生具有指定輸出名稱的二進位檔,可在 Fuchsia 上執行:

    executable("bin") {
      output_name = "fidl_echo_cpp_client_sync"
      sources = [ "main.cc" ]
    
      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",
    
        # This library is used to log messages.
        "//sdk/lib/syslog/cpp",
    
        # This library is used to consume capabilities, e.g. protocols,
        # from the component's incoming directory.
        "//sdk/lib/component/incoming/cpp",
      ]
    }
    
    
  2. 已設定為執行用戶端可執行檔的元件。元件為 Fuchsia 軟體執行單位。元件會由其資訊清單檔案描述。在這種情況下,meta/client.cml 會將 echo-client 設為可執行的元件,在 :bin 中執行 fidl_echo_cpp_client_sync

    fuchsia_component("echo-client") {
      component_name = "echo_client"
      manifest = "meta/client.cml"
      deps = [ ":bin" ]
    }
    
    

    伺服器元件資訊清單位於 //examples/fidl/cpp/client_sync/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_sync",
        },
    
        // Capabilities used by this component.
        use: [
            { protocol: "fuchsia.examples.Echo" },
        ],
    }
    
    
  3. 接著,元件會放入套件中,也就是 Fuchsia 的軟體分配單位。在這種情況下,套件會包含用戶端和伺服器元件,並且

    # C++ sync client and server example package
    fuchsia_package("echo-cpp-client-sync") {
      deps = [
        ":echo-client",
        "//examples/fidl/cpp/server:echo-server",
        "//examples/fidl/echo-realm:echo_realm",
      ]
    }
    
    

建構用戶端

  1. 將用戶端新增至建構設定。這項操作只需執行一次:

    fx set core.x64 --with //examples/fidl/cpp/client_sync
    
  2. 建構用戶端:

    fx build examples/fidl/cpp/client_sync
    

連線至通訊協定

在主函式中,用戶端元件會在其 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 通訊協定的例項。在教學課程結尾執行通訊協定時,會建立一個

初始化用戶端

如要向伺服器提出 Echo 要求,請使用上一個步驟中的用戶端端點,初始化用戶端。

fidl::SyncClient client{std::move(*client_end)};

發出 FIDL 呼叫

執行 FIDL 呼叫的方法會在解除參照運算子之後公開,例如 FIDL 呼叫看起來像 client->EchoString(...)

以下兩種呼叫 (例如 EchoString) 會接受要求物件,並傳回表示成功或失敗的結果物件:

fidl::Result result = client->EchoString({"hello"});
// Check if the FIDL call succeeded or not.
if (!result.is_ok()) {
  // If the call failed, log the error, and quit the program.
  // Production code should do more graceful error handling depending
  // on the situation.
  FX_LOGS(ERROR) << "EchoString failed: " << result.error_value();
  return -1;
}
const std::string& reply_string = result->response();
FX_LOGS(INFO) << "Got response: " << reply_string;

您也可以使用自然結構資料表支援的指定初始化樣式雙大括號語法:

fidl::Result result = client->EchoString({{.value = "hello"}});

單向 SendString 呼叫沒有回應。傳回的結果代表傳送要求時發生的任何錯誤。

fit::result<fidl::Error> result = client->SendString({"hi"});
if (!result.is_ok()) {
  FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
  return -1;
}

處理事件

定義事件處理常式

// Define the event handler implementation for the client.
//
// The event handler should be an object that implements
// |fidl::SyncEventHandler<Echo>|, and override all pure virtual methods
// in that class corresponding to the events offered by the protocol.
class EventHandler : public fidl::SyncEventHandler<fuchsia_examples::Echo> {
 public:
  EventHandler() = default;

  void OnString(fidl::Event<fuchsia_examples::Echo::OnString>& event) override {
    const std::string& reply_string = event.response();
    FX_LOGS(INFO) << "Got event: " << reply_string;
  }
};

呼叫 client.HandleOneEvent 以阻斷,直到收到事件為止。如果系統已辨識並成功解碼事件,HandleOneEvent 會傳回 fidl::Status::Ok()。否則,會傳回適當的錯誤。如果伺服器使用碑文關閉連線,系統會傳回碑文中包含的狀態。

// Block to receive exactly one event from the server, which is handled using
// the event handler defined above.
EventHandler event_handler;
fidl::Status status = client.HandleOneEvent(event_handler);
if (!status.ok()) {
  FX_LOGS(ERROR) << "HandleOneEvent failed: " << status.error();
  return -1;
}

使用線路網域物件撥打電話

上述教學課程會使用自然網域物件建立用戶端呼叫:每個呼叫都會使用自然網域物件代表要求訊息,並以自然網域物件傳回回覆。在針對效能和堆積配置進行最佳化時,您可以使用線路網域物件進行呼叫。方法是在發出呼叫時使用的解除參照運算子之前插入 .wire(),例如 client.wire()->EchoString(...)

使用線路類型建立 EchoString 雙向呼叫:

fidl::WireResult result = client.wire()->EchoString("hello");
if (!result.ok()) {
  FX_LOGS(ERROR) << "EchoString failed: " << result.error();
  return -1;
}
FX_LOGS(INFO) << "Got response: " << result->response.get();

使用線路類型發出 SendString 單向呼叫:

fidl::Status wire_result = client.wire()->SendString("hi");
if (!wire_result.ok()) {
  FX_LOGS(ERROR) << "SendString failed: " << wire_result.error();
  return -1;
}

在線用戶端呼叫中使用的相關類別和函式,與自然用戶端呼叫中使用的類別和函式形狀相似。呼叫其他類別或函式時,線路對應項目通常會加上 Wire 前置字元。指標和參照以及引數結構也有差異:

  • EchoString 方法會採用自然網域物件,並接受單一引數,也就是要求網域物件:

    fidl::Result result = client->EchoString({{.value = "hello"}});
    

    如果要求酬載是結構體,則使用線路網域物件的 EchoString 方法會將要求主體中的結構體欄位清單扁平化為個別引數 (此處為單一 fidl::StringView 引數):

    fidl::WireResult result = client.wire()->EchoString("hello");
    
  • 兩種自然呼叫都會傳回 fidl::Result<Method>

    fidl::Result result = client->EchoString({{.value = "hello"}});
    if (!result.is_ok()) {
      FX_LOGS(ERROR) << "EchoString failed: " << result.error_value();
      return -1;
    }
    const std::string& reply_string = result->response();
    FX_LOGS(INFO) << "Got response: " << reply_string;
    
    • 如要檢查成功或錯誤,請使用 is_ok()is_error() 方法。
    • 如要之後存取回應酬載,請使用 value()->
    • 您可以移出結果或酬載,因為這些類型都會實作階層式物件擁有權。

    雙向電線呼叫會傳回 fidl::WireResult<Method>

    fidl::WireResult result = client.wire()->EchoString("hello");
    if (!result.ok()) {
      FX_LOGS(ERROR) << "EchoString failed: " << result.error();
      return -1;
    }
    FX_LOGS(INFO) << "Got response: " << result->response.get();
    
    • 如要確認是否成功,請使用 ok() 方法。
    • 如要之後存取回應酬載,請使用 value()->
    • 您無法移動結果物件。
  • 其中一種方式呼叫也會以自然情況下取得整個要求網域物件,並將要求結構欄位簡化為傳輸案例中的個別引數:

    // Make a SendString call using natural types.
    fit::result<fidl::Error> result = client->SendString({"hi"});
    
    // Make a SendString call using wire types.
    fidl::Status wire_result = client.wire()->SendString("hi");
    

執行用戶端

為了讓用戶端和伺服器使用 Echo 通訊協定進行通訊,元件架構必須將 fuchsia.examples.Echo 能力從伺服器路由至用戶端。在本教學課程中,我們會使用

中探索領域元件的完整來源。
  1. 設定建構作業,加入提供的套件,其中包含回音領域、伺服器和用戶端:

    fx set core.x64 --with //examples/fidl/cpp/client_sync
    
  2. 建構 Fuchsia 映像檔:

    fx build
    
  3. 執行 echo_realm 元件。這會建立用戶端和伺服器元件執行個體,並將功能導向:

    ffx component run /core/ffx-laboratory:echo-client fuchsia-pkg://fuchsia.com/echo-cpp-client-sync#meta/echo_realm.cm
    
  4. 啟動 echo_client 執行個體:

    ffx component start /core/ffx-laboratory:echo_realm/echo_client
    

當用戶端嘗試連線至 Echo 通訊協定時,伺服器元件就會啟動。您應該會在裝置記錄 (ffx log) 中看到類似以下的輸出內容:

[echo_server][][I] Running echo server
[echo_client][I] Got response: hello
[echo_client][I] Got event: hi
[echo_client][I] Got response: hello
[echo_server][I] Client disconnected

終止領域元件,停止執行並清理元件例項:

ffx component destroy /core/ffx-laboratory:echo_realm

僅限線網域物件用戶端

fidl::SyncClient 支援使用自然網域物件線纜網域物件進行呼叫。如果您只需要使用線路網域物件,可以建立 WireSyncClient,將等效的方法呼叫介面公開為在 fidl::SyncClient 上呼叫 client.wire() 所取得的子集。

WireSyncClient 的建立方式與 SyncClient 相同:

fidl::WireSyncClient client(std::move(*client_end));

fidl::SyncClient 一律會以自然網域物件的形式,向使用者公開收到的事件。另一方面,fidl::WireSyncClient 會以傳輸網域物件的形式公開接收的事件。為此,傳遞至 WireSyncClient 的事件處理常式需要實作 fidl::WireSyncEventHandler<Protocol>

  • 實作自然事件處理常式:

    // Define the event handler implementation for the client.
    //
    // The event handler should be an object that implements
    // |fidl::SyncEventHandler<Echo>|, and override all pure virtual methods
    // in that class corresponding to the events offered by the protocol.
    class EventHandler : public fidl::SyncEventHandler<fuchsia_examples::Echo> {
     public:
      EventHandler() = default;
    
      void OnString(fidl::Event<fuchsia_examples::Echo::OnString>& event) override {
        const std::string& reply_string = event.response();
        FX_LOGS(INFO) << "Got event: " << reply_string;
      }
    };
    
  • 實作線路事件處理常式:

    // Define the event handler implementation for the client.
    //
    // The event handler should be an object that implements
    // |fidl::WireSyncEventHandler<Echo>|, and override all pure virtual methods
    // in that class corresponding to the events offered by the protocol.
    class EventHandler : public fidl::WireSyncEventHandler<fuchsia_examples::Echo> {
     public:
      EventHandler() = default;
    
      void OnString(fidl::WireEvent<fuchsia_examples::Echo::OnString>* event) override {
        FX_LOGS(INFO) << "Got event: " << event->response.get();
      }
    };
    

您可以在 Fuchsia Checkout 的結帳頁面 (//examples/fidl/cpp/client_sync/wire) 中找到使用電線用戶端的完整程式碼範例。