Protocol request pipelining in C++
Prerequisites
This tutorial builds on theC++ getting started tutorials.
Overview
A common aspect of using FIDL on Fuchsia is passing protocol endpoints acrossprotocols. Many FIDL messages include either the client end or the server end ofa channel, where the channel is used to communicate over a different FIDLprotocol. In this case, the client end allows making requests to the specifiedprotocol, whereas the server end must implement the specified protocol. Analternate set of terms for client end and server end are protocol and protocolrequest.
This tutorial covers:
- The usage of these client and server ends, both in FIDL and in the C++FIDL bindings.
- Theprotocol request pipelining pattern and its benefits.
The full example code for this tutorial is located at//examples/fidl/cpp/request_pipelining.
The FIDL protocol
This tutorial implements theEchoLauncher protocol from thefuchsia.examples library:
@discoverableclosedprotocolEchoLauncher{strictGetEcho(struct{echo_prefixstring:MAX_STRING_LENGTH;})->(resourcestruct{responseclient_end:Echo;});strictGetEchoPipelined(resourcestruct{echo_prefixstring:MAX_STRING_LENGTH;requestserver_end:Echo;});};This is a protocol that lets clients retrieve an instance of theEchoprotocol. Clients can specify a prefix, and the resultingEcho instanceadds that prefix to every response.
There are two methods that can be used to accomplish this:
GetEcho: Takes the prefix as a request, and responds with the client end ofa channel connected to an implementation of theEchoprotocol. Afterreceiving the client end in the response, the client can start making requestson theEchoprotocol using the client end.GetEchoPipelined: Takes the server end of a channel as one of the requestparameters and binds an implementation ofEchoto it. The client thatmade the request is assumed to already hold the client end, and willstart makingEchorequests on that channel after callingGetEchoPipelined.
As the name suggests, the latter uses a pattern called protocol requestpipelining, and is the preferred approach. This tutorial implements bothapproaches.
Implement the server
Implement the Echo protocol
This implementation ofEcho allows specifying a prefix in order todistinguish between the different instances ofEcho servers:
// Implementation of the Echo protocol that prepends a prefix to every response.classEchoImplfinal:publicfidl::Server<fuchsia_examples::Echo>{public:explicitEchoImpl(std::stringprefix):prefix_(prefix){}// This method is not used in the request pipelining example, so requests are ignored.voidSendString(SendStringRequest&request,SendStringCompleter::Sync&completer)override{}voidEchoString(EchoStringRequest&request,EchoStringCompleter::Sync&completer)override{FX_LOGS(INFO) <<"Got echo request for prefix " <<prefix_;completer.Reply(prefix_+request.value());}conststd::stringprefix_;};TheSendString handler is empty as the client just usesEchoString.
Implement the EchoLauncher protocol
// Implementation of EchoLauncher. Each method creates an instance of EchoImpl// with the specified prefix.classEchoLauncherImplfinal:publicfidl::Server<fuchsia_examples::EchoLauncher>{public:explicitEchoLauncherImpl(async_dispatcher_t*dispatcher):dispatcher_(dispatcher){}voidGetEcho(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));}voidGetEchoPipelined(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_;};ForGetEcho, the code first needs to instantiate both ends of the channel. Itthen launches anEcho instance using the server end, and then sends a responseback with the client end. ForGetEchoPipelined, the client has already donethe work of creating both ends of the channel. It keeps one end and has passedthe other to the server, so all the code needs to do is to bind the server endto a newEchoImpl.
Serve the EchoLauncher protocol
The main loop is the same as in theserver tutorial but serves anEchoLauncher instead ofEcho.
intmain(intargc,char**argv){async::Looploop(&kAsyncLoopConfigNeverAttachToThread);async_dispatcher_t*dispatcher=loop.dispatcher();component::OutgoingDirectoryoutgoing=component::OutgoingDirectory(dispatcher);zx::resultresult=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();return0;}Build the server
Optionally, to check that things are correct, try building the server:
Configure your GN build to include the server:
fxsetcore.x64--with//examples/fidl/cpp/request_pipelining/server:echo-serverBuild the Fuchsia image:
fxbuild
Implement the client
Note: Most of the client code inclient/main.cc should be familiar afterhaving followed theclient tutorial. The different parts of thecode are covered in more detail here.After connecting to theEchoLauncher server, the clientcode connects to one instance ofEcho usingGetEcho and another usingGetEchoPipelined and then makes anEchoString request on each instance.
Non-pipelined client
This is the non-pipelined code:
intmain(intargc,constchar**argv){async::Looploop(&kAsyncLoopConfigNeverAttachToThread);async_dispatcher_t*dispatcher=loop.dispatcher();intnum_responses=0;// Connect to the EchoLauncher protocolzx::resultlauncher_client_end=component::Connect<fuchsia_examples::EchoLauncher>();ZX_ASSERT(launcher_client_end.is_ok());fidl::Clientlauncher(std::move(*launcher_client_end),dispatcher);// Make a non-pipelined request to get an instance of Echolauncher->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::SharedClientecho(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 EchoZX_ASSERT(launcher->GetEchoPipelined({"pipelined: ",std::move(server_end)}).is_ok());// A client can be initialized using the client end without waiting for a responsefidl::Clientecho_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();returnnum_responses==2?0:1;}This code has two layers of callbacks:
- The outer layer handles the launcher
GetEchoresponse. - The inner layer handles the
EchoStringresponse.
Inside theGetEcho response callback, the code binds the returned client endto afidl::SharedClient<Echo>, and places a clone into theEchoStringcallback, so that the client's lifetime is extended until when the echo responseis received, which will most likely be after the top level callback returns.
Pipelined client
Despite having to create a pair of endpoints first, the pipelined code is muchsimpler:
intmain(intargc,constchar**argv){async::Looploop(&kAsyncLoopConfigNeverAttachToThread);async_dispatcher_t*dispatcher=loop.dispatcher();intnum_responses=0;// Connect to the EchoLauncher protocolzx::resultlauncher_client_end=component::Connect<fuchsia_examples::EchoLauncher>();ZX_ASSERT(launcher_client_end.is_ok());fidl::Clientlauncher(std::move(*launcher_client_end),dispatcher);// Make a non-pipelined request to get an instance of Echolauncher->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::SharedClientecho(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 EchoZX_ASSERT(launcher->GetEchoPipelined({"pipelined: ",std::move(server_end)}).is_ok());// A client can be initialized using the client end without waiting for a responsefidl::Clientecho_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();returnnum_responses==2?0:1;}Unlike in theclient tutorial, the async loop is run to completiononce, which runs both non-pipelined and pipelined code concurrently in order toobserve the order of operations. The client keeps track of the number ofresponses being received, so that it can quit the loop once no more messagesfrom the server are expected.
Build the client
Optionally, to check that things are correct, try building the client:
Configure your GN build to include the server:
fxsetcore.x64--with//examples/fidl/cpp/request_pipelining/client:echo-clientBuild the Fuchsia image:
fxbuild
Run the example code
For this tutorial, arealm component isprovided to declare the appropriate capabilities and routes forfuchsia.examples.Echo andfuchsia.examples.EchoLauncher.
//examples/fidl/echo-realmConfigure your build to include the provided package that includes theecho realm, server, and client:
fxsetcore.x64--with//examples/fidl/cpp/request_pipeliningBuild the Fuchsia image:
fxbuildRun the
echo_realmcomponent. This creates the client and server componentinstances and routes the capabilities:ffxcomponentrun/core/ffx-laboratory:echo_realmfuchsia-pkg://fuchsia.com/echo-launcher-cpp#meta/echo_realm.cmStart the
echo_clientinstance:ffxcomponentstart/core/ffx-laboratory:echo_realm/echo_client
The server component starts when the client attempts to connect to theEchoLauncher protocol. You should see output similar to the followingin the device logs (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!Based on the print order, you can see that the pipelined case is faster. Theecho response for the pipelined case arrives first, even though the nonpipelined request is sent first, since request pipelining saves a roundtripbetween the client and server. Request pipelining also simplifies the code.
For further reading about protocol request pipelining, including how to handleprotocol requests that may fail, see theFIDL API rubric.
Terminate the realm component to stop execution and clean up the componentinstances:
ffxcomponentdestroy/core/ffx-laboratory:echo_realmExcept as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-03-22 UTC.