Prerequisites
This tutorial builds on the Compiling FIDL tutorial. For the full set of FIDL tutorials, refer to the overview.
Overview
This tutorial shows you how to implement a FIDL protocol
(fuchsia.examples.Echo
) and run it on Fuchsia. This protocol has one method
of each kind: a fire and forget method, a two-way method, and an event:
@discoverable
protocol Echo {
EchoString(struct {
value string:MAX_STRING_LENGTH;
}) -> (struct {
response string:MAX_STRING_LENGTH;
});
SendString(struct {
value string:MAX_STRING_LENGTH;
});
-> 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:
- Implement a FIDL protocol.
- Build and run a package on Fuchsia.
- Serve a FIDL protocol.
The tutorial starts by creating a component that is served to a Fuchsia device and run. Then, it gradually adds functionality to get the server up and running.
If you want to write the code yourself, delete the following directories:
rm -r examples/fidl/llcpp/server/*
Create the component
To create a component:
Add a
main()
function toexamples/fidl/llcpp/server/main.cc
:#include <iostream> int main(int argc, const char** argv) { std::cout << "Hello, world!" << std::endl; return 0; }
Declare a target for the server in
examples/fidl/llcpp/server/BUILD.gn
:import("//build/components.gni") # Declare an executable for the server. This produces a binary with the # specified output name that can run on Fuchsia. executable("bin") { output_name = "fidl_echo_llcpp_server" sources = [ "main.cc" ] } # Declare a component for the server, which consists of the manifest and the # binary that the component will run. fuchsia_component("echo-server") { component_name = "echo_server" manifest = "meta/server.cml" deps = [ ":bin" ] } # Declare a package that contains a single component, our server. fuchsia_package("echo-llcpp-server") { package_name = "echo-llcpp-server" deps = [ ":echo-server" ] }
To get the server component up and running, there are three targets that are defined:
- The raw executable file for the server that is built to run on Fuchsia.
- A component that is set up to simply run the server executable, which is described using the component's manifest file.
- 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.
For more details on packages, components, and how to build them, refer to the Building components page.
Add a component manifest in
examples/fidl/llcpp/server/meta/server.cml
:{ include: [ "syslog/client.shard.cml", "syslog/elf_stdio.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_llcpp_server", }, // Capabilities provided by this component. capabilities: [ { protocol: "fuchsia.examples.Echo" }, ], expose: [ { protocol: "fuchsia.examples.Echo", from: "self", }, ], }
Add the server to your build configuration:
fx set core.qemu-x64 --with //examples/fidl/llcpp/server:echo-llcpp-server
Build the Fuchsia image:
fx build
Implement the server
Add a dependency on the FIDL library
Add the
fuchsia.examples
FIDL library target as a dependency of yourexecutable
inexamples/fidl/llcpp/server/BUILD.gn
:executable("bin") { output_name = "fidl_echo_llcpp_server" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_llcpp" ] }
Import the LLCPP bindings at the top of
examples/fidl/llcpp/server/main.cc
:#include <fidl/fuchsia.examples/cpp/wire.h>
Add an implementation for the protocol
Add the following to main.cc
, above the main()
function:
// An implementation of the Echo protocol. Protocols are implemented in LLCPP by
// creating a subclass of the ::Interface class for the protocol.
class EchoImpl final : public fidl::WireServer<fuchsia_examples::Echo> {
public:
// Bind this implementation to a channel.
EchoImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<fuchsia_examples::Echo> request)
: binding_(fidl::BindServer(dispatcher, std::move(request), this,
// This is a fidl::OnUnboundFn<EchoImpl>.
[this](EchoImpl* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
if (info.is_peer_closed()) {
std::cout << "Client disconnected" << std::endl;
} else if (!info.is_user_initiated()) {
std::cerr << "server error: " << info << std::endl;
}
delete this;
})) {}
// Handle a SendString request by sending on OnString "event" (an unsolicited server-to-client
// message) back on the same channel.
//
// 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 if the connection is "done"
// or it encountered an unrecoverable error).
void SendString(SendStringRequestView request, SendStringCompleter::Sync& completer) override {
fidl::Status status = fidl::WireSendEvent(binding_)->OnString(request->value);
ZX_ASSERT(status.ok());
}
// Handle an EchoString request by responding with the request value. For two-way methods (those
// with a response) like this one, the completer is used to send the response.
void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
completer.Reply(request->value);
}
private:
// A reference back to the Binding that this class is bound to, which is used
// to send events to the client.
fidl::ServerBindingRef<fuchsia_examples::Echo> binding_;
};
The implementation contains the following elements:
- The class subclasses the generated protocol class and overrides its pure virtual methods corresponding to the protocol methods.
- It contains a
ServerBindingRef
in order to be able to send events to the client. - The constructor method binds the implementation to a given
request
. - The method for
EchoString
replies synchronously with the request value by using the completer (for asynchronous replies, see responding to requests asynchronously in LLCPP). - The method for
SendString
uses thebinding_
member (if defined) to send anOnString
event containing the request value.
You can verify that the implementation builds by running:
fx build
Serve the protocol
When running a component that implements a FIDL protocol, you must make a request to the component manager to expose that FIDL protocol to other components. The component manager then routes any requests for the echo protocol to our server.
To fulfill these requests, the component manager requires the name of the protocol as well as a handler that it should call when it has any incoming requests to connect to a protocol matching the specified name.
The handler passed to it is a function that takes a channel (whose remote
end is owned by the client), and binds it to our server implementation.
The resulting fidl::ServerBindingRef
is reference to a server binding
that takes a FIDL protocol implementation and a channel,
and then listens on the channel for incoming requests. The binding then decodes
the requests, dispatches them to the correct method on our server class, and
writes any response back to the client. Our main method will keep listening
for incoming requests on an async loop.
This complete process is described in further detail in the Life of a protocol open.
Add new dependencies
This new code requires the following additional dependencies:
"//zircon/system/ulib/async-loop:async-loop-cpp"
and"//zircon/system/ulib/async-loop:async-loop-default"
: These libraries contain the async loop code."//sdk/lib/fdio"
and"//zircon/system/ulib/svc"
: These libraries are used to interact with the components environment (e.g. for serving protocols).
Add the library targets as dependencies of your
executable
inexamples/fidl/llcpp/server/BUILD.gn
:executable("bin") { output_name = "fidl_echo_llcpp_server" sources = [ "main.cc" ] deps = [ "//examples/fidl/fuchsia.examples:fuchsia.examples_llcpp", "//sdk/lib/fdio", "//sdk/lib/stdcompat", "//zircon/system/ulib/async-loop:async-loop-cpp", "//zircon/system/ulib/async-loop:async-loop-default", "//zircon/system/ulib/svc", ] }
Import these dependencies at the top of
examples/fidl/llcpp/server/main.cc
:#include <lib/async-loop/cpp/loop.h> #include <lib/async-loop/default.h> #include <lib/fdio/directory.h> #include <lib/fidl/llcpp/server.h> #include <lib/stdcompat/optional.h> #include <lib/svc/outgoing.h> #include <zircon/process.h> #include <zircon/processargs.h> #include <zircon/status.h>
Initialize the event loop
int main(int argc, char** argv) {
// Initialize the async loop. The Echo server will use the dispatcher of this
// loop to listen for incoming requests.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
// Create an Outgoing class which will serve requests from the /svc/ directory.
svc::Outgoing outgoing(loop.dispatcher());
zx_status_t status = outgoing.ServeFromStartupInfo();
if (status != ZX_OK) {
std::cerr << "error: ServeFromStartupInfo returned: " << status << " ("
<< zx_status_get_string(status) << ")" << std::endl;
return -1;
}
// Register a handler for components trying to connect to fuchsia.examples.Echo.
status = outgoing.svc_dir()->AddEntry(
fidl::DiscoverableProtocolName<fuchsia_examples::Echo>,
fbl::MakeRefCounted<fs::Service>(
[dispatcher](fidl::ServerEnd<fuchsia_examples::Echo> request) mutable {
std::cout << "Incoming connection for "
<< fidl::DiscoverableProtocolName<fuchsia_examples::Echo> << std::endl;
// Create an instance of our EchoImpl that destroys itself when the connection closes.
new EchoImpl(dispatcher, std::move(request));
return ZX_OK;
}));
std::cout << "Running echo server" << std::endl;
loop.Run();
return 0;
}
The event loop is used to asynchronously listen for incoming connections and requests from the client. This code initializes the loop, and obtains the dispatcher, which will be used when binding the server implementation to a channel.
At the end of the main function, the code runs the loop to completion.
Serve component's service directory
The svc::Outgoing
class serves the service directory ("/svc") for a given
component. This directory is where the outgoing FIDL protocols are installed so
that they can be provided to other components. The ServeFromStartupInfo()
function sets up the service 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.
int main(int argc, char** argv) {
// Initialize the async loop. The Echo server will use the dispatcher of this
// loop to listen for incoming requests.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
// Create an Outgoing class which will serve requests from the /svc/ directory.
svc::Outgoing outgoing(loop.dispatcher());
zx_status_t status = outgoing.ServeFromStartupInfo();
if (status != ZX_OK) {
std::cerr << "error: ServeFromStartupInfo returned: " << status << " ("
<< zx_status_get_string(status) << ")" << std::endl;
return -1;
}
// Register a handler for components trying to connect to fuchsia.examples.Echo.
status = outgoing.svc_dir()->AddEntry(
fidl::DiscoverableProtocolName<fuchsia_examples::Echo>,
fbl::MakeRefCounted<fs::Service>(
[dispatcher](fidl::ServerEnd<fuchsia_examples::Echo> request) mutable {
std::cout << "Incoming connection for "
<< fidl::DiscoverableProtocolName<fuchsia_examples::Echo> << std::endl;
// Create an instance of our EchoImpl that destroys itself when the connection closes.
new EchoImpl(dispatcher, std::move(request));
return ZX_OK;
}));
std::cout << "Running echo server" << std::endl;
loop.Run();
return 0;
}
Serve the protocol
The server then registers the Echo protocol using ougoing.svc_dir()->AddEntry()
.
int main(int argc, char** argv) {
// Initialize the async loop. The Echo server will use the dispatcher of this
// loop to listen for incoming requests.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
// Create an Outgoing class which will serve requests from the /svc/ directory.
svc::Outgoing outgoing(loop.dispatcher());
zx_status_t status = outgoing.ServeFromStartupInfo();
if (status != ZX_OK) {
std::cerr << "error: ServeFromStartupInfo returned: " << status << " ("
<< zx_status_get_string(status) << ")" << std::endl;
return -1;
}
// Register a handler for components trying to connect to fuchsia.examples.Echo.
status = outgoing.svc_dir()->AddEntry(
fidl::DiscoverableProtocolName<fuchsia_examples::Echo>,
fbl::MakeRefCounted<fs::Service>(
[dispatcher](fidl::ServerEnd<fuchsia_examples::Echo> request) mutable {
std::cout << "Incoming connection for "
<< fidl::DiscoverableProtocolName<fuchsia_examples::Echo> << std::endl;
// Create an instance of our EchoImpl that destroys itself when the connection closes.
new EchoImpl(dispatcher, std::move(request));
return ZX_OK;
}));
std::cout << "Running echo server" << std::endl;
loop.Run();
return 0;
}
The call to AddEntry
installs a handler for the name of the FIDL protocol
(fuchsia_examples::Echo::Name
, which is the string "fuchsia.examples.Echo"
).
The handler will call the lambda function that we created, and this lambda
function will construct an EchoImpl
with the
fidl::ServerEnd<fuchsia_examples::Echo>
, which internally wraps a
zx::channel
, that represents a request from a client. The EchoImpl
stays
alive until it is unbound, at which point it deletes itself.
When the handler is called (i.e. when a client has requested to connect to the
/svc/fuchsia.examples.Echo
protocol), it binds the incoming channel to our
Echo
implementation, which will start listening for Echo
requests on that
channel and dispatch them to the EchoImpl
instance. EchoImpl
's constructor
creates a fidl::ServerBindingRef
which is used to send events back to the
client.
Test the server
Rebuild:
fx build
Then run the server component:
ffx component run fuchsia-pkg://fuchsia.com/echo-llcpp-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 echo server
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