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:

@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;
    });
};

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:

    executable("bin") {
      output_name = "fidl_echo_cpp_server"
      sources = [ "main.cc" ]
      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",
    
        # This library is used to log messages.
        "//sdk/lib/syslog/cpp",
    
        # This library is used to publish capabilities, e.g. protocols,
        # to the component's outgoing directory.
        "//sdk/lib/component/outgoing/cpp",
    
        # This library provides an the asynchronous event loop implementation.
        "//zircon/system/ulib/async-loop:async-loop-cpp",
      ]
    }
    
    
  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.

    fuchsia_component("echo-server") {
      component_name = "echo_server"
      manifest = "meta/server.cml"
      deps = [ ":bin" ]
    }
    
    

    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.

    {
        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_cpp_server",
        },
    
        // Capabilities provided by this component.
        capabilities: [
            { protocol: "fuchsia.examples.Echo" },
        ],
        expose: [
            {
                protocol: "fuchsia.examples.Echo",
                from: "self",
            },
        ],
    }
    
    
  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.

    fuchsia_package("echo-cpp-server") {
      package_name = "echo-cpp-server"
      deps = [ ":echo-server" ]
    }
    
    

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.qemu-x64 --with //examples/fidl/cpp/server
    
  2. Build the server package:

    fx build examples/fidl/cpp/server
    

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:
  // The handler for `fuchsia.examples/Echo.EchoString`.
  //
  // For two-way methods (those with a response) like this one, the completer is
  // used complete the call: either to send the reply via |completer.Reply|, or
  // close the channel via |completer.Close|.
  //
  // |EchoStringRequest| exposes the same API as the request struct domain
  // object, that is |fuchsia_examples::EchoEchoStringRequest|.
  void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
    // Call |Reply| to reply synchronously with the request value.
    completer.Reply({{.response = request.value()}});
  }

  // The handler for `fuchsia.examples/Echo.SendString`.
  //
  // For fire-and-forget methods like this one, the completer is normally not
  // used, but its |Close(zx_status_t)| method can be used to close the channel,
  // either when the protocol has reached its intended terminal state or the
  // server encountered an unrecoverable error.
  //
  // |SendStringRequest| exposes the same API as the request struct domain
  // object, that is |fuchsia_examples::EchoSendStringRequest|.
  void SendString(SendStringRequest& request, SendStringCompleter::Sync& completer) override {
    ZX_ASSERT(binding_ref_.has_value());

    // Handle a SendString request by sending an |OnString| event (an
    // unsolicited server-to-client message) back to the client.
    fit::result result = fidl::SendEvent(*binding_ref_)->OnString({request.value()});
    if (!result.is_ok()) {
      FX_LOGS(ERROR) << "Error sending event: " << result.error_value();
    }
  }

  // ... other methods from examples/fidl/cpp/server/main.cc omitted, to be covered later.

 private:
  // `ServerBindingRef` can be used to:
  // - Control the binding, such as to unbind the server from the channel or
  //   close the channel.
  // - Send events back to the client.
  // See the documentation comments on |fidl::ServerBindingRef|.
  std::optional<fidl::ServerBindingRef<fuchsia_examples::Echo>> binding_ref_;
};

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 {`... */

  // Bind a new implementation of |EchoImpl| to handle requests coming from
  // the server endpoint |server_end|.
  static void BindSelfManagedServer(async_dispatcher_t* dispatcher,
                                    fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
    // Create a new instance of |EchoImpl|.
    std::unique_ptr impl = std::make_unique<EchoImpl>();
    EchoImpl* impl_ptr = impl.get();

    // |fidl::BindServer| takes a FIDL protocol server implementation and a
    // channel. It asynchronously reads requests off the channel, decodes them
    // and dispatches them to the correct handler on the server implementation.
    //
    // The FIDL protocol server implementation can be passed as a
    // |std::shared_ptr|, |std::unique_ptr|, or raw pointer. For shared and
    // unique pointers, the binding will manage the lifetime of the
    // implementation object. For raw pointers, it's up to the caller to ensure
    // that the implementation object outlives the binding but does not leak.
    //
    // See the documentation comment of |fidl::BindServer|.
    fidl::ServerBindingRef binding_ref = fidl::BindServer(
        dispatcher, std::move(server_end), std::move(impl), std::mem_fn(&EchoImpl::OnUnbound));
    // Put the returned |binding_ref| into the |EchoImpl| object.
    impl_ptr->binding_ref_.emplace(std::move(binding_ref));
  }

  // This method is passed to the |BindServer| call as the last argument,
  // which means it will be called when the connection is torn down.
  // In this example we use it to log some connection lifecycle information.
  // Production code could do more things such as resource cleanup.
  void OnUnbound(fidl::UnbindInfo info, fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
    // |is_user_initiated| returns true if the server code called |Close| on a
    // completer, or |Unbind| / |Close| on the |binding_ref_|, to proactively
    // teardown the connection. These cases are usually part of normal server
    // shutdown, so logging is unnecessary.
    if (info.is_user_initiated()) {
      return;
    }
    if (info.is_peer_closed()) {
      // If the peer (the client) closed their endpoint, log that as INFO.
      FX_LOGS(INFO) << "Client disconnected";
    } else {
      // Treat other unbind causes as errors.
      FX_LOGS(ERROR) << "Server error: " << info;
    }
  }

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:

executable("bin") {
  output_name = "fidl_echo_cpp_server"
  sources = [ "main.cc" ]
  deps = [
    "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",

    # This library is used to log messages.
    "//sdk/lib/syslog/cpp",

    # This library is used to publish capabilities, e.g. protocols,
    # to the component's outgoing directory.
    "//sdk/lib/component/outgoing/cpp",

    # This library provides an the asynchronous event loop implementation.
    "//zircon/system/ulib/async-loop:async-loop-cpp",
  ]
}

Import the library at the top of examples/fidl/cpp/server/main.cc:

#include <fidl/fuchsia.examples/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/syslog/cpp/macros.h>

Serve the component's outgoing directory:

int main(int argc, const char** argv) {
  // The event loop is used to asynchronously listen for incoming connections
  // and requests from the client. The following initializes the loop, and
  // obtains the dispatcher, which will be used when binding the server
  // implementation to a channel.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

  // Create an |OutgoingDirectory| instance.
  //
  // The |component::OutgoingDirectory| class serves the outgoing directory for
  // our component. This directory is where the outgoing FIDL protocols are
  // installed so that they can be provided to other components.
  component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);

  // The `ServeFromStartupInfo()` function sets up the outgoing directory with
  // the startup handle. The startup handle is a handle provided to every
  // component by the system, so that they can serve capabilities (e.g. FIDL
  // protocols) to other components.
  zx::result result = outgoing.ServeFromStartupInfo();
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
    return -1;
  }

  // ...

Serve the protocol

The server then registers the Echo protocol using outgoing.AddProtocol.

int main(int argc, const char** argv) {
  // The event loop is used to asynchronously listen for incoming connections
  // and requests from the client. The following initializes the loop, and
  // obtains the dispatcher, which will be used when binding the server
  // implementation to a channel.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

  // Create an |OutgoingDirectory| instance.
  //
  // The |component::OutgoingDirectory| class serves the outgoing directory for
  // our component. This directory is where the outgoing FIDL protocols are
  // installed so that they can be provided to other components.
  component::OutgoingDirectory outgoing = component::OutgoingDirectory(dispatcher);

  // The `ServeFromStartupInfo()` function sets up the outgoing directory with
  // the startup handle. The startup handle is a handle provided to every
  // component by the system, so that they can serve capabilities (e.g. FIDL
  // protocols) to other components.
  zx::result result = outgoing.ServeFromStartupInfo();
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
    return -1;
  }

  // Register a handler for components trying to connect to fuchsia.examples.Echo.
  result = outgoing.AddUnmanagedProtocol<fuchsia_examples::Echo>(
      [dispatcher](fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
        FX_LOGS(INFO) << "Incoming connection for "
                      << fidl::DiscoverableProtocolName<fuchsia_examples::Echo>;
        EchoImpl::BindSelfManagedServer(dispatcher, std::move(server_end));
      });
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to add Echo protocol: " << result.status_string();
    return -1;
  }

  FX_LOGS(INFO) << "Running C++ echo server with natural types";

  // This runs the event loop and blocks until the loop is quit or shutdown.
  // See documentation comments on |async::Loop|.
  loop.Run();
  return 0;
}

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

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

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:
  // The handler for `fuchsia.examples/Echo.EchoString`.
  //
  // For two-way methods (those with a response) like this one, the completer is
  // used complete the call: either to send the reply via |completer.Reply|, or
  // close the channel via |completer.Close|.
  //
  // |EchoStringRequestView| exposes the same API as a pointer to the request
  // struct domain object, that is
  // |fuchsia_examples::wire::EchoEchoStringRequest*|.
  void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
    // Call |Reply| to reply synchronously with the request value.
    completer.Reply(request->value);
  }

  // The handler for `fuchsia.examples/Echo.SendString`.
  //
  // For fire-and-forget methods like this one, the completer is normally not
  // used, but its |Close(zx_status_t)| method can be used to close the channel,
  // either when the protocol has reached its intended terminal state or the
  // server encountered an unrecoverable error.
  //
  // |SendStringRequestView| exposes the same API as a pointer to the request
  // struct domain object, that is
  // |fuchsia_examples::wire::EchoSendStringRequest*|.
  void SendString(SendStringRequestView request, SendStringCompleter::Sync& completer) override {
    // Handle a SendString request by sending an |OnString| event (an
    // unsolicited server-to-client message) back to the client.
    fidl::Status status = fidl::WireSendEvent(binding_)->OnString(request->value);
    if (!status.ok()) {
      FX_LOGS(ERROR) << "Error sending event: " << status.error();
    }
  }

  // ... |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:

    void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
      // Call |Reply| to reply synchronously with the request value.
      completer.Reply({{.response = request.value()}});
    }
    

    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):

    void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
      // Call |Reply| to reply synchronously with the request value.
      completer.Reply(request->value);
    }
    
  • 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.