This tutorial builds on the domain objects tutorial. For the full set of FIDL tutorials, refer to the overview.
This tutorial shows you how to implement a server for a FIDL protocol (fuchsia.examples/Echo
) and run it on Fuchsia. This protocol has one method of each kind: a one-way method, a two-way method, and an event:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/echo.test.fidl" region_tag="echo" %}
For more on FIDL methods and messaging models, refer to the FIDL concepts page.
This document covers how to complete the following tasks:
The example code accompanying this tutorial is located in your Fuchsia checkout at //examples/fidl/cpp/server
. It consists of a server component and its containing package. For more information about building components, see Build components.
To get the server component up and running, there are three targets that are defined in //examples/fidl/cpp/server/BUILD.gn
:
The raw executable file for the server. This produces a binary with the specified output name that can run on Fuchsia:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/BUILD.gn" region_tag="bin" %}
A component that is set up to run the server executable. Components are the units of software execution on Fuchsia. A component is described by its manifest file. In this case meta/server.cml
configures echo-server
as an executable component which runs fidl_echo_cpp_server
in :bin
.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/BUILD.gn" region_tag="component" %}
The server component manifest is located at //examples/fidl/cpp/server/meta/server.cml
. The binary name in the manifest must match the output name of the executable
defined in BUILD.gn
.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/meta/server.cml" region_tag="example_snippet" %}
The component is then put into a package, which is the unit of software distribution on Fuchsia. In this case, the package just contains a single component.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/BUILD.gn" region_tag="package" %}
You may build the server package via the following:
Add the server to your build configuration. This only needs to be done once:
fx set core.x64 --with //examples/fidl/cpp/server
Build the server package:
fx build examples/fidl/cpp/server
Note: This build configuration assumes your device target is the emulator. To run the example on a physical device, select the appropriate product configuration for your hardware.
EchoImpl
implements the server request handler for the fuchsia.examples/Echo
protocol. To do so, EchoImpl
inherits from the generated pure virtual server interface fidl::Server<fuchsia_examples::Echo>
, and overrides its pure virtual methods corresponding to every one way and two way call:
class EchoImpl : public fidl::Server<fuchsia_examples::Echo> { public: {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="impl" %} // ... other methods from examples/fidl/cpp/server/main.cc omitted, to be covered later. {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="binding_ref" %} };
Note: in this tutorial the EchoString
handler replies synchronously. For asynchronous replies, see responding to requests asynchronously.
Implementing the request handlers is only half the story. The server needs to be able to monitor new messages that arrives on a server endpoint. To do this, EchoImpl
defines two more methods: a BindSelfManagedServer
static factory function that creates a new EchoImpl
instance to handle requests on a new server endpoint fidl::ServerEnd<fuchsia_examples::Echo>
, and an OnUnbound
method that is called when the connection is torn down:
/* Inside `class EchoImpl {`... */ {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="bind_server" %}
A component that implements a FIDL protocol can expose that FIDL protocol to other components. This is done by publishing the protocol implementation to the component's outgoing directory. This complete process is described in further detail in the Life of a protocol open. We can use component::OutgoingDirectory
from the C++ component runtime library to perform the heavy lifting.
To depend on the component runtime library:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/BUILD.gn" region_tag="bin" highlight="12" %}
Import the library at the top of examples/fidl/cpp/server/main.cc
:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="includes" highlight="3" %}
Serve the component's outgoing directory:
int main(int argc, const char** argv) { {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="serve-out-dir" %} // ...
The server then registers the Echo protocol using outgoing.AddProtocol
.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="main" highlight="27,28,29,30,31,32,33,34,35,36" %}
The call to AddProtocol
installs a handler at the name of the FIDL protocol (fidl::DiscoverableProtocolName<fuchsia_examples::Echo>
, which is the string "fuchsia.examples.Echo"
). When a client component connects to fuchsia.examples.Echo
, outgoing
will call the lambda function that we created with a server endpoint corresponding to the client endpoint from the client, and this lambda function will call the EchoImpl::BindSelfManagedServer
detailed above to bind the server endpoint to a new instance of EchoImpl
.
Our main method will keep listening for incoming requests on the async loop.
After building the server, you may run the example on a running instance of Fuchsia emulator via
ffx component run /core/ffx-laboratory:echo-server fuchsia-pkg://fuchsia.com/echo-cpp-server#meta/echo_server.cm
Note: Components are resolved using their component URL, which is determined with the fuchsia-pkg://
scheme.
You should see output similar to the following in the device logs (ffx log
):
[ffx-laboratory:echo_server][][I] Running C++ echo server with natural types
The server is now running and waiting for incoming requests. The next step will be to write a client that sends Echo
protocol requests. For now, you can simply terminate the server component:
ffx component destroy /core/ffx-laboratory:echo_server
Note: Component instances are referenced by their component moniker, which is determined by their location in the component instance tree
The above tutorial implements a server with natural domain objects: the server receives requests represented in natural domain objects, and sends replies encoded from natural domain objects. When optimizing for performance and heap allocation, one may implement a server that speaks wire domain objects, i.e. a wire server. Here is the EchoImpl
rewritten to use wire domain objects:
class EchoImpl final : public fidl::WireServer<fuchsia_examples::Echo> { public: {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/wire/main.cc" region_tag="handlers" %} // ... |BindSelfManagedServer| etc omitted. Those stay the same. };
The relevant classes and functions used in a wire server have similar shapes to those used in a natural server. When a different class or function is called for, the wire counterpart is usually prefixed with Wire
. There are also small differences in pointers vs references and argument structure:
The server interface implemented by a natural server is fidl::Server<fuchsia_examples::Echo>
. The server interface implemented by a wire server is fidl::WireServer<fuchsia_examples::Echo>
.
The handler function in a natural server takes a reference to the request message. The Reply
method takes a single argument that is the response payload domain object:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/main.cc" region_tag="impl-echo-string" adjust_indentation="auto" %}
Whereas the handler function in a wire server takes a view (akin to a pointer) of the request message. When the response payload is a struct, the Reply
method flattens the list of struct fields in the response payload into separate arguments (here, a single fidl::StringView
argument):
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/server/wire/main.cc" region_tag="impl-echo-string" adjust_indentation="auto" %}
The function to send events with natural types is fidl::SendEvent
. The function to send events with wire types is fidl::WireSendEvent
. Struct fields are also flattened into separate arguments when sending an event.
The same fidl::BindServer
function may be used to bind either a natural server or a wire server.
The full example code for a wire server is located in your Fuchsia checkout at //examples/fidl/cpp/server/wire
.