Implement a C++ FIDL server

Prerequisites

This tutorial builds on the domain objects tutorial. For the full set of FIDL tutorials, refer to the overview.

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:

Structure of the server example

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:

  1. 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" %}
    
  2. 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" %}
    
  3. 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" %}
    

Building the server

You may build the server package via the following:

  1. Add the server to your build configuration. This only needs to be done once:

    fx set core.x64 --with //examples/fidl/cpp/server
    
  2. 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.

Implement the FIDL protocol

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.

Bind the implementation to a server endpoint

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" %}

Publish the protocol implementation

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" %}

  // ...

Serve the protocol

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.

Test the server

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

Serve requests using wire domain objects

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.