实现 FIDL 客户端

前提条件

本教程基于 FIDL 服务器教程构建。对于 完整的 FIDL 教程,请参阅概览

概览

本教程为 FIDL 协议实现了一个客户端,并针对 在上一个教程中创建的服务器。此 是异步的请参阅备用教程, 同步客户端。

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

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

创建组件

examples/fidl/hlcpp/client 中创建一个新的组件项目:

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

    int main(int argc, const char** argv) {
      printf("Hello, world!\n");
      return 0;
    }
    
  2. examples/fidl/hlcpp/client/BUILD.gn 中声明客户端的目标:

    import("//build/components.gni")
    
    
    # Declare an executable for the client.
    executable("bin") {
      output_name = "fidl_echo_hlcpp_client"
      sources = [ "main.cc" ]
    }
    
    fuchsia_component("echo-client") {
      component_name = "echo_client"
      manifest = "meta/client.cml"
      deps = [ ":bin" ]
    }
    
  3. examples/fidl/hlcpp/client/meta/client.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_client",
        },
    
        // Capabilities used by this component.
        use: [
            { protocol: "fuchsia.examples.Echo" },
        ],
    }
    
    
  4. 创建组件后,请务必将其添加到 build 配置:

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

    fx build

修改 GN 依赖项

  1. 添加以下依赖项:

      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. 然后,将它们添加到 main.cc 中:

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

    导入这些依赖项的原因 服务器教程

连接到服务器

本部分中的步骤说明了如何向 main() 函数添加代码 用于将客户端连接到服务器并向服务器发出请求的命令。

初始化事件循环

与在服务器中一样,代码首先设置一个异步循环,以便客户端可以 在不阻止的情况下监听来自服务器的传入响应。

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

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  printf("Async loop starting\n");
  loop.Run();
  printf("Async loop finished\n");
  return num_responses == 2 ? 0 : 1;
}

初始化代理类

在 FIDL 中,代理会指定代码 由 FIDL 绑定生成的,可让用户 远程过程调用。在 HLCPP 中,代理采用以下形式 类,其中包含与每个 FIDL 协议方法相对应的方法。

然后,代码会为 Echo 协议创建一个代理类,并将其连接起来 发送到服务器。

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

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  printf("Async loop starting\n");
  loop.Run();
  printf("Async loop finished\n");
  return num_responses == 2 ? 0 : 1;
}
  • fuchsia::examples::EchoPtr 是以下类的别名 由绑定生成的 fidl::InterfaceRequest<fuchsia::examples::Echo>
  • 与服务器中使用的 fidl::Binding<fuchsia::examples::Echo> 类似, fidl::InterfaceRequest<fuchsia::examples::Echo> 由 FIDL 参数化 以及它将通过该通道代理请求的通道,并监听 传入的响应和事件。
  • 代码会调用 EchoPtr::NewRequest(),这会创建一个通道。 将类绑定到通道的一端,并返回 。
  • 通道的结尾会传递给 sys::ServiceDirectory::Connect()
    • 类似于在服务器上调用 context->out()->AddPublicService() Connect 有一个隐式的第二个参数, 名称 ("fuchsia.examples.Echo")。这是处理程序的输入 在上一个教程中定义的所有参数都来自: 客户端将其传递给 Connect,后者随后将其传递给处理程序。

需要注意的一点是,此代码假定 /svc 已经 包含 Echo 协议的实例。默认情况下,并非如此 这要归功于组件框架提供的沙盒

设置错误处理程序

最后,该代码为代理设置一个错误处理程序:

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

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  printf("Async loop starting\n");
  loop.Run();
  printf("Async loop finished\n");
  return num_responses == 2 ? 0 : 1;
}

向服务器发送请求

代码会向服务器发出两个请求:

  • EchoString 请求
  • SendString 请求
int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  printf("Async loop starting\n");
  loop.Run();
  printf("Async loop finished\n");
  return num_responses == 2 ? 0 : 1;
}

对于 EchoString,代码会传入回调来处理响应。 SendString 不需要此类回调,因为该方法不需要 没有任何响应。

设置事件处理脚本

然后,该代码会为任何传入的 OnString 事件设置处理程序:

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

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  printf("Async loop starting\n");
  loop.Run();
  printf("Async loop finished\n");
  return num_responses == 2 ? 0 : 1;
}

终止事件循环

代码会等待接收对 EchoString 方法的响应以及 OnString 事件(在当前实现中,该事件是在接收到 SendString 请求),然后再退出循环。代码返回一个成功的退出 才能同时收到响应和事件事件:

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

  fuchsia::examples::EchoPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  echo_proxy.set_error_handler([&loop](zx_status_t status) {
    printf("Error reading incoming message: %d\n", status);
    loop.Quit();
  });

  int num_responses = 0;
  echo_proxy->SendString("hi");
  echo_proxy->EchoString("hello", [&](std::string response) {
    printf("Got response %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  });
  echo_proxy.events().OnString = [&](std::string response) {
    printf("Got event %s\n", response.c_str());
    if (++num_responses == 2) {
      loop.Quit();
    }
  };

  printf("Async loop starting\n");
  loop.Run();
  printf("Async loop finished\n");
  return num_responses == 2 ? 0 : 1;
}

运行客户端

为使客户端和服务器使用 Echo 协议进行通信, 组件框架必须将 fuchsia.examples.Echo 功能从 从服务器发送到客户端在本教程中, 领域 组件是 来声明相应的功能和路线。

  1. 配置 build,使其包含提供的软件包,其中包含 echo 领域、服务器和客户端:

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

    fx build
  3. 运行 echo_realm 组件。这会创建客户端和服务器组件 并路由功能:

    ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-hlcpp-client#meta/echo_realm.cm
  4. 启动 echo_client 实例:

    ffx component start /core/ffx-laboratory:echo_realm/echo_client

在客户端尝试连接到 Echo 时,服务器组件将启动 协议。您应该会在设备日志中看到类似于以下内容的输出 (ffx log):

[echo_server][][I] Running echo server
[echo_client][][I] Got event hi
[echo_client][][I] Got response hello

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

ffx component destroy /core/ffx-laboratory:echo_realm