实现 HLCPP FIDL 服务器

前提条件

本教程基于编译 FIDL 教程。如需查看完整的 FIDL 教程集,请参阅概览

概览

本教程介绍了如何实现 FIDL 协议 (fuchsia.examples.Echo) 并在 Fuchsia 上运行该协议。此协议包含每种类型各一种方法:一次性触发方法、双向方法和事件:

@discoverable
closed protocol Echo {
    strict EchoString(struct {
        value string:MAX_STRING_LENGTH;
    }) -> (struct {
        response string:MAX_STRING_LENGTH;
    });
    strict SendString(struct {
        value string:MAX_STRING_LENGTH;
    });
    strict -> OnString(struct {
        response string:MAX_STRING_LENGTH;
    });
};

本文档介绍了如何完成以下任务:

  • 实现 FIDL 协议。
  • 在 Fuchsia 上构建并运行软件包。
  • 提供 FIDL 协议。

本教程首先创建一个要提供给 Fuchsia 设备并运行的组件。然后,逐步添加功能以启动和运行服务器。

如果您想自行编写代码,请删除以下目录:

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

创建组件

要创建组件,请执行以下操作:

  1. main() 函数添加到 examples/fidl/hlcpp/server/main.cc

    #include <stdio.h>
    
    int main(int argc, const char** argv) {
      printf("Hello, world!\n");
      return 0;
    }
    
  2. examples/fidl/hlcpp/server/BUILD.gn 中为服务器声明目标:

    import("//build/components.gni")
    
    
    # Declare an executable for the server. This produces a binary with the
    # specified output name that can run on Fuchsia.
    executable("bin") {
      output_name = "fidl_echo_hlcpp_server"
      sources = [ "main.cc" ]
    }
    
    # Declare a component for the server, which consists of the manifest and the
    # binary that the component will run.
    fuchsia_component("echo-server") {
      component_name = "echo_server"
      manifest = "meta/server.cml"
      deps = [ ":bin" ]
    }
    
    # Declare a package that contains a single component, our server.
    fuchsia_package("echo-hlcpp-server") {
      package_name = "echo-hlcpp-server"
      deps = [ ":echo-server" ]
    }
    

    为了启动并运行服务器组件,需要定义三个目标:

    • 构建为在 Fuchsia 上运行的服务器的原始可执行文件。
    • 设置为仅运行服务器可执行文件的组件,该可执行文件使用组件的清单文件进行描述。
    • 然后,将该组件放入软件包中,软件包是 Fuchsia 上软件分发的单元。在本例中,该软件包仅包含一个组件。

    如需详细了解软件包、组件以及如何构建它们,请参阅构建组件页面。

  3. examples/fidl/hlcpp/server/meta/server.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_server",
        },
    
        // Capabilities provided by this component.
        capabilities: [
            { protocol: "fuchsia.examples.Echo" },
        ],
        expose: [
            {
                protocol: "fuchsia.examples.Echo",
                from: "self",
            },
        ],
    }
    
    
  4. 将该服务器添加到您的构建配置中:

    fx set core.x64 --with //examples/fidl/hlcpp/server:echo-hlcpp-server
    
  5. 构建 Fuchsia 映像:

    fx build
    

实现服务器

添加 FIDL 库的依赖项

  1. examples/fidl/hlcpp/server/BUILD.gn 中将 fuchsia.examples FIDL 库目标添加为 executable 的依赖项:

    executable("bin") {
      output_name = "fidl_echo_hlcpp_server"
      sources = [ "main.cc" ]
      deps = [ "//examples/fidl/fuchsia.examples" ]
    }
    
  2. examples/fidl/hlcpp/server/main.cc 顶部导入 HLCPP 绑定:

    #include <fuchsia/examples/cpp/fidl.h>
    

添加协议的实现

将以下代码添加到 main.cc 中的 main() 函数上方:

class EchoImpl : public fuchsia::examples::Echo {
 public:
  void EchoString(std::string value, EchoStringCallback callback) override { callback(value); }
  void SendString(std::string value) override {
    if (event_sender_) {
      event_sender_->OnString(value);
    }
  }

  fuchsia::examples::Echo_EventSender* event_sender_;
};

该实现包含以下元素:

  • 该类会对生成的协议类进行子类化,并替换与协议方法对应的纯虚方法。
  • EchoString 的方法会通过对其调用回调来回复请求值。
  • SendString 的方法不接受回调,因为此方法没有响应。而是使用 Echo_EventSender 发送 OnString 事件。
  • 该类包含指向 Echo_EventSender 的指针。稍后会在 main() 函数中设置此值。

您可以通过运行以下命令来验证实现是否正确:

fx build

提供协议

如需运行实现 FIDL 协议的组件,请向组件管理器发出请求,以将该 FIDL 协议公开给其他组件。然后,组件管理器会将对回声协议的所有请求路由到我们的服务器。

为了执行这些请求,组件管理器需要协议的名称,以及当它有任何传入请求连接到与指定名称匹配的协议时应调用的处理程序。

传递给它的处理程序是一个函数,该函数接受一个通道(其远程端由客户端拥有),并将其绑定到使用服务器实现初始化的 fidl::Bindingfidl::Binding 是 FIDL 运行时中的一种类,它接受 FIDL 协议实现和通道,然后在通道上监听传入请求。然后,它会对请求进行解码,将其分派给服务器类上的正确方法,并将所有响应写回客户端。我们的主方法将在异步循环上持续监听传入请求。

开放协议的生命周期中对此完整流程进行了更详细的介绍。

添加新的依赖项

此新代码需要以下额外的依赖项:

  • "//sdk/lib/async-loop:async-loop-cpp""//sdk/lib/async-loop:async-loop-default":这些库包含异步循环代码。
  • "//sdk/lib/sys/cpp":组件框架 C++ 运行时,其中包含用于与组件环境交互的实用程序代码。
  1. examples/fidl/hlcpp/server/BUILD.gn 中将库目标添加为 executable 的依赖项:

    executable("bin") {
      output_name = "fidl_echo_hlcpp_server"
      sources = [ "main.cc" ]
      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_hlcpp",
        "//sdk/lib/async-loop:async-loop-cpp",
        "//sdk/lib/async-loop:async-loop-default",
        "//sdk/lib/sys/cpp",
      ]
    }
    
  2. examples/fidl/hlcpp/server/main.cc 顶部导入以下依赖项:

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

初始化事件循环

第一种方式是使用异步循环:

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

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

  printf("Running echo server\n");
  return loop.Run();
}

该代码首先会初始化循环,并将其注册为当前线程的默认调度程序。这是因为 main() 函数中的异步代码会向默认调度程序注册自己,后者是一个静态线程局部变量(因此无需在代码的其余部分中显式传递)。在 main 函数的末尾,代码会运行异步循环。

初始化绑定

然后,代码会按上述方式初始化 fidl::Binding

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

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

  printf("Running echo server\n");
  return loop.Run();
}

要运行绑定,需要具备以下两项:

  • 协议的实现。
  • 绑定将监听该协议的消息的通道。该绑定会先使用回声实现进行初始化,然后再绑定到某个通道。

该代码还会设置用于向客户端发送事件的事件发送器。对 Binding 使用 events() 方法获取事件发送者,然后将其传递给 EchoImpl 类。

定义协议请求处理程序

接下来,该代码为来自客户端的传入请求定义了一个处理程序:

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

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

  printf("Running echo server\n");
  return loop.Run();
}
  • “传入请求”不是对 Echo 协议的特定方法的请求,而是客户端连接到 Echo 协议实现的一般请求。
  • 该请求定义为 fidl::InterfaceRequest<Echo>。这是一个类型安全的封装容器,用于指示以下两种情况:
    • InterfaceRequest 表示这是通道的服务器端(即客户端连接到通道的远程端)
    • 模板参数 Echo 表示客户端希望实现 Echo 协议的服务器将其自身绑定到此通道。与此对应的客户端(即在客户端用于表示此通道另一端的类型)是 fidl::InterfaceHandle<Echo>
  • 该处理程序只需获取从客户端发送的通道,并将其绑定到 Echo 绑定即可。
  • 发生这种情况后,Binding 会开始根据 Echo 协议处理通道中的消息。这是协议请求流水线的一个示例,后续教程将对此进行介绍。

注册协议请求处理程序

最后,代码向组件管理器注册处理程序:

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

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

  printf("Running echo server\n");
  return loop.Run();
}

第一行会初始化并传送传出目录,其中包含此组件向其他组件公开的协议,第二行用于将处理程序添加到传出目录。

除处理程序外,隐式第二个参数是此处理程序应注册到的名称。默认情况下,此参数是传入的协议的名称,这是由于 Echo 协议中存在 [Discoverable] 属性而生成的。换句话说,执行此行代码后,您应该能够对组件的 /out 目录调用 ls,并看到一个名为 fuchsia.examples.Echo 的条目。

测试服务器

重建:

fx build

然后,运行服务器组件:

ffx component run /core/ffx-laboratory:echo_server fuchsia-pkg://fuchsia.com/echo-hlcpp-server#meta/echo_server.cm

您应该会在设备日志 (ffx log) 中看到类似如下所示的输出:

[ffx-laboratory:echo_server][][I] Running echo server

服务器现在正在运行并等待传入请求。下一步是编写一个发送 Echo 协议请求的客户端。目前,您只需终止服务器组件即可: