傳送 FIDL 通訊協定

必要條件

本教學課程的設計是以 HLCPP 入門教學課程為基礎。

總覽

在 Fuchsia 上使用 FIDL 的常見做法,就是在通訊協定之間傳遞通訊協定本身。更精確地說,許多訊息都包含管道的用戶端端或伺服器端,而管道則用於透過特定通訊協定進行通訊。在這種情況下,用戶端端指的是管道的遠端端實作指定的通訊協定,而伺服器端指的是遠端端針對指定的通訊協定提出要求。用於用戶端和伺服器端的另一組術語是「通訊協定」和「通訊協定要求」。

本教學課程涵蓋以下內容:

  • 在 FIDL 和 HLCPP FIDL 繫結中,這些用戶端和伺服器端的用法都會結束。
  • 要求管線模式及其優點。

本教學課程的完整範例程式碼位於 //examples/fidl/hlcpp/request_pipelining

FIDL 通訊協定

為此,本教學課程會實作 fuchsia.examples 程式庫中的 EchoLauncher 通訊協定:

@discoverable
closed protocol EchoLauncher {
    strict GetEcho(struct {
        echo_prefix string:MAX_STRING_LENGTH;
    }) -> (resource struct {
        response client_end:Echo;
    });
    strict GetEchoPipelined(resource struct {
        echo_prefix string:MAX_STRING_LENGTH;
        request server_end:Echo;
    });
};

這是一個通訊協定,可讓用戶端擷取 Echo 通訊協定的例項。用戶端可以指定前置字串,而產生的 Echo 例項會在每個回應中加入該前置字串。

您可以使用以下兩種方法完成這項操作:

  • GetEcho:將前置字串視為要求,並以連線至 Echo 通訊協定實作的管道的用戶端端點回應。在回應中收到用戶端端點後,用戶端就可以開始使用用戶端端點,透過 Echo 通訊協定提出要求。
  • GetEchoPipelined:將管道的伺服器端做為要求參數之一,並將 Echo 的實作項目繫結至該端。系統會假設提出要求的用戶端已持有用戶端端點,並會在呼叫 GetEchoPipeliend 後開始在該管道上提出 Echo 要求。

顧名思義,後者會使用稱為通訊協定要求管道化的模式,也是較佳的做法。本教學課程會實作這兩種方法。

實作伺服器

實作 Echo 通訊協定

這個 Echo 實作可讓您指定前置字串,以便區分不同 Echo 伺服器執行個體:

class EchoImpl : public fuchsia::examples::Echo {
 public:
  explicit EchoImpl(std::string prefix) : prefix_(prefix) {}
  void EchoString(std::string value, EchoStringCallback callback) override {
    std::cout << "Got echo request for prefix " << prefix_ << std::endl;
    callback(prefix_ + value);
  }
  void SendString(std::string value) override {}

  const std::string prefix_;
};

由於用戶端只使用 EchoString,因此 SendString 處理常式為空白。

實作 EchoLauncher 通訊協定

這個類別會使用繫結集合,追蹤啟動的所有 Echo 例項:

class EchoLauncherImpl : public fuchsia::examples::EchoLauncher {
 public:
  void GetEcho(std::string echo_prefix, GetEchoCallback callback) override {
    std::cout << "Got non pipelined request" << std::endl;
    fidl::InterfaceHandle<fuchsia::examples::Echo> client_end;
    fidl::InterfaceRequest<fuchsia::examples::Echo> server_end = client_end.NewRequest();
    bindings_.AddBinding(std::make_unique<EchoImpl>(echo_prefix), std::move(server_end));
    callback(std::move(client_end));
  }

  void GetEchoPipelined(std::string echo_prefix,
                        fidl::InterfaceRequest<fuchsia::examples::Echo> server_end) override {
    std::cout << "Got pipelined request" << std::endl;
    bindings_.AddBinding(std::make_unique<EchoImpl>(echo_prefix), std::move(server_end));
  }

  fidl::BindingSet<fuchsia::examples::Echo, std::unique_ptr<fuchsia::examples::Echo>> bindings_;
};

程式碼除了明確指定繫結集的範本所使用的通訊協定,還會指定儲存繫結的指標類型。程式碼使用 unique_ptr 而非原始指標,因此繫結集合會擁有 EchoImpl 的例項。

以下是這兩種方法的實作方式:

class EchoLauncherImpl : public fuchsia::examples::EchoLauncher {
 public:
  void GetEcho(std::string echo_prefix, GetEchoCallback callback) override {
    std::cout << "Got non pipelined request" << std::endl;
    fidl::InterfaceHandle<fuchsia::examples::Echo> client_end;
    fidl::InterfaceRequest<fuchsia::examples::Echo> server_end = client_end.NewRequest();
    bindings_.AddBinding(std::make_unique<EchoImpl>(echo_prefix), std::move(server_end));
    callback(std::move(client_end));
  }

  void GetEchoPipelined(std::string echo_prefix,
                        fidl::InterfaceRequest<fuchsia::examples::Echo> server_end) override {
    std::cout << "Got pipelined request" << std::endl;
    bindings_.AddBinding(std::make_unique<EchoImpl>(echo_prefix), std::move(server_end));
  }

  fidl::BindingSet<fuchsia::examples::Echo, std::unique_ptr<fuchsia::examples::Echo>> bindings_;
};

對於 GetEcho,程式碼首先需要將管道的兩端例項化。它會使用伺服器端建立 Binding,然後透過用戶端端傳回回應。對於 GetEchoPipelined,用戶端已完成建立管道兩端的工作。它會保留一端,並將另一端傳遞至伺服器,因此所有程式碼只需將其繫結至 Echo 實作項目即可。

提供 EchoLauncher 通訊協定

主迴圈與伺服器教學課程中的迴圈相同,但會提供 EchoLauncher 而非 Echo

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

  EchoLauncherImpl impl;
  fidl::Binding<fuchsia::examples::EchoLauncher> binding(&impl);
  fidl::InterfaceRequestHandler<fuchsia::examples::EchoLauncher> handler =
      [&](fidl::InterfaceRequest<fuchsia::examples::EchoLauncher> request) {
        binding.Bind(std::move(request));
      };
  auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
  context->outgoing()->AddPublicService(std::move(handler));

  std::cout << "Running echo launcher server" << std::endl;
  return loop.Run();
}

建構伺服器

您可以視需要檢查是否正確無誤,並嘗試建構伺服器:

  1. 設定 GN 建構作業,以便納入伺服器:

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

    fx build

實作用戶端

連線至 EchoLauncher 伺服器後,用戶端程式碼會使用 GetEcho 連線至一個 Echo 例項,並使用 GetEchoPipelined 連線至另一個例項,然後對每個例項提出 EchoString 要求。

以下是未管線的程式碼:

  fuchsia::examples::EchoPtr echo;
  auto callback = [&](fidl::InterfaceHandle<fuchsia::examples::Echo> client_end) {
    std::cout << "Got non pipelined response\n";
    echo.Bind(std::move(client_end));
    echo->EchoString("hello!", [&](std::string response) {
      std::cout << "Got echo response " << response << "\n";
      if (++num_responses == 2) {
        loop.Quit();
      }
    });
  };
  echo_launcher->GetEcho("not pipelined: ", std::move(callback));

此程式碼有兩層回呼:

  • 外層會處理啟動器要求。
  • 內層會處理 EchoString 要求。

此外,程式碼會在外部範圍中將 EchoPtr 例項化,然後在回呼中 Bind 該例項化,而不是呼叫 fidl::InterfaceRequest<T>::Bind。這是因為在收到回音回應時,Proxy 必須在範圍內,而這通常會在頂層回呼傳回後發生。

雖然必須初始化管道,但管道程式碼會變得簡單許多:

  fuchsia::examples::EchoPtr echo_pipelined;
  echo_launcher->GetEchoPipelined("pipelined: ", echo_pipelined.NewRequest());
  echo_pipelined->EchoString("hello!", [&](std::string response) {
    std::cout << "Got echo response " << response << "\n";
    if (++num_responses == 2) {
      loop.Quit();
    }
  });

建構用戶端

您可以視需要嘗試建構用戶端,以便檢查設定是否正確無誤:

  1. 設定 GN 建構作業,以便納入用戶端:

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

    fx build

執行程式碼範例

在本教學課程中,我們會使用 fuchsia.examples.Echofuchsia.examples.EchoLauncher

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

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

    fx build
  3. 執行 echo_realm 元件。這會建立用戶端和伺服器元件例項,並將功能路由:

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

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

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

[echo_server][][I] Running echo launcher server
[echo_server][][I] Got non pipelined request
[echo_server][][I] Got pipelined request
[echo_server][][I] Got echo request for prefix pipelined:
[echo_client][][I] Got non pipelined response
[echo_client][][I] Got echo response pipelined: hello!
[echo_server][][I] Got echo request for prefix not pipelined:
[echo_client][][I] Got echo response not pipelined: hello!

根據列印順序,您可以看到管線化案例的速度較快。即使先傳送非管線要求,管線情況的回音回應還是會先傳送,因為要求管線可節省用戶端和伺服器之間的來回傳輸。要求管道也能簡化程式碼。

如要進一步瞭解通訊協定要求管道化,包括如何處理可能失敗的通訊協定要求,請參閱 FIDL API 評分標準

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

ffx component destroy /core/ffx-laboratory:echo_realm