必要條件
本教學課程是以編譯 FIDL 教學課程為基礎。如需完整的 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 通訊協定。
- 在 Fuchsia 上建構並執行套件。
- 提供 FIDL 通訊協定。
本教學課程首先請建立要提供給 Fuchsia 裝置的元件,並立即執行。然後逐步新增功能,讓伺服器啟動並執行。
如要自行編寫程式碼,請刪除下列目錄:
rm -r examples/fidl/hlcpp/server/*
建立元件
如何建立元件:
在
examples/fidl/hlcpp/server/main.cc
中新增main()
函式:#include <stdio.h> int main(int argc, const char** argv) { printf("Hello, world!\n"); return 0; }
在
examples/fidl/hlcpp/server/BUILD.gn
中宣告伺服器的目標:import("//build/components.gni") # Declare an executable for the server. This produces a binary with the # specified output name that can run on Fuchsia. executable("bin") { output_name = "fidl_echo_hlcpp_server" sources = [ "main.cc" ] } # Declare a component for the server, which consists of the manifest and the # binary that the component will run. fuchsia_component("echo-server") { component_name = "echo_server" manifest = "meta/server.cml" deps = [ ":bin" ] } # Declare a package that contains a single component, our server. fuchsia_package("echo-hlcpp-server") { package_name = "echo-hlcpp-server" deps = [ ":echo-server" ] }
為了啟動並執行伺服器元件,定義了三個目標:
- 針對在 Fuchsia 上執行的伺服器的原始可執行檔。
- 設定為僅執行伺服器執行檔的元件,使用元件的資訊清單檔案說明。
- 元件隨後會放入套件中,也就是 Fuchsia 上的軟體發布單位。在此情況下,套件只包含單一元件。
如要進一步瞭解套件、元件和建構方式,請參閱建構元件頁面。
在
examples/fidl/hlcpp/server/meta/server.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_server", }, // Capabilities provided by this component. capabilities: [ { protocol: "fuchsia.examples.Echo" }, ], expose: [ { protocol: "fuchsia.examples.Echo", from: "self", }, ], }
將伺服器新增到建構設定:
fx set core.x64 --with //examples/fidl/hlcpp/server:echo-hlcpp-server
建構 Fuchsia 映像檔:
fx build
實作伺服器
新增 FIDL 程式庫的依附元件
將
fuchsia.examples
FIDL 程式庫目標新增為examples/fidl/hlcpp/server/BUILD.gn
中executable
的依附元件:executable("bin") { output_name = "fidl_echo_hlcpp_server" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples" ] }
匯入
examples/fidl/hlcpp/server/main.cc
頂端的 HLCPP 繫結:#include <fuchsia/examples/cpp/fidl.h>
新增通訊協定的實作方式
請將以下內容新增至 main()
函式上方的 main.cc
:
class EchoImpl : public fuchsia::examples::Echo {
public:
void EchoString(std::string value, EchoStringCallback callback) override { callback(value); }
void SendString(std::string value) override {
if (event_sender_) {
event_sender_->OnString(value);
}
}
fuchsia::examples::Echo_EventSender* event_sender_;
};
實作項目包含下列元素:
- 該類別會將產生的通訊協定類別設為子類別,並覆寫其對應至通訊協定方法的純虛擬方法。
EchoString
的方法會呼叫其回呼,以回應要求值。SendString
的方法不會接收回呼,因為這個方法沒有回應。實作方式改為使用Echo_EventSender
傳送OnString
事件。- 類別包含指向
Echo_EventSender
的指標。這項設定會在稍後的main()
函式中設定。
您可以執行下列指令來驗證實作是否正確:
fx build
提供通訊協定
如要執行實作 FIDL 通訊協定的元件,請向元件管理員發出要求,向其他元件公開該 FIDL 通訊協定。接著,元件管理員會將 echo 通訊協定的所有要求轉送至我們的伺服器。
為執行這些要求,元件管理員需要該通訊協定的名稱,以及任何傳入要求,連線至符合指定名稱的通訊協定時應呼叫的處理常式。
傳遞至它的處理常式是一個函式,該函式會擷取管道 (遠端端由用戶端擁有),並將其繫結至已使用伺服器實作初始化的 fidl::Binding
。fidl::Binding
是 FIDL 執行階段中的類別,會取得 FIDL 通訊協定實作和管道,然後在管道上監聽傳入的要求。接著,系統會解碼要求,將要求分派到伺服器類別的正確方法,然後將任何回應寫回用戶端。我們的主要方法會持續監聽非同步迴圈中的傳入要求。
如要進一步瞭解完整程序,請參閱「通訊協定開啟流程」一文。
新增依附元件
這個新程式碼需要下列其他依附元件:
"//zircon/system/ulib/async-loop:async-loop-cpp"
和"//zircon/system/ulib/async-loop:async-loop-default"
:這些程式庫包含非同步迴圈程式碼。"//sdk/lib/sys/cpp"
:元件架構 C++ 執行階段,包含與元件環境互動的公用程式程式碼。
將程式庫目標新增為
examples/fidl/hlcpp/server/BUILD.gn
中executable
的依附元件:executable("bin") { output_name = "fidl_echo_hlcpp_server" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_hlcpp", "//sdk/lib/sys/cpp", "//zircon/system/ulib/async-loop:async-loop-cpp", "//zircon/system/ulib/async-loop:async-loop-default", ] }
在
examples/fidl/hlcpp/server/main.cc
頂端匯入以下依附元件:#include <lib/async-loop/cpp/loop.h> #include <lib/async-loop/default.h> #include <lib/fidl/cpp/binding.h> #include <lib/sys/cpp/component_context.h> #include <lib/sys/cpp/service_directory.h>
初始化事件迴圈
第一個方面是使用非同步迴圈:
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
EchoImpl impl;
fidl::Binding<fuchsia::examples::Echo> binding(&impl);
impl.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fuchsia::examples::Echo> handler =
[&](fidl::InterfaceRequest<fuchsia::examples::Echo> request) {
binding.Bind(std::move(request));
};
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
context->outgoing()->AddPublicService(std::move(handler));
printf("Running echo server\n");
return loop.Run();
}
程式碼會先初始化迴圈,並將其註冊為目前執行緒的預設調度工具。首先,由於 main()
函式中的非同步程式碼會自行註冊預設調度工具,這個調度工具是靜態執行緒本機變數,因此不需要在程式碼的其餘部分明確傳遞。在主函式結束時,程式碼會執行非同步迴圈。
初始化繫結
接著,程式碼會初始化 fidl::Binding
,如上所述:
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
EchoImpl impl;
fidl::Binding<fuchsia::examples::Echo> binding(&impl);
impl.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fuchsia::examples::Echo> handler =
[&](fidl::InterfaceRequest<fuchsia::examples::Echo> request) {
binding.Bind(std::move(request));
};
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
context->outgoing()->AddPublicService(std::move(handler));
printf("Running echo server\n");
return loop.Run();
}
如要執行,繫結需要符合兩項條件:
- 通訊協定的實作。
- 繫結將監聽該通訊協定已啟用訊息的管道。繫結會先使用 echo 實作方式初始化,並稍後繫結至管道。
程式碼也會設定用來將事件傳送至用戶端的事件傳送者。系統會對 Binding
使用 events()
方法取得事件傳送者,然後傳遞至 EchoImpl
類別。
定義通訊協定要求處理常式
接著,程式碼會定義來自用戶端傳入要求的處理常式:
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
EchoImpl impl;
fidl::Binding<fuchsia::examples::Echo> binding(&impl);
impl.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fuchsia::examples::Echo> handler =
[&](fidl::InterfaceRequest<fuchsia::examples::Echo> request) {
binding.Bind(std::move(request));
};
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
context->outgoing()->AddPublicService(std::move(handler));
printf("Running echo server\n");
return loop.Run();
}
- 「傳入要求」不是
Echo
通訊協定的特定方法要求,而是來自用戶端的一般要求,用於連線至Echo
通訊協定的實作。 - 要求定義為
fidl::InterfaceRequest<Echo>
。這是一種類型安全包裝函式,位於管道周圍,代表有兩個項目:InterfaceRequest
表示這是管道的伺服器端 (即用戶端已連結至頻道的遠端端)- 範本參數
Echo
表示用戶端預期實作Echo
通訊協定的伺服器會繫結至這個管道。它的用戶端類比 (即在用戶端用來代表此管道另一端的類型) 為fidl::InterfaceHandle<Echo>
。
- 處理常式只需接收從用戶端傳送的管道,並將其繫結至
Echo
繫結即可。 - 發生這種情況時,
Binding
會根據Echo
通訊協定開始在管道上處理訊息。這是通訊協定要求管道的範例,請參閱後續的教學課程。
註冊通訊協定要求處理常式
最後,程式碼會使用元件管理員註冊處理常式:
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
EchoImpl impl;
fidl::Binding<fuchsia::examples::Echo> binding(&impl);
impl.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fuchsia::examples::Echo> handler =
[&](fidl::InterfaceRequest<fuchsia::examples::Echo> request) {
binding.Bind(std::move(request));
};
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
context->outgoing()->AddPublicService(std::move(handler));
printf("Running echo server\n");
return loop.Run();
}
第一行會初始化並提供傳出目錄,其中包含此元件向其他元件公開的通訊協定,第二行則會將處理常式新增至外送目錄。
處理常式以外的隱含第二個參數是這個處理常式應註冊的名稱。根據預設,這個參數是傳入的通訊協定名稱,之所以產生,是因為 Echo
通訊協定上有 [Discoverable]
屬性。也就是說,執行此行後,您應該就能在元件的 /out
目錄中呼叫 ls
,並查看名為 fuchsia.examples.Echo
的項目。
測試伺服器
重新建構:
fx build
然後執行伺服器元件:
ffx component run /core/ffx-laboratory:echo_server fuchsia-pkg://fuchsia.com/echo-hlcpp-server#meta/echo_server.cm
您應該會在裝置記錄 (ffx log
) 中看到類似以下的輸出內容:
[ffx-laboratory:echo_server][][I] Running echo server
伺服器現已執行並等候傳入要求。下一步是編寫傳送 Echo
通訊協定要求的用戶端。目前,您可以直接終止伺服器元件:
ffx component destroy /core/ffx-laboratory:echo_server