必要條件
本教學課程是以網域物件教學課程為基礎。對於 完整的 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
。由伺服器設定
及其包含套件如要進一步瞭解如何建構
請參閱「建構元件」一文。
為了讓伺服器元件正常執行,在 Pod 中
定義於 //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. "//zircon/system/ulib/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
。接著, 資訊清單必須與在executable
BUILD.gn
。{ 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" ] }
建構伺服器
您可以透過下列程式碼建構伺服器套件:
將伺服器新增至建構設定。這項操作只需執行一次:
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
比其他元件更為穩定方法是發布通訊協定
新增至元件的
傳出目錄
,直接在 Google Cloud 控制台實際操作。完整流程
進一步瞭解開放通訊協定的生命週期。
我們可以從 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.
"//zircon/system/ulib/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 函式會呼叫 EchoImpl::BindSelfManagedServer
詳細步驟,以將伺服器端點繫結至 EchoImpl
的新執行個體。
我們的主要方法是繼續監聽 非同步迴圈。
測試伺服器
建構伺服器之後,您可以在 您可以透過 Cloud Shell 的
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
,以便使用線路網域物件:
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.
};
電線伺服器中使用的相關類別和函式,其形狀與
用於自然伺服器中的 Pod呼叫其他類別或函式時
電線對應的代碼通常開頭是 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
。