实现 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. fuchsia.examples FIDL 库目标添加为 examples/fidl/hlcpp/server/BUILD.gn 中的 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 的方法不接受回调,因为此方法会接受回调 没有响应。相反,实现会发送 OnString 事件 使用 Echo_EventSender
  • 该类包含一个指向 Echo_EventSender 的指针。此设置将设置为 main() 函数后面的部分。

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

fx build

提供协议

要运行实现 FIDL 协议的组件,请向 组件管理器,以便将该 FIDL 协议提供给其他 组件。然后,组件管理器会路由对 echo 协议的所有请求 发送到我们的服务器。

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

传递给它的处理程序是一个函数,它将获取一个通道(其远程 end 归客户端所有),并将其绑定到一个 fidl::Binding 使用服务器实现进行初始化。fidl::Binding 是一个类 从采用 FIDL 协议实现和通道的 FIDL 运行时中获取, 然后监听通道是否传入请求。然后解码 请求,将它们分派给服务器类中的正确方法 将任何响应写回客户端。我们的主要方法将继续监听 异步循环传入请求的影响。

此完整过程将在 开放协议的生命周期

添加新的依赖项

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

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

    executable("bin") {
      output_name = "fidl_echo_hlcpp_server"
      sources = [ "main.cc" ]
      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. 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() 中的异步代码 函数将向默认调度程序注册自身,该调度程序是静态 线程局部变量(这就是它不需要 代码的其余部分)。在主函数的末尾,代码会运行异步循环。

初始化绑定

然后,代码会按上文所述初始化 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();
}

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

  • 协议的实现。
  • 绑定将在其中监听该协议的消息的通道。 该绑定首先使用 echo 实现进行初始化, 之后会绑定到某个频道

该代码还会设置用于向客户端发送事件的事件发送者。 对 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();
}

第一行会初始化并提供传出目录,其中包含 第二行 将处理程序添加到传出目录。

处理程序外,隐式第二个参数是 资源。默认情况下,此参数是协议的名称 这是由于存在 [Discoverable] Echo 协议中的属性。换句话说,执行 使用此行,您应该能够在组件的 /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

注意:组件是使用其 组件网址 , 该值取决于 `fuchsia-pkg://` 架构。

您应该会在设备日志中看到类似于以下内容的输出 (ffx log):

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

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

ffx component destroy /core/ffx-laboratory:echo_server