传递 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_;
};

SendString 处理脚本为空,因为客户端只使用 EchoString

实现 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 build 配置为包含该服务器:

    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。这是因为在收到回声响应时,代理需要在作用域内,而这很可能是在顶级回调返回之后。

尽管必须初始化通道,但流水线代码要简单得多:

  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 build 配置为包含该客户端:

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

    fx build

运行示例代码

在本教程中,我们提供了一个 realm 组件,用于为 fuchsia.examples.Echofuchsia.examples.EchoLauncher 声明适当的 capability 和路由。

探索 Realm 组件的完整源代码
  1. 将 build 配置为包含包含 echo 领域、服务器和客户端的提供的软件包:

    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 评分标准

终止 Realm 组件以停止执行并清理组件实例:

ffx component destroy /core/ffx-laboratory:echo_realm