处理多个客户端

前提条件

本教程基于 HLCPP 入门教程

概览

本教程更新了入门教程中的 Echo 客户端,以与服务器建立多个连接,并更新 Echo 服务器以处理多个客户端连接。如需运行服务器的多个实例(或多个 FIDL 协议),请参阅有关服务的教程。

本教程的完整示例代码位于 //examples/fidl/hlcpp/multiple_clients

实现服务器

之前的实现中,main() 函数会初始化单个 fidl::Binding,并将所有传入请求绑定到该 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.Bind 会覆盖第一个客户端的通道。如需支持多个客户端,请使用 fidl::BindingSet 跟踪多个 fidl::Binding(每个客户端一个):

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

  EchoImpl impl;
  fidl::BindingSet<fuchsia::examples::Echo> bindings;
  auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
  context->outgoing()->AddPublicService(bindings.GetHandler(&impl));

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

绑定集还简化了代码,因为它不再需要创建自定义处理程序。绑定集有一个 GetHandler 方法,该方法会返回一个用于创建新 Binding 并将其存储在矢量中的处理脚本。

如需使用 fidl::BindingSet,请添加 lib/fidl/cpp/binding_set.h

实现客户端

为了管理连接到同一协议的多个客户端,FIDL HLCPP 运行时库提供了 fidl::BindingSet 的 anolog:fidl::InterfacePtrSet。使用该类编写代码,以便与同一协议建立多个连接:

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

  auto context = sys::ComponentContext::Create();

  fidl::InterfacePtrSet<fuchsia::examples::Echo> echoers;
  for (int i = 0; i < kNumClients; i++) {
    fuchsia::examples::EchoPtr proxy;
    context->svc()->Connect(proxy.NewRequest());
    proxy.set_error_handler([&loop](zx_status_t status) {
      std::cout << "Error reading incoming message: " << status << std::endl;
      loop.Quit();
    });
    echoers.AddInterfacePtr(std::move(proxy));
  }

  size_t responses = 0;
  for (auto& echoer : echoers.ptrs()) {
    (*echoer)->EchoString("Hello echoer " + std::to_string(responses++), [&](std::string response) {
      std::cout << "Got response " << response << std::endl;
      if (responses == echoers.size()) {
        loop.Quit();
      }
    });
  }

  loop.Run();
  return responses == kNumClients ? 0 : 1;
}

设置代理和发出请求的代码与客户端教程中的代码相同,只不过它使用接口指针集来简化向一组客户端广播消息的过程。使用 fidl::InterfacePtrSetfidl::BindingSet 的另一个好处是,如果任何绑定或接口指针在其通道上发生错误,系统都会自动将其从集合中移除。

如需使用 fidl::InterfacePtrSet,请添加 lib/fidl/cpp/interface_ptr_set.h

运行示例

为了让客户端和服务器能够使用 Echo 协议进行通信,组件框架必须将 fuchsia.examples.Echo capability 从服务器路由到客户端。在本教程中,我们提供了一个 realm 组件来声明适当的 capability 和路由。

探索 Realm 组件的完整源代码
  1. 将 build 配置为包含包含 echo 领域、服务器和客户端的提供的软件包:

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

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

    ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-hlcpp-multi-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 response Hello echoer 0

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

ffx component destroy /core/ffx-laboratory:echo_realm