前提条件
本教程基于 C++ 使用入门教程。
概览
在 Fuchsia 上使用 FIDL 的一个常见方面是跨协议传递协议端点。许多 FIDL 消息包括通道的客户端或服务器端,其中通道用于通过不同的 FIDL 协议进行通信。在这种情况下,客户端允许向指定协议发出请求,而服务器端必须实现指定协议。客户端和服务器端的另一组术语是协议和协议请求。
本教程将介绍以下内容:
- 这些客户端和服务器在 FIDL 和 C++ FIDL 绑定中的使用结束。
- 协议请求管道模式及其优势。
本教程的完整示例代码位于 //examples/fidl/cpp/request_pipelining
。
FIDL 协议
本教程实现了 fuchsia.examples
库中的 EchoLauncher
协议:
@discoverable
closed protocol EchoLauncher {
strict GetEcho(struct {
echo_prefix string:MAX_STRING_LENGTH;
}) -> (resource struct {
response client_end:Echo;
});
strict GetEchoPipelined(resource struct {
echo_prefix string:MAX_STRING_LENGTH;
request server_end:Echo;
});
};
这是一个协议,可让客户端检索 Echo
协议的实例。客户端可以指定前缀,生成的 Echo
实例会将该前缀添加到每个响应中。
您可以通过以下两种方法实现此目的:
GetEcho
:将前缀作为请求,并使用连接到Echo
协议实现的通道的客户端端进行响应。在响应中收到客户端后,客户端可以开始使用客户端对Echo
协议发出请求。GetEchoPipelined
:将通道的服务器端作为请求参数之一,并将Echo
的实现绑定到该端。假定发出请求的客户端已持有客户端端,并且会在调用GetEchoPipelined
后开始在该通道上发出Echo
请求。
顾名思义,后者使用一种称为协议请求流水线的模式,是首选方法。本教程将实现这两种方法。
实现服务器
实现 Echo 协议
此 Echo
实现允许指定前缀,以便区分 Echo
服务器的不同实例:
// Implementation of the Echo protocol that prepends a prefix to every response.
class EchoImpl final : public fidl::Server<fuchsia_examples::Echo> {
public:
explicit EchoImpl(std::string prefix) : prefix_(prefix) {}
// This method is not used in the request pipelining example, so requests are ignored.
void SendString(SendStringRequest& request, SendStringCompleter::Sync& completer) override {}
void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
FX_LOGS(INFO) << "Got echo request for prefix " << prefix_;
completer.Reply(prefix_ + request.value());
}
const std::string prefix_;
};
SendString
处理脚本为空,因为客户端只使用 EchoString
。
实现 EchoLauncher 协议
// Implementation of EchoLauncher. Each method creates an instance of EchoImpl
// with the specified prefix.
class EchoLauncherImpl final : public fidl::Server<fuchsia_examples::EchoLauncher> {
public:
explicit EchoLauncherImpl(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
void GetEcho(GetEchoRequest& request, GetEchoCompleter::Sync& completer) override {
FX_LOGS(INFO) << "Got non-pipelined request";
auto [client_end, server_end] = fidl::Endpoints<fuchsia_examples::Echo>::Create();
fidl::BindServer(dispatcher_, std::move(server_end),
std::make_unique<EchoImpl>(request.echo_prefix()));
completer.Reply(std::move(client_end));
}
void GetEchoPipelined(GetEchoPipelinedRequest& request,
GetEchoPipelinedCompleter::Sync& completer) override {
FX_LOGS(INFO) << "Got pipelined request";
fidl::BindServer(dispatcher_, std::move(request.request()),
std::make_unique<EchoImpl>(request.echo_prefix()));
}
async_dispatcher_t* dispatcher_;
};
对于 GetEcho
,代码首先需要实例化通道两端。然后,它使用服务器端启动 Echo
实例,然后使用客户端端发送响应。对于 GetEchoPipelined
,客户端已完成创建渠道两端的工作。它保留一端,并将另一端传递给服务器,因此所有代码只需将服务器端绑定到新的 EchoImpl
即可。
提供 EchoLauncher 协议
主循环与服务器教程中的主循环相同,但提供的是 EchoLauncher
,而不是 Echo
。
int main(int argc, char** argv) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);
zx::result result = outgoing.ServeFromStartupInfo();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
return -1;
}
result = outgoing.AddUnmanagedProtocol<fuchsia_examples::EchoLauncher>(
[dispatcher](fidl::ServerEnd<fuchsia_examples::EchoLauncher> server_end) {
FX_LOGS(INFO) << "Incoming connection for "
<< fidl::DiscoverableProtocolName<fuchsia_examples::EchoLauncher>;
fidl::BindServer(dispatcher, std::move(server_end),
std::make_unique<EchoLauncherImpl>(dispatcher));
});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to add EchoLauncher protocol: " << result.status_string();
return -1;
}
FX_LOGS(INFO) << "Running echo launcher server" << std::endl;
loop.Run();
return 0;
}
构建服务器
(可选)如需检查是否正确,请尝试构建服务器:
将您的 GN build 配置为包含服务器:
fx set core.x64 --with //examples/fidl/cpp/request_pipelining/server:echo-server
构建 Fuchsia 映像:
fx build
实现客户端
连接到 EchoLauncher
服务器后,客户端代码会使用 GetEcho
连接到 Echo
的一个实例,并使用 GetEchoPipelined
连接到另一个实例,然后对每个实例发出 EchoString
请求。
非流水线客户端
以下是非流水线代码:
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
int num_responses = 0;
// Connect to the EchoLauncher protocol
zx::result launcher_client_end = component::Connect<fuchsia_examples::EchoLauncher>();
ZX_ASSERT(launcher_client_end.is_ok());
fidl::Client launcher(std::move(*launcher_client_end), dispatcher);
// Make a non-pipelined request to get an instance of Echo
launcher->GetEcho({"non pipelined: "})
.ThenExactlyOnce([&](fidl::Result<fuchsia_examples::EchoLauncher::GetEcho>& result) {
ZX_ASSERT(result.is_ok());
// Take the Echo client end in the response, bind it to another client, and
// make an EchoString request on it.
fidl::SharedClient echo(std::move(result->response()), dispatcher);
echo->EchoString({"hello!"})
.ThenExactlyOnce(
// Clone |echo| into the callback so that the client
// is only destroyed after we receive the response.
[&, echo = echo.Clone()](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ZX_ASSERT(result.is_ok());
FX_LOGS(INFO) << "Got echo response " << result->response();
if (++num_responses == 2) {
loop.Quit();
}
});
});
auto [client_end, server_end] = fidl::Endpoints<fuchsia_examples::Echo>::Create();
// Make a pipelined request to get an instance of Echo
ZX_ASSERT(launcher->GetEchoPipelined({"pipelined: ", std::move(server_end)}).is_ok());
// A client can be initialized using the client end without waiting for a response
fidl::Client echo_pipelined(std::move(client_end), dispatcher);
echo_pipelined->EchoString({"hello!"})
.ThenExactlyOnce([&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ZX_ASSERT(result.is_ok());
FX_LOGS(INFO) << "Got echo response " << result->response();
if (++num_responses == 2) {
loop.Quit();
}
});
loop.Run();
return num_responses == 2 ? 0 : 1;
}
此代码包含两层回调:
- 外层负责处理启动器
GetEcho
响应。 - 内部层会处理
EchoString
响应。
在 GetEcho
响应回调中,代码会将返回的客户端端点绑定到 fidl::SharedClient<Echo>
,并将克隆项放入 EchoString
回调,以延长客户端的生命周期,直到收到回声响应为止,这很可能发生在顶级回调返回之后。
流水线客户端
尽管必须先创建一对端点,但流水线代码要简单得多:
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
int num_responses = 0;
// Connect to the EchoLauncher protocol
zx::result launcher_client_end = component::Connect<fuchsia_examples::EchoLauncher>();
ZX_ASSERT(launcher_client_end.is_ok());
fidl::Client launcher(std::move(*launcher_client_end), dispatcher);
// Make a non-pipelined request to get an instance of Echo
launcher->GetEcho({"non pipelined: "})
.ThenExactlyOnce([&](fidl::Result<fuchsia_examples::EchoLauncher::GetEcho>& result) {
ZX_ASSERT(result.is_ok());
// Take the Echo client end in the response, bind it to another client, and
// make an EchoString request on it.
fidl::SharedClient echo(std::move(result->response()), dispatcher);
echo->EchoString({"hello!"})
.ThenExactlyOnce(
// Clone |echo| into the callback so that the client
// is only destroyed after we receive the response.
[&, echo = echo.Clone()](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ZX_ASSERT(result.is_ok());
FX_LOGS(INFO) << "Got echo response " << result->response();
if (++num_responses == 2) {
loop.Quit();
}
});
});
auto [client_end, server_end] = fidl::Endpoints<fuchsia_examples::Echo>::Create();
// Make a pipelined request to get an instance of Echo
ZX_ASSERT(launcher->GetEchoPipelined({"pipelined: ", std::move(server_end)}).is_ok());
// A client can be initialized using the client end without waiting for a response
fidl::Client echo_pipelined(std::move(client_end), dispatcher);
echo_pipelined->EchoString({"hello!"})
.ThenExactlyOnce([&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
ZX_ASSERT(result.is_ok());
FX_LOGS(INFO) << "Got echo response " << result->response();
if (++num_responses == 2) {
loop.Quit();
}
});
loop.Run();
return num_responses == 2 ? 0 : 1;
}
与客户端教程不同,异步循环会运行一次,直至完成一次,它会同时运行非流水线代码和流水线代码,以遵循操作顺序。客户端会跟踪收到的响应数量,以便在预计不会再收到来自服务器的消息时退出循环。
构建客户端
(可选)如需检查是否正确,请尝试构建客户端:
将您的 GN build 配置为包含服务器:
fx set core.x64 --with //examples/fidl/cpp/request_pipelining/client:echo-client
构建 Fuchsia 映像:
fx build
运行示例代码
在本教程中,我们提供了一个 realm 组件,用于为 fuchsia.examples.Echo
和 fuchsia.examples.EchoLauncher
声明适当的 capability 和路由。
配置 build 以包含提供的软件包,其中包含 echo 领域、服务器和客户端:
fx set core.x64 --with //examples/fidl/cpp/request_pipelining
构建 Fuchsia 映像:
fx build
运行
echo_realm
组件。这会创建客户端和服务器组件实例并路由功能:ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-launcher-cpp#meta/echo_realm.cm
启动
echo_client
实例:ffx component start /core/ffx-laboratory:echo_realm/echo_client
当客户端尝试连接到 EchoLauncher
协议时,服务器组件会启动。您应该会在设备日志 (ffx log
) 中看到类似如下所示的输出:
[echo_server][I] Running echo launcher server
[echo_server][I] Incoming connection for fuchsia.examples.EchoLauncher
[echo_server][I] Got non-pipelined request
[echo_server][I] Got pipelined request
[echo_server][I] Got echo request for prefix pipelined:
[echo_server][I] Got echo request for prefix non pipelined:
[echo_client][I] Got echo response pipelined: hello!
[echo_client][I] Got echo response non pipelined: hello!
根据打印顺序,您可以看到流水线处理速度较快。由于请求流水线可以节省客户端和服务器之间的往返次数,因此流水线示例的回声响应会先到达,即使非流水线请求先发送也是如此。请求管道也能简化代码。
如需详细了解协议请求流水线,包括如何处理可能失败的协议请求,请参阅 FIDL API 评分标准。
终止 Realm 组件以停止执行并清理组件实例:
ffx component destroy /core/ffx-laboratory:echo_realm