初识 Fuchsia 中的 IPC 机制

FIDL(Fuchsia Interface Definition Language)是 fuchsia 系统上的远程调用机制(IPC),类似 Android 中的 binder。

FIDL 的主要作用是允许不同的客户端和服务端相互操作。因为客户端的语言不同,可能会包括了 C/C++、Rust、Dart 等,但是服务端不会为每个服务都提供一种协议来实现,所以就需要 FIDL 工具链。服务端的开发人员仅需要创建一个定义文件,该文件定义了协议。然后,FIDL 编译器可以使用此文件以任何受支持的目标语言生成客户端和服务端的代码(.fidl)。

图:服务器以 C++ 语写给客户,以多种语言书写

FIDL 架构

FIDL 主要分为以下三个部分:

  • FIDL 定义文件
  • 客户端代码
  • 服务端代码

其中,客户端代码和服务端代码均由 FIDL 编译器工具链生成。

简易 FIDL 通信举例

1.编译 FIDL

首先定义 FIDL 协议://examples/fidl/fuchsia.examples/types.test.fidl

  1. library fuchsia.examples;
  2. const BOARD_SIZE uint8 = 9;
  3. const NAME string = "Tic-Tac-Toe";
  4. type FileMode = strict bits : uint16 {
  5. READ = 0b001;
  6. WRITE = 0b010;
  7. EXECUTE = 0b100;
  8. };
  9. type LocationType = strict enum {
  10. MUSEUM = 1;
  11. AIRPORT = 2;
  12. RESTAURANT = 3;
  13. };
  14. type Color = struct {
  15. id uint32;
  16. name string:MAX_STRING_LENGTH = "red";
  17. };
  18. type JsonValue = strict union {
  19. 1: reserved;
  20. 2: int_value int32;
  21. 3: string_value string:MAX_STRING_LENGTH;
  22. };
  23. type User = table {
  24. 1: reserved;
  25. 2: age uint8;
  26. 3: name string:MAX_STRING_LENGTH;
  27. };
  28. type GameState = struct {};
  29. protocol TicTacToe {
  30. StartGame(struct {
  31. start_first bool;
  32. });
  33. MakeMove(struct {
  34. row uint8;
  35. col uint8;
  36. }) -> (struct {
  37. success bool;
  38. new_state box<GameState>;
  39. });
  40. -> OnOpponentMove(struct {
  41. new_state GameState;
  42. });
  43. };

以及:examples/fidl/fuchsia.examples/echo.test.fidl

  1. // Copyright 2020 The Fuchsia Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. library fuchsia.examples;
  5. const MAX_STRING_LENGTH uint64 = 32;
  6. @discoverable
  7. protocol Echo {
  8. EchoString(struct {
  9. value string:MAX_STRING_LENGTH;
  10. }) -> (struct {
  11. response string:MAX_STRING_LENGTH;
  12. });
  13. SendString(struct {
  14. value string:MAX_STRING_LENGTH;
  15. });
  16. -> OnString(struct {
  17. response string:MAX_STRING_LENGTH;
  18. });
  19. };
  20. @discoverable
  21. protocol EchoLauncher {
  22. GetEcho(struct {
  23. echo_prefix string:MAX_STRING_LENGTH;
  24. }) -> (resource struct {
  25. response client_end:Echo;
  26. });
  27. GetEchoPipelined(resource struct {
  28. echo_prefix string:MAX_STRING_LENGTH;
  29. request server_end:Echo;
  30. });
  31. };
  32. service EchoService {
  33. regular_echo client_end:Echo;
  34. reversed_echo client_end:Echo;
  35. };

接着为 FIDL 库创建 GN 文件

  1. # Copyright 2020 The Fuchsia Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. # Import the fidl GN template.
  5. import("//build/fidl/fidl.gni")
  6. # Define a target for our FIDL library by passing it the FIDL source files
  7. # that make up the library.
  8. fidl("fuchsia.examples") {
  9. sources = [
  10. "echo.test.fidl",
  11. "types.test.fidl",
  12. ]
  13. fuzzers = [
  14. {
  15. protocol = "fuchsia.examples.Echo"
  16. },
  17. ]
  18. }

随后就可以编译 FIDL 库了:fx set core.x64 —with //examples/fidl/fuchsia.examples

2.为 FIDL 协议实现服务端

创建组件的过程略过。

首先添加 FIDL 库的依赖,在 bin 中添加如下:

  1. executable("bin") {
  2. output_name = "fidl_echo_hlcpp_server"
  3. sources = [ "main.cc" ]
  4. deps = [
  5. "//examples/fidl/fuchsia.examples",
  6. "//sdk/lib/fidl/cpp",
  7. "//sdk/lib/sys/cpp",
  8. "//zircon/system/ulib/async-loop:async-loop-cpp",
  9. "//zircon/system/ulib/async-loop:async-loop-default",
  10. ]
  11. }

并且将如下头文件添加依赖:

  1. #include <fuchsia/examples/cpp/fidl.h>
  2. #include <lib/async-loop/cpp/loop.h>
  3. #include <lib/async-loop/default.h>
  4. #include <lib/fidl/cpp/binding.h>
  5. #include <lib/sys/cpp/component_context.h>

在 main 函数上方添加

  1. class EchoImpl : public fuchsia::examples::Echo {
  2. public:
  3. void EchoString(std::string value, EchoStringCallback callback) override { callback(value); }
  4. void SendString(std::string value) override {
  5. if (event_sender_) {
  6. event_sender_->OnString(value);
  7. }
  8. }
  9. fuchsia::examples::Echo_EventSender* event_sender_;
  10. };

在 main 函数中添加如下:

  1. int main(int argc, const char** argv) {
  2. async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  3. EchoImpl impl;
  4. fidl::Binding<fuchsia::examples::Echo> binding(&impl);
  5. impl.event_sender_ = &binding.events();
  6. fidl::InterfaceRequestHandler<fuchsia::examples::Echo> handler =
  7. [&](fidl::InterfaceRequest<fuchsia::examples::Echo> request) {
  8. binding.Bind(std::move(request));
  9. };
  10. auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
  11. context->outgoing()->AddPublicService(std::move(handler));
  12. printf("Running echo server\n");
  13. return loop.Run();
  14. }

随后就可以编译 Fuchsia,运行服务端后可以看到输出,并且服务端并没有立刻退出,而是继续等待传入请求。

3.为 FIDL 协议实现客户端

这里我们以异步客户端距离。

在 GN 中添加如下依赖:

  1. deps = [
  2. "//examples/fidl/fuchsia.examples",
  3. "//sdk/lib/sys/cpp",
  4. "//zircon/system/ulib/async-loop:async-loop-cpp",
  5. "//zircon/system/ulib/async-loop:async-loop-default",
  6. ]

在 main.cc 中添加:

  1. #include <fuchsia/examples/cpp/fidl.h>
  2. #include <lib/async-loop/cpp/loop.h>
  3. #include <lib/async-loop/default.h>
  4. #include <lib/sys/cpp/component_context.h>

在组建清单中将协议包含在客户端组件的沙盒中:

  1. {
  2. "include": [
  3. "syslog/client.shard.cmx"
  4. ],
  5. "program": {
  6. "binary": "bin/fidl_echo_hlcpp_client"
  7. },
  8. "sandbox": {
  9. "services": [
  10. "fuchsia.examples.Echo"
  11. ]
  12. }
  13. }

main 函数中:

  1. int main(int argc, const char** argv) {
  2. async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  3. fuchsia::examples::EchoPtr echo_proxy;
  4. auto context = sys::ComponentContext::Create();
  5. context->svc()->Connect(echo_proxy.NewRequest());
  6. echo_proxy.set_error_handler([&loop](zx_status_t status) {
  7. printf("Error reading incoming message: %d\n", status);
  8. loop.Quit();
  9. });
  10. int num_responses = 0;
  11. echo_proxy->SendString("hi");
  12. echo_proxy->EchoString("hello", [&](std::string response) {
  13. printf("Got response %s\n", response.c_str());
  14. if (++num_responses == 2) {
  15. loop.Quit();
  16. }
  17. });
  18. echo_proxy.events().OnString = [&](std::string response) {
  19. printf("Got event %s\n", response.c_str());
  20. if (++num_responses == 2) {
  21. loop.Quit();
  22. }
  23. };
  24. loop.Run();
  25. return num_responses == 2 ? 0 : 1;
  26. }

4.在 Fuchsia 上将服务端和客户端一起运行

如果我们直接运行服务端和客户端是无法正常运行的,这时我们需要使用 Launcher 工具。

这是因为客户端不会在它的沙盒里自动获取 Echo 协议。为了让它工作,就需要使用 Launcher 工具来发布服务,创建一个新的环境给客户端,这个环境提供服务端的协议然后在它里面运行客户端。

上面的解释看起来非常拗口,简单来说的话就是,客户端没有服务端和协议的环境,所以需要 Launcher 工具来创建这个环境。

运行的命令如下:

  1. fx shell run fuchsia-pkg://fuchsia.com/echo-launcher#meta/launcher.cmx fuchsia-pkg://fuchsia.com/echo-hlcpp-client#meta/echo-client.cmx fuchsia-pkg://fuchsia.com/echo-hlcpp-server#meta/echo-server.cmx fuchsia.examples.Echo