实现 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. examples/fidl/hlcpp/server/main.cc 添加 main() 函数:

    #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. 将服务器添加到您的 build 配置中:

    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.ccmain() 函数的上方:

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 协议公开给其他组件。然后,组件管理器会将对 echo 协议的所有请求路由到我们的服务器。

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

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

开放协议的生命周期中更详细地介绍了这一完整流程。

添加新的依赖项

此新代码还需要以下其他依赖项:

  • "//zircon/system/ulib/async-loop:async-loop-cpp""//zircon/system/ulib/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/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() 函数中的异步代码会向默认调度程序自行注册,默认调度程序是一个静态线程局部变量(这就是不需要在其余代码中显式传递的原因)。在 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 协议请求的客户端。目前,您可以直接终止服务器组件:

ffx component destroy /core/ffx-laboratory:echo_server