前提条件
本教程基于 FIDL 服务器教程构建而成。如需查看全套 FIDL 教程,请参阅概览。
概览
本教程将实现一个 FIDL 协议的客户端,并针对上一个教程中创建的服务器运行该客户端。本教程中的客户端是异步的。同步客户端有替代教程。
客户端示例的结构
本教程随附的示例代码位于 Fuchsia 代码检出中的 //examples/fidl/cpp/client。它由客户端组件及其包含的软件包组成。如需详细了解如何构建组件,请参阅构建组件。
为了让客户端组件正常运行,//examples/fidl/cpp/client/BUILD.gn 中定义了三个目标:
客户端的原始可执行文件。这会生成一个具有指定输出名称的二进制文件,该文件可在 Fuchsia 上运行:
executable("bin") { output_name = "fidl_echo_cpp_client" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp", # This library is used to log messages. "//sdk/lib/syslog/cpp", # This library provides an asynchronous event loop implementation. "//sdk/lib/async-loop:async-loop-cpp", # This library is used to consume capabilities, e.g. protocols, # from the component's incoming directory. "//sdk/lib/component/incoming/cpp", ] }已设置为运行客户端可执行文件的组件。 组件是 Fuchsia 上的软件执行单元。组件由其清单文件描述。在这种情况下,
meta/client.cml将echo-client配置为在:bin中运行fidl_echo_cpp_client的可执行组件。fuchsia_component("echo-client") { component_name = "echo_client" manifest = "meta/client.cml" deps = [ ":bin" ] }服务器组件清单位于
//examples/fidl/cpp/client/meta/client.cml。清单中的二进制名称必须与BUILD.gn中定义的executable的输出名称匹配。{ 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_cpp_client", }, // Capabilities used by this component. use: [ { protocol: "fuchsia.examples.Echo" }, ], }然后,将该组件放入软件包中,软件包是 Fuchsia 上的软件分发单元。在这种情况下,软件包包含客户端和服务器组件,并且 <0x0
# C++ async client and server example package fuchsia_package("echo-cpp-client") { deps = [ ":echo-client", "//examples/fidl/cpp/server:echo-server", "//examples/fidl/echo-realm:echo_realm", ] }
构建客户端
将客户端添加到 build 配置中。此操作只需执行一次:
fx set core.x64 --with //examples/fidl/cpp/client构建客户端:
fx build examples/fidl/cpp/client
连接到协议
在其主函数中,客户端组件会连接到 fuchsia.examples/Echo 协议。
int main(int argc, const char** argv) {
// Connect to the |fuchsia.examples/Echo| protocol inside the component's
// namespace. This can fail so it's wrapped in a |zx::result| and it must be
// checked for errors.
zx::result client_end = component::Connect<fuchsia_examples::Echo>();
if (!client_end.is_ok()) {
FX_LOGS(ERROR) << "Synchronous error when connecting to the |Echo| protocol: "
<< client_end.status_string();
return -1;
}
// ...
同时,组件管理器会将请求路由到服务器组件。在服务器教程中实现的服务器处理程序将通过服务器端点进行调用,从而将渠道绑定到服务器实现。
需要注意的一点是,此代码假定组件的命名空间已包含 Echo 协议的实例。当运行教程末尾的示例时,系统会使用 realm 组件来路由来自服务器的协议,并将其提供给客户端组件。
初始化事件循环
异步客户端需要 async_dispatcher_t* 来异步监控来自渠道的消息。async::Loop 提供由事件循环支持的调度器实现。
// As in the server, the code sets up an async loop so that the client can
// listen for incoming responses from the server without blocking.
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
调度程序用于运行异步代码片段。它首先用于运行 EchoString 方法,并在收到响应时退出。然后,在调用 SendString 以监听事件后运行,并在收到 OnString 事件时退出。这两个实例之间的 ResetQuit() 调用允许客户端重用该循环。
初始化客户端
为了向服务器发出 Echo 请求,请使用上一步中的客户端端点、循环调度程序以及事件处理程序委托来初始化客户端:
// Define the event handler implementation for the client.
//
// The event handler delegate should be an object that implements the
// |fidl::AsyncEventHandler<Echo>| virtual class, which has methods
// corresponding to the events offered by the protocol. By default those
// methods are no-ops.
class EventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> {
public:
void OnString(fidl::Event<::fuchsia_examples::Echo::OnString>& event) override {
FX_LOGS(INFO) << "(Natural types) got event: " << event.response();
loop_.Quit();
}
// One may also override the |on_fidl_error| method, which is called
// when the client encounters an error and is going to teardown.
void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; }
explicit EventHandler(async::Loop& loop) : loop_(loop) {}
private:
async::Loop& loop_;
};
// Create an instance of the event handler.
EventHandler event_handler{loop};
// Create a client to the Echo protocol, passing our |event_handler| created
// earlier. The |event_handler| must live for at least as long as the
// |client|. The |client| holds a reference to the |event_handler| so it is a
// bug for the |event_handler| to be destroyed before the |client|.
fidl::Client client(std::move(*client_end), dispatcher, &event_handler);
进行 FIDL 调用
用于进行 FIDL 调用的方法通过解引用运算符公开,因此 FIDL 调用看起来像 client->EchoString(...)。
异步 EchoString 调用会接受一个请求对象,并接受一个回调,该回调会随调用结果(指示成功或失败)一起调用:
client->EchoString({"hello"}).ThenExactlyOnce(
[&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
// Check if the FIDL call succeeded or not.
if (!result.is_ok()) {
// If the call failed, log the error, and crash the program.
// Production code should do more graceful error handling depending
// on the situation.
FX_LOGS(ERROR) << "EchoString failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
// Dereference (->) the result object to access the response payload.
FX_LOGS(INFO) << "(Natural types) got response: " << result->response();
loop.Quit();
});
// Run the dispatch loop, until we receive a reply, in which case the callback
// above will quit the loop, breaking us out of the |Run| function.
loop.Run();
loop.ResetQuit();
您还可以使用 natural struct 和表支持的指定初始化样式双大括号语法:
client->EchoString({{.value = "hello"}})
.ThenExactlyOnce(
// ... callback ...
单向 SendString 调用没有回复,因此不需要回调。返回的结果表示发送请求时发生的任何错误。
// Make a SendString one way call with natural types.
fit::result<::fidl::Error> result = client->SendString({"hello"});
if (!result.is_ok()) {
FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
使用线网域对象进行调用
上述教程使用自然网域对象进行客户端调用:每次调用都会使用以自然网域对象表示的请求消息,并返回同样以自然网域对象表示的回复。在优化性能和堆分配时,可以使用 wire 网域对象进行调用。为此,请在进行调用时使用的解引用运算符(即 client.wire()->EchoString(...))之前插入 .wire()。
使用以下有线类型进行 EchoString 双向调用:
client.wire()->EchoString("hello").ThenExactlyOnce(
[&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
if (!result.ok()) {
FX_LOGS(ERROR) << "EchoString failed: " << result.error();
ZX_PANIC("%s", result.error().FormatDescription().c_str());
return;
}
fidl::WireResponse<fuchsia_examples::Echo::EchoString>& response = result.value();
std::string reply(response.response.data(), response.response.size());
FX_LOGS(INFO) << "(Wire types) got response: " << reply;
loop.Quit();
});
使用以下 wire 类型进行 SendString 单向调用:
fidl::Status wire_status = client.wire()->SendString("hello");
if (!wire_status.ok()) {
FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
}
在有线客户端调用中使用的相关类和函数与在自然客户端调用中使用的类和函数具有相似的形状。当需要不同的类或函数时,有线对应项通常以 Wire 为前缀。指针与引用以及实参结构也存在差异:
采用自然网域对象作为参数的
EchoString方法接受一个实参,即请求网域对象:client->EchoString({{.value = "hello"}}) .ThenExactlyOnce(如果请求载荷是结构体,则采用有线网域对象的
EchoString方法会将请求正文中的结构体字段列表扁平化为单独的实参(此处为单个fidl::StringView实参):client.wire()->EchoString("hello").ThenExactlyOnce(异步自然调用中的回调接受
fidl::Result<Method>&:client->EchoString({"hello"}).ThenExactlyOnce( [&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) { // Check if the FIDL call succeeded or not. if (!result.is_ok()) { // If the call failed, log the error, and crash the program. // Production code should do more graceful error handling depending // on the situation. FX_LOGS(ERROR) << "EchoString failed: " << result.error_value(); ZX_PANIC("%s", result.error_value().FormatDescription().c_str()); } // Dereference (->) the result object to access the response payload. FX_LOGS(INFO) << "(Natural types) got response: " << result->response(); loop.Quit(); }); // Run the dispatch loop, until we receive a reply, in which case the callback // above will quit the loop, breaking us out of the |Run| function. loop.Run(); loop.ResetQuit();- 如需检查成功或错误,请使用
is_ok()或is_error()方法。 - 如需在之后访问响应载荷,请使用
value()或->。 - 您可以移出结果或载荷,因为这些类型都实现了分层对象所有权。
异步连线调用中的回调接受
fidl::WireUnownedResult<Method>&:client.wire()->EchoString("hello").ThenExactlyOnce( [&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) { if (!result.ok()) { FX_LOGS(ERROR) << "EchoString failed: " << result.error(); ZX_PANIC("%s", result.error().FormatDescription().c_str()); return; } fidl::WireResponse<fuchsia_examples::Echo::EchoString>& response = result.value(); std::string reply(response.response.data(), response.response.size()); FX_LOGS(INFO) << "(Wire types) got response: " << reply; loop.Quit(); });- 如需检查是否成功,请使用
ok()方法。 - 如需在之后访问响应载荷,请使用
value()或->。 - 您必须在回调中同步使用结果。结果类型为 unowned,这意味着它仅借用由 FIDL 运行时在其他位置分配的响应。
- 如需检查成功或错误,请使用
单向调用在自然情况下也会采用整个请求网域对象,并在序列化情况下将请求结构字段展平为单独的实参:
// Make a SendString one way call with natural types. fit::result<::fidl::Error> result = client->SendString({"hello"}); fidl::Status wire_status = client.wire()->SendString("hello");
运行客户端
为了使客户端和服务器能够使用 Echo 协议进行通信,组件框架必须将 fuchsia.examples.Echo 功能从服务器路由到客户端。在本教程中,我们提供了一个组件来声明相应的功能和路由。
配置 build 以包含提供的软件包,该软件包包含回显 realm、服务器和客户端:
fx set core.x64 --with //examples/fidl/cpp/client构建 Fuchsia 映像:
fx build运行
echo_realm组件。这会创建客户端和服务器组件实例,并路由功能:ffx component run /core/ffx-laboratory:echo-realm fuchsia-pkg://fuchsia.com/echo-cpp-client#meta/echo_realm.cm启动
echo_client实例:ffx component start /core/ffx-laboratory:echo-realm/echo_client
当客户端尝试连接到 Echo 协议时,服务器组件会启动。您应该会在设备日志 (ffx log) 中看到类似如下所示的输出:
[echo_server][][I] Running C++ echo server with natural types
[echo_server][][I] Incoming connection for fuchsia.examples.Echo
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got event: hello
[echo_client][][I] (Wire types) got response: hello
[echo_client][][I] (Natural types) got event: hello
[echo_server][][I] Client disconnected
终止 realm 组件以停止执行并清理组件实例:
ffx component destroy /core/ffx-laboratory:echo_realm仅限 Wire 网域对象的客户端
fidl::Client 支持使用自然网域对象和有线网域对象进行调用。如果您只需要使用网络网域对象,可以创建一个 WireClient,该对象公开的等效方法调用接口与通过对 fidl::Client 调用 client.wire() 获得的子集相同。
WireClient 的创建方式与 Client 相同:
fidl::WireClient client(std::move(*client_end), dispatcher, &event_handler);
fidl::Client 始终以自然网域对象的形式向用户公开接收到的事件。另一方面,fidl::WireClient 将以有线网域对象的形式公开接收到的事件。为此,传递给 WireClient 的事件处理脚本需要实现 fidl::WireAsyncEventHandler<Protocol>。
实现自然事件处理脚本:
class EventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> { public: void OnString(fidl::Event<::fuchsia_examples::Echo::OnString>& event) override { FX_LOGS(INFO) << "(Natural types) got event: " << event.response(); loop_.Quit(); } // ... };实现连线事件处理脚本:
class EventHandler : public fidl::WireAsyncEventHandler<fuchsia_examples::Echo> { public: void OnString(fidl::WireEvent<fuchsia_examples::Echo::OnString>* event) override { FX_LOGS(INFO) << "(Natural types) got event: " << event->response.get(); loop_.Quit(); } // ... };
同步调用
WireClient 对象还支持同步调用,该调用会阻塞,直到收到响应并返回响应对象。这些类型可以使用 .sync() 访问器进行选择。(例如 client.sync()->EchoString())。
// Make a synchronous EchoString call, which blocks until it receives the response,
// then returns a WireResult object for the response.
fidl::WireResult result_sync = client.sync()->EchoString("hello");
ZX_ASSERT(result_sync.ok());
std::string_view response = result_sync->response.get();
FX_LOGS(INFO) << "Got synchronous response: " << response;
在同步调用中,系统会返回一个结果对象,以同步方式传达调用的成功或失败。
使用有线客户端的完整示例代码位于 Fuchsia 代码库的 //examples/fidl/cpp/client/wire 中。