必要條件
本教學課程以 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_;
};
由於用戶端只使用 EchoString
,因此 SendString
處理常式為空白。
實作 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 建構作業,以便納入伺服器:
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 建構作業,以便納入伺服器:
fx set core.x64 --with //examples/fidl/cpp/request_pipelining/client:echo-client
建構 Fuchsia 映像檔:
fx build
執行程式碼範例
fuchsia.examples.Echo
fuchsia.examples.EchoLauncher
設定建構作業,加入提供的套件,其中包含回音領域、伺服器和用戶端:
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!
根據列印順序,可看到管道案件的處理速度較快。即使先傳送非管道的要求,管道案件的 echo 回應也會先送達,因為要求管道會節省用戶端與伺服器之間的來回傳送。要求管道也會簡化程式碼。
如要進一步瞭解通訊協定要求管道,包括如何處理可能失敗的通訊協定要求,請參閱 FIDL API 評分量表。
終止領域元件以停止執行並清除元件執行個體:
ffx component destroy /core/ffx-laboratory:echo_realm