實作同步 C++ FIDL 用戶端

必要條件

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

總覽

本教學課程將實作 FIDL 通訊協定的用戶端,並以上一個教學課程中建立的伺服器執行該用戶端。本教學課程中的用戶端處於同步狀態。另有非同步用戶端的替代教學課程

客戶範例結構

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

如要讓用戶端元件啟動並執行,//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()。否則,系統會傳回適當的錯誤。如果伺服器以 Epitaph 關閉連線,系統會傳回 Epitaph 中包含的狀態。

// 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. 設定建構以納入提供的套件,當中包含 echo 領域、伺服器和用戶端:

    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 結帳頁面的 //examples/fidl/cpp/client_sync/wire 中找到使用接線用戶端的完整程式碼範例。