前提条件
本教程以网域对象教程为基础。如需查看全套 FIDL 教程,请参阅概览。
概览
本教程介绍了如何为 FIDL 协议 (fuchsia.examples/Echo) 实现服务器并在 Fuchsia 上高效运转该服务器。此协议包含每种类型的一个方法:单向方法、双向方法和事件:
@discoverable
closed protocol Echo {
strict EchoString(struct {
value string:MAX_STRING_LENGTH;
}) -> (struct {
response string:MAX_STRING_LENGTH;
});
strict SendString(struct {
value string:MAX_STRING_LENGTH;
});
strict -> OnString(struct {
response string:MAX_STRING_LENGTH;
});
};
如需详细了解 FIDL 方法和消息传递模型,请参阅 FIDL 概念页面。
本文档介绍了如何完成以下任务:
服务器示例的结构
本教程随附的示例代码位于 Fuchsia 代码检出中的 //examples/fidl/cpp/server。它由服务器组件及其包含的软件包组成。如需详细了解如何构建组件,请参阅构建组件。
为了使服务器组件正常运行,//examples/fidl/cpp/server/BUILD.gn 中定义了三个目标:
服务器的原始可执行文件。这会生成一个具有指定输出名称的二进制文件,该文件可在 Fuchsia 上运行:
executable("bin") { output_name = "fidl_echo_cpp_server" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp", # This library is used to log messages. "//sdk/lib/syslog/cpp", # This library is used to publish capabilities, e.g. protocols, # to the component's outgoing directory. "//sdk/lib/component/outgoing/cpp", # This library provides an the asynchronous event loop implementation. "//sdk/lib/async-loop:async-loop-cpp", ] }一种设置为运行服务器可执行文件的组件。 组件是 Fuchsia 上的软件执行单元。组件由其清单文件描述。在这种情况下,
meta/server.cml将echo-server配置为在:bin中运行fidl_echo_cpp_server的可执行组件。fuchsia_component("echo-server") { component_name = "echo_server" manifest = "meta/server.cml" deps = [ ":bin" ] }服务器组件清单位于
//examples/fidl/cpp/server/meta/server.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_server", }, // Capabilities provided by this component. capabilities: [ { protocol: "fuchsia.examples.Echo" }, ], expose: [ { protocol: "fuchsia.examples.Echo", from: "self", }, ], }然后,将该组件放入软件包中,软件包是 Fuchsia 上的软件分发单元。在这种情况下,软件包仅包含一个组件。
fuchsia_package("echo-cpp-server") { package_name = "echo-cpp-server" deps = [ ":echo-server" ] }
构建服务器
您可以通过以下方式构建服务器软件包:
将服务器添加到 build 配置中。此操作只需执行一次:
fx set core.x64 --with //examples/fidl/cpp/server构建服务器软件包:
fx build //examples/fidl/cpp/server
实现 FIDL 协议
EchoImpl 为 fuchsia.examples/Echo 协议实现了服务器请求处理程序。为此,EchoImpl 会继承生成的纯虚服务器接口 fidl::Server<fuchsia_examples::Echo>,并替换其与每个单向和双向调用对应的纯虚方法:
class EchoImpl : public fidl::Server<fuchsia_examples::Echo> {
public:
// The handler for `fuchsia.examples/Echo.EchoString`.
//
// For two-way methods (those with a response) like this one, the completer is
// used complete the call: either to send the reply via |completer.Reply|, or
// close the channel via |completer.Close|.
//
// |EchoStringRequest| exposes the same API as the request struct domain
// object, that is |fuchsia_examples::EchoEchoStringRequest|.
void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
// Call |Reply| to reply synchronously with the request value.
completer.Reply({{.response = request.value()}});
}
// The handler for `fuchsia.examples/Echo.SendString`.
//
// For fire-and-forget methods like this one, the completer is normally not
// used, but its |Close(zx_status_t)| method can be used to close the channel,
// either when the protocol has reached its intended terminal state or the
// server encountered an unrecoverable error.
//
// |SendStringRequest| exposes the same API as the request struct domain
// object, that is |fuchsia_examples::EchoSendStringRequest|.
void SendString(SendStringRequest& request, SendStringCompleter::Sync& completer) override {
ZX_ASSERT(binding_ref_.has_value());
// Handle a SendString request by sending an |OnString| event (an
// unsolicited server-to-client message) back to the client.
fit::result result = fidl::SendEvent(*binding_ref_)->OnString({request.value()});
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Error sending event: " << result.error_value();
}
}
// ... other methods from examples/fidl/cpp/server/main.cc omitted, to be covered later.
private:
// `ServerBindingRef` can be used to:
// - Control the binding, such as to unbind the server from the channel or
// close the channel.
// - Send events back to the client.
// See the documentation comments on |fidl::ServerBindingRef|.
std::optional<fidl::ServerBindingRef<fuchsia_examples::Echo>> binding_ref_;
};
将实现绑定到服务器端点
实现请求处理程序只是整个过程的一部分。服务器需要能够监控到达服务器端点的新消息。为此,EchoImpl 又定义了两种方法:一种是 BindSelfManagedServer 静态工厂函数,用于创建新的 EchoImpl 实例以处理新服务器端点 fidl::ServerEnd<fuchsia_examples::Echo> 上的请求;另一种是 OnUnbound 方法,在连接断开时调用:
/* Inside `class EchoImpl {`... */
// Bind a new implementation of |EchoImpl| to handle requests coming from
// the server endpoint |server_end|.
static void BindSelfManagedServer(async_dispatcher_t* dispatcher,
fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
// Create a new instance of |EchoImpl|.
std::unique_ptr impl = std::make_unique<EchoImpl>();
EchoImpl* impl_ptr = impl.get();
// |fidl::BindServer| takes a FIDL protocol server implementation and a
// channel. It asynchronously reads requests off the channel, decodes them
// and dispatches them to the correct handler on the server implementation.
//
// The FIDL protocol server implementation can be passed as a
// |std::shared_ptr|, |std::unique_ptr|, or raw pointer. For shared and
// unique pointers, the binding will manage the lifetime of the
// implementation object. For raw pointers, it's up to the caller to ensure
// that the implementation object outlives the binding but does not leak.
//
// See the documentation comment of |fidl::BindServer|.
fidl::ServerBindingRef binding_ref = fidl::BindServer(
dispatcher, std::move(server_end), std::move(impl), std::mem_fn(&EchoImpl::OnUnbound));
// Put the returned |binding_ref| into the |EchoImpl| object.
impl_ptr->binding_ref_.emplace(std::move(binding_ref));
}
// This method is passed to the |BindServer| call as the last argument,
// which means it will be called when the connection is torn down.
// In this example we use it to log some connection lifecycle information.
// Production code could do more things such as resource cleanup.
void OnUnbound(fidl::UnbindInfo info, fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
// |is_user_initiated| returns true if the server code called |Close| on a
// completer, or |Unbind| / |Close| on the |binding_ref_|, to proactively
// teardown the connection. These cases are usually part of normal server
// shutdown, so logging is unnecessary.
if (info.is_user_initiated()) {
return;
}
if (info.is_peer_closed()) {
// If the peer (the client) closed their endpoint, log that as INFO.
FX_LOGS(INFO) << "Client disconnected";
} else {
// Treat other unbind causes as errors.
FX_LOGS(ERROR) << "Server error: " << info;
}
}
发布协议实现
实现 FIDL 协议的组件可以向其他组件公开该 FIDL 协议。这是通过将协议实现发布到组件的 传出目录来完成的。如需详细了解整个流程,请参阅协议打开的生命周期。我们可以使用 C++ 组件运行时库中的 component::OutgoingDirectory 来执行繁重的工作。
如需依赖组件运行时库,请执行以下操作:
executable("bin") {
output_name = "fidl_echo_cpp_server"
sources = [ "main.cc" ]
deps = [
"//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",
# This library is used to log messages.
"//sdk/lib/syslog/cpp",
# This library is used to publish capabilities, e.g. protocols,
# to the component's outgoing directory.
"//sdk/lib/component/outgoing/cpp",
# This library provides an the asynchronous event loop implementation.
"//sdk/lib/async-loop:async-loop-cpp",
]
}
在 examples/fidl/cpp/server/main.cc 的顶部导入库:
#include <fidl/fuchsia.examples/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
提供组件的传出目录:
int main(int argc, const char** argv) {
// The event loop is used to asynchronously listen for incoming connections
// and requests from the client. The following initializes the loop, and
// obtains the dispatcher, which will be used when binding the server
// implementation to a channel.
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
fuchsia_logging::LogSettingsBuilder builder;
builder.WithDispatcher(dispatcher).BuildAndInitialize();
// Create an |OutgoingDirectory| instance.
//
// The |component::OutgoingDirectory| class serves the outgoing directory for
// our component. This directory is where the outgoing FIDL protocols are
// installed so that they can be provided to other components.
component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);
// The `ServeFromStartupInfo()` function sets up the outgoing directory with
// the startup handle. The startup handle is a handle provided to every
// component by the system, so that they can serve capabilities (e.g. FIDL
// protocols) to other components.
zx::result result = outgoing.ServeFromStartupInfo();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
return -1;
}
// ...
提供协议
然后,服务器使用 outgoing.AddProtocol 注册 Echo 协议。
int main(int argc, const char** argv) {
// The event loop is used to asynchronously listen for incoming connections
// and requests from the client. The following initializes the loop, and
// obtains the dispatcher, which will be used when binding the server
// implementation to a channel.
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
fuchsia_logging::LogSettingsBuilder builder;
builder.WithDispatcher(dispatcher).BuildAndInitialize();
// Create an |OutgoingDirectory| instance.
//
// The |component::OutgoingDirectory| class serves the outgoing directory for
// our component. This directory is where the outgoing FIDL protocols are
// installed so that they can be provided to other components.
component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);
// The `ServeFromStartupInfo()` function sets up the outgoing directory with
// the startup handle. The startup handle is a handle provided to every
// component by the system, so that they can serve capabilities (e.g. FIDL
// protocols) to other components.
zx::result result = outgoing.ServeFromStartupInfo();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
return -1;
}
// Register a handler for components trying to connect to fuchsia.examples.Echo.
result = outgoing.AddUnmanagedProtocol<fuchsia_examples::Echo>(
[dispatcher](fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
FX_LOGS(INFO) << "Incoming connection for "
<< fidl::DiscoverableProtocolName<fuchsia_examples::Echo>;
EchoImpl::BindSelfManagedServer(dispatcher, std::move(server_end));
});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to add Echo protocol: " << result.status_string();
return -1;
}
FX_LOGS(INFO) << "Running C++ echo server with natural types";
// This runs the event loop and blocks until the loop is quit or shutdown.
// See documentation comments on |async::Loop|.
loop.Run();
return 0;
}
对 AddProtocol 的调用会在 FIDL 协议名称 (fidl::DiscoverableProtocolName<fuchsia_examples::Echo>,即字符串 "fuchsia.examples.Echo") 处安装一个处理程序。当客户端组件连接到 fuchsia.examples.Echo 时,outgoing 将调用我们创建的 lambda 函数,该函数具有与客户端端点对应的服务器端点,并且此 lambda 函数将调用上面详细介绍的 EchoImpl::BindSelfManagedServer,以将服务器端点绑定到 EchoImpl 的新实例。
我们的主方法将继续在 async 循环中监听传入的请求。
测试服务器
构建服务器后,您可以通过以下命令在运行中的 Fuchsia 模拟器实例上运行该示例
ffx component run /core/ffx-laboratory:echo-server fuchsia-pkg://fuchsia.com/echo-cpp-server#meta/echo_server.cm您应该会在设备日志 (ffx log) 中看到类似如下所示的输出:
[ffx-laboratory:echo_server][][I] Running C++ echo server with natural types
服务器现已运行,并等待传入请求。
下一步是编写一个发送 Echo 协议请求的客户端。目前,您只需终止服务器组件即可:
ffx component destroy /core/ffx-laboratory:echo_server使用线网域对象处理请求
上述教程实现了一个具有自然网域对象的服务器:该服务器接收以自然网域对象表示的请求,并发送从自然网域对象编码的回复。在针对性能和堆分配量进行优化时,可以实现一个可处理序列化网域对象(即序列化服务器)的服务器。以下是重写后的 EchoImpl,它使用了 wire 网域对象:
class EchoImpl final : public fidl::WireServer<fuchsia_examples::Echo> {
public:
// The handler for `fuchsia.examples/Echo.EchoString`.
//
// For two-way methods (those with a response) like this one, the completer is
// used complete the call: either to send the reply via |completer.Reply|, or
// close the channel via |completer.Close|.
//
// |EchoStringRequestView| exposes the same API as a pointer to the request
// struct domain object, that is
// |fuchsia_examples::wire::EchoEchoStringRequest*|.
void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
// Call |Reply| to reply synchronously with the request value.
completer.Reply(request->value);
}
// The handler for `fuchsia.examples/Echo.SendString`.
//
// For fire-and-forget methods like this one, the completer is normally not
// used, but its |Close(zx_status_t)| method can be used to close the channel,
// either when the protocol has reached its intended terminal state or the
// server encountered an unrecoverable error.
//
// |SendStringRequestView| exposes the same API as a pointer to the request
// struct domain object, that is
// |fuchsia_examples::wire::EchoSendStringRequest*|.
void SendString(SendStringRequestView request, SendStringCompleter::Sync& completer) override {
// Handle a SendString request by sending an |OnString| event (an
// unsolicited server-to-client message) back to the client.
fidl::Status status = fidl::WireSendEvent(binding_)->OnString(request->value);
if (!status.ok()) {
FX_LOGS(ERROR) << "Error sending event: " << status.error();
}
}
// ... |BindSelfManagedServer| etc omitted. Those stay the same.
};
有线服务器中使用的相关类和函数与自然服务器中使用的类和函数类似。当调用其他类或函数时,相应导线通常以 Wire 为前缀。指针与引用以及实参结构也存在细微差异:
自然服务器实现的服务器接口为
fidl::Server<fuchsia_examples::Echo>。有线服务器实现的服务器接口为fidl::WireServer<fuchsia_examples::Echo>。自然服务器中的处理程序函数会获取对请求消息的引用。
Reply方法接受一个实参,即响应载荷网域对象:void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override { // Call |Reply| to reply synchronously with the request value. completer.Reply({{.response = request.value()}}); }而有线服务器中的处理程序函数则会获取请求消息的视图(类似于指针)。如果响应载荷是结构体,
Reply方法会将响应载荷中的结构体字段列表扁平化为单独的实参(此处为单个fidl::StringView实参):void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override { // Call |Reply| to reply synchronously with the request value. completer.Reply(request->value); }用于发送具有自然类型的事件的函数是
fidl::SendEvent。用于发送具有线类型的事件的函数是fidl::WireSendEvent。发送事件时,结构体字段也会被展平为单独的参数。
可以使用相同的 fidl::BindServer 函数来绑定自然服务器或有线服务器。
有线服务器的完整示例代码位于 Fuchsia 代码库的 //examples/fidl/cpp/server/wire 中。