實作 FIDL 用戶端

必要條件

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

總覽

本教學課程會實作 FIDL 通訊協定的用戶端,並透過在上一個教學課程中建立的伺服器執行該用戶端。本教學課程中的用戶端是非同步的。另外還有適用於同步用戶端的替代教學課程

如要自行編寫程式碼,請刪除下列目錄:

rm -r examples/fidl/hlcpp/client/*

建立元件

examples/fidl/hlcpp/client 建立新的元件專案:

  1. examples/fidl/hlcpp/client/main.cc 中新增 main() 函式:

    int main(int argc, const char** argv) {
      printf("Hello, world!\n");
      return 0;
    }
    
  2. examples/fidl/hlcpp/client/BUILD.gn 中宣告用戶端的目標:

    import("//build/components.gni")
    
    
    # Declare an executable for the client.
    executable("bin") {
      output_name = "fidl_echo_hlcpp_client"
      sources = [ "main.cc" ]
    }
    
    fuchsia_component("echo-client") {
      component_name = "echo_client"
      manifest = "meta/client.cml"
      deps = [ ":bin" ]
    }
    
  3. examples/fidl/hlcpp/client/meta/client.cml 中新增元件資訊清單:

    {
        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_hlcpp_client",
        },
    
        // Capabilities used by this component.
        use: [
            { protocol: "fuchsia.examples.Echo" },
        ],
    }
    
    
  4. 建立元件後,請確保您可以將其新增至建構設定:

    fx set core.x64 --with //examples/fidl/hlcpp/client:echo-client
    
  5. 建構 Fuchsia 映像檔:

    fx build
    

編輯 GN 依附元件

  1. 新增下列依附元件:

      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_hlcpp",
        "//sdk/lib/sys/cpp",
        "//zircon/system/ulib/async-loop:async-loop-cpp",
        "//zircon/system/ulib/async-loop:async-loop-default",
      ]
    
    
  2. 然後加入 main.cc 中:

    #include <fuchsia/examples/cpp/fidl.h>
    #include <lib/async-loop/cpp/loop.h>
    #include <lib/async-loop/default.h>
    #include <lib/sys/cpp/component_context.h>
    

    如需納入這些依附元件的原因,請參閱伺服器教學課程

連線至伺服器

本節中的步驟說明如何將程式碼新增至 main() 函式,將用戶端連線至伺服器並向伺服器發出要求。

初始化事件迴圈

與伺服器相同,程式碼會先設定非同步迴圈,以便用戶端監聽來自伺服器的傳入回應,而不會封鎖。

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  loop.Run();
  return num_responses == 2 ? 0 : 1;
}

初始化 Proxy 類別

在 FIDL 的內容中,Proxy 會指定由 FIDL 繫結產生的程式碼,能讓使用者向伺服器發出遠端程序呼叫。在 HLCPP 中,Proxy 會以類別的形式,其中包含每個 FIDL 通訊協定方法對應的方法。

程式碼隨後會為 Echo 通訊協定建立 Proxy 類別,並將該類別連線至伺服器。

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  loop.Run();
  return num_responses == 2 ? 0 : 1;
}
  • fuchsia::examples::EchoPtr 是繫結產生的 fidl::InterfaceRequest<fuchsia::examples::Echo> 別名。
  • 類似於伺服器中使用的 fidl::Binding<fuchsia::examples::Echo>fidl::InterfaceRequest<fuchsia::examples::Echo> 是由 FIDL 通訊協定參數化,且管道會透過 FIDL 通訊協定參數化,並監聽傳入的回應和事件。
  • 程式碼會呼叫 EchoPtr::NewRequest() 來建立管道、將類別繫結至管道的任一端,並傳回該管道的另一端。
  • 傳回的管道結尾會傳遞至 sys::ServiceDirectory::Connect()
    • Connect 類似於伺服器端對 context->out()->AddPublicService() 的呼叫,此處有一個隱含的第二個參數,也就是通訊協定名稱 ("fuchsia.examples.Echo")。這是上一個教學課程中定義的處理常式輸入內容的來源:用戶端傳遞至 Connect,再將其傳遞至處理常式。

請特別注意,此程式碼假設 /svc 已包含 Echo 通訊協定的執行個體。由於元件架構提供的沙箱機制屬於沙箱機制,根據預設,此情況並非如此。

設定錯誤處理常式

最後,程式碼會設定 Proxy 的錯誤處理常式:

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  loop.Run();
  return num_responses == 2 ? 0 : 1;
}

傳送要求至伺服器

程式碼會向伺服器發出兩項要求:

  • EchoString 要求
  • SendString 要求
int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  loop.Run();
  return num_responses == 2 ? 0 : 1;
}

針對 EchoString,程式碼會傳入回呼來處理回應。SendString 不需要這類回呼,因為該方法沒有任何回應。

設定事件處理常式

然後,程式碼會為任何傳入的 OnString 事件設定處理常式:

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  loop.Run();
  return num_responses == 2 ? 0 : 1;
}

終止事件迴圈

程式碼會等待收到對 EchoString 方法的回應,以及 OnString 事件 (在收到 SendString 要求後,系統會於目前實作中傳送上述事件),然後才退出迴圈。只有在同時收到回應和事件時,程式碼才會傳回成功的結束代碼:

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  loop.Run();
  return num_responses == 2 ? 0 : 1;
}

執行用戶端

如要讓用戶端和伺服器使用 Echo 通訊協定進行通訊,元件架構必須將 fuchsia.examples.Echo 能力從伺服器轉送至用戶端。

  1. 設定您的版本以納入包含 echo 領域、伺服器和用戶端的套件:

    fx set core.x64 --with //examples/fidl/hlcpp:echo-hlcpp-client
    
  2. 建構 Fuchsia 映像檔:

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

    ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-hlcpp-client#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 event hi
[echo_client][][I] Got response hello

終止領域元件以停止執行並清除元件執行個體:

ffx component destroy /core/ffx-laboratory:echo_realm