前提条件
本教程基于 FIDL 服务器教程构建而成。如需查看完整的 FIDL 教程集,请参阅概览。
概览
本教程将实现一个 FIDL 协议客户端,并针对上一个教程中创建的服务器运行该客户端。本教程中的客户端是异步的。如需了解同步客户端,请参阅备用教程。
如果要自行编写代码,请删除以下目录:
rm -r examples/fidl/hlcpp/client/*
创建组件
在 examples/fidl/hlcpp/client
中创建一个新的组件项目:
将
main()
函数添加到examples/fidl/hlcpp/client/main.cc
:int main(int argc, const char** argv) { printf("Hello, world!\n"); return 0; }
在
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" ] }
在
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" }, ], }
创建组件后,请确保您可以将其添加到 build 配置中:
fx set core.x64 --with //examples/fidl/hlcpp/client:echo-client
构建 Fuchsia 映像:
fx build
修改 GN 依赖项
添加以下依赖项:
deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_hlcpp", "//sdk/lib/async-loop:async-loop-cpp", "//sdk/lib/async-loop:async-loop-default", "//sdk/lib/sys/cpp", ]
然后,将它们添加到
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
,然后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
capability 从服务器路由到客户端。在本教程中,我们提供了一个 realm 组件来声明适当的 capability 和路由。
将 build 配置为包含提供的软件包,其中包含 echo 领域、服务器和客户端:
fx set core.x64 --with //examples/fidl/hlcpp:echo-hlcpp-client
构建 Fuchsia 映像:
fx build
运行
echo_realm
组件。这会创建客户端和服务器组件实例并路由功能:ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-hlcpp-client#meta/echo_realm.cm
启动
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