Connecting components
<<../../_common/fidl/_connecting_intro.md>>
Publishing a protocol implementation
Components that implement a FIDL protocol declare and expose that protocol as a capability in their component manifest. This enables the component framework to perform capability routing from this component to others in the topology that request the capability.
{
// ...
capabilities: [
{ protocol: "fuchsia.example.Foo" },
],
expose: [
{
protocol: "fuchsia.example.Foo",
from: "self",
},
],
}
Capability routing describes the access rights for the protocol, but it does
not establish the necessary endpoints for a connection. Components must publish
the implementation as an /svc/ handle in the outgoing directory using the
fuchsia.io protocol. The
generated FIDL bindings wrap this handle and enable the provider to connect a
request handle to begin receiving FIDL messages.
- {Rust}
let mut service_fs = ServiceFs::new_local();
// Serve the protocol
service_fs.dir("svc").add_fidl_service(PROTOCOL_NAME);
service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;
- {C++}
// Serve the protocol
FooImplementation instance;
fidl::Binding<fuchsia::example::Foo> binding(&instance);
instance.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fuchsia::example::Foo> handler =
[&](fidl::InterfaceRequest<fuchsia::example::Foo> request) {
binding.Bind(std::move(request));
};
context->outgoing()->AddPublicService(std::move(handler));
Connecting to a protocol implementation
Client components declare the protocol as a required capability in their
component manifest. This allows the component framework to determine whether
the component has the rights to access protocol implementation. If a valid route
exists, the component's namespace contains a corresponding /svc/ handle.
The client component uses the fuchsia.io protocol to establish a connection to the protocol implementation and open a channel. The generated FIDL bindings wrap this channel and enable the client to begin sending messages to the provider.
- {Rust}
// Connect to FIDL protocol
let protocol = connect_to_protocol::<FooMarker>().expect("error connecting to echo");
- {C++}
// Connect to FIDL protocol
fuchsia::example::FooSyncPtr proxy;
auto context = sys::ComponentContext::Create();
context->svc()->Connect(proxy.NewRequest());
Exercise: Echo server and client
In this section, you'll use the generated FIDL bindings for
fidl.examples.routing.echo to implement client and server components in Rust.
<<../_common/_start_femu_with_packages.md>>
Create the server component
Begin by creating a new component project to implement the echo server. This
component will serve the Echo protocol and handle incoming requests.
- {Rust}
- {C++}
Add the generated bindings to the BUILD.gn file as a dependency:
- {Rust}
echo-server/BUILD.gn:
rustc_binary("bin") {
output_name = "echo-server"
deps = [
{{ '<strong>' }}"//vendor/fuchsia-codelab/echo-fidl:echo-rustc",{{ '</strong>' }}
...
]
sources = [ "src/main.rs" ]
}
- {C++}
echo-server/BUILD.gn:
executable("bin") {
output_name = "echo-server"
deps = [
{{ '<strong>' }}"//vendor/fuchsia-codelab/echo-fidl:echo",{{ '</strong>' }}
...
]
sources = [ "main.cc" ]
}
Declare the Echo protocol as a capability provided by the server component,
and expose it for use by the parent realm:
- {Rust}
echo-server/meta/echo_server.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_server/meta/echo_server.cml" region_tag="example_snippet" adjust_indentation="auto" %}
- {C++}
echo-server/meta/echo_server.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_server/meta/echo_server.cml" region_tag="example_snippet" adjust_indentation="auto" %}
Implement the server
Open the main source file and replace the import statements with the following code:
- {Rust}
echo-server/src/main.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_server/src/main.rs" region_tag="imports" adjust_indentation="auto" %}
- {C++}
echo-server/main.cc:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_server/main.cc" region_tag="imports" adjust_indentation="auto" %}
Add the following code to main() to serve the Echo protocol:
- {Rust}
echo-server/src/main.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_server/src/main.rs" region_tag="main_body" adjust_indentation="auto" highlight="1,2,3,4,14,15,23,24,25,26,27" %}
This code performs the following steps to serve the Echo protocol:
- Initialize
ServiceFsand add an entry under/svc/fidl.examples.routing.echo.Echoin the outgoing directory. - Serve the directory and begin listening for incoming connections.
-
Attach the
handle_echo_request()function as a request handler for any matchingEchorequests. -
{C++}
echo-server/main.cc:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_server/main.cc" region_tag="main_body" adjust_indentation="auto" highlight="3,8,13,14,15,16,17" %}
This code performs the following steps to serve the Echo protocol:
- Initialize
ComponentContextand add an entry under/svc/fidl.examples.routing.echo.Echoin the outgoing directory. - Serve the directory and begin listening for incoming connections.
- Attach the
EchoImplementationinstance as a request handler for any matchingEchorequests.
Add the following code to implement the protocol request handler:
- {Rust}
echo-server/src/main.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_server/src/main.rs" region_tag="handler" adjust_indentation="auto" %}
Each request in the EchoRequestStream is typed by the method name
(EchoString) and includes a responder interface to send back the return value.
- {C++}
echo-server/main.cc:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_server/main.cc" region_tag="handler" adjust_indentation="auto" %}
Each Echo protocol method has a corresponding override function
(EchoString()) and includes a callback interface to send back the return value.
This implementation simply "echoes" the same string value from the request back in the response payload.
Create the client component
Create another new component project to implement the echo client. This component will connect to the protocol implementation and send requests.
- {Rust}
- {C++}
Add the generated bindings to the BUILD.gn file as a dependency:
- {Rust}
echo-client/BUILD.gn:
rustc_binary("bin") {
output_name = "echo-client"
deps = [
{{ '<strong>' }}"//vendor/fuchsia-codelab/echo-fidl:echo-rustc",{{ '</strong>' }}
...
]
sources = [ "src/main.rs" ]
}
- {C++}
echo-client/BUILD.gn:
executable("bin") {
output_name = "echo-client"
deps = [
{{ '<strong>' }}"//vendor/fuchsia-codelab/echo-fidl:echo",{{ '</strong>' }}
...
]
sources = [ "main.cc" ]
}
Configure the client's component manifest to request the
fidl.examples.routing.echo.Echo capability exposed by the server:
- {Rust}
echo-client/meta/echo_client.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_client/meta/echo_client.cml" region_tag="example_snippet" adjust_indentation="auto" %}
- {C++}
echo-client/meta/echo_client.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_client/meta/echo_client.cml" region_tag="example_snippet" adjust_indentation="auto" %}
Implement the client
Similar to echo-args, the client passes the program arguments as a message
to the server. Add the following program arguments to echo_client.cml:
- {Rust}
echo-client/meta/echo_client.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_client/meta/echo_client.cml" region_tag="program_args" adjust_indentation="auto" highlight="9,10" %}
- {C++}
echo-client/meta/echo_client.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_client/meta/echo_client.cml" region_tag="program_args" adjust_indentation="auto" highlight="9,10" %}
Update the manifest includes to provide logging support on stdout:
- {Rust}
echo-client/meta/echo_client.cml:
{
include: [
"inspect/client.shard.cml",
// Enable logging on stdout
"syslog/elf_stdio.shard.cml",
],
// ...
}
- {C++}
echo-client/meta/echo_client.cml:
{
include: [
"inspect/client.shard.cml",
// Enable logging on stdout
"syslog/elf_stdio.shard.cml",
],
// ...
}
Open the main source file and replace the import statements with the following code:
- {Rust}
echo-client/src/main.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_client/src/main.rs" region_tag="imports" adjust_indentation="auto" %}
- {C++}
echo-client/main.cc:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_client/main.cc" region_tag="imports" adjust_indentation="auto" %}
Add the following code to main() to connect to the Echo protocol and send
a request:
- {Rust}
echo-client/src/main.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/rust/echo_client/src/main.rs" region_tag="main_body" adjust_indentation="auto" %}
The EchoMarker provides a wrapper to connect to the exposed capability by
name and returns a handle to the open EchoProxy interface. This proxy contains
the echo_string() FIDL protocol method.
- {C++}
echo-client/main.cc:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/cpp/echo_client/main.cc" region_tag="main_body" adjust_indentation="auto" %}
The EchoSyncPtr provides a wrapper to connect to the exposed capability by
name and returns a handle to the open proxy interface. This proxy contains
the EchoString() FIDL protocol method.
Integrate the components
The capabilities provided by the server must be routed to the client through the component framework. To enable this, you will implement a realm component to act as the parent and manage capability routing.
Create a new project directory for the realm product definition:
Create a new component manifest file meta/echo_realm.cml with the
following contents:
echo-realm/meta/echo_realm.cml:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/routing/meta/echo_realm.cml" region_tag="example_snippet" adjust_indentation="auto" %}
This creates a component realm with the server and client as child components, and routes the fidl.examples.routing.echo.Echo protocol capability to the client.
Add a BUILD.gn file to create a build target for the realm component:
echo-realm/BUILD.gn:
import("//build/components.gni")
fuchsia_component("echo_realm") {
manifest = "meta/echo_realm.cml"
}
fuchsia_package("echo-realm") {
deps = [
":echo_realm",
"//vendor/fuchsia-codelab/echo-server:component",
"//vendor/fuchsia-codelab/echo-client:component",
]
}
Update the build configuration to include the new components:
fx set workstation_eng.qemu-x64 \
--with //vendor/fuchsia-codelab/echo-fidl:echo \
--with //vendor/fuchsia-codelab/echo-server \
--with //vendor/fuchsia-codelab/echo-client \
--with //vendor/fuchsia-codelab/echo-realm
Run fx build again to build the components:
Add the components to the topology
You will add your component to the ffx-laboratory — a restricted collection
used for development inside the product's core realm. Collections enable
components to be dynamically created and destroyed at runtime.
Create the component instances by passing the echo-realm component URL and
an appropriate moniker inside ffx-laboratory to ffx component create:
ffx component create /core/ffx-laboratory:echo-realm \
fuchsia-pkg://fuchsia.com/echo-realm#meta/echo_realm.cm
Then, resolve the echo-realm component with ffx component resolve:
Verify that instances of the server and client were also created as child
components using ffx component show:
```none {:.devsite-disable-click-to-copy} Moniker: /core/ffx-laboratory:echo-realm/echo_client URL: #meta/echo_client.cm Type: CML static component Component State: Unresolved Execution State: Stopped
Moniker: /core/ffx-laboratory:echo-realm/echo_server
URL: #meta/echo_server.cm
Type: CML static component
Component State: Unresolved
Execution State: Stopped
Moniker: /core/ffx-laboratory:echo-realm
URL: fuchsia-pkg://fuchsia.com/echo-realm#meta/echo_realm.cm
Type: CML dynamic component
Component State: Resolved
Execution State: Stopped
Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b
```
Verify the component interactions
Start the existing client component instance using ffx component start:
posix-terminal
ffx component start /core/ffx-laboratory:echo-realm/echo_client
Open another terminal window and verify the log output from the client component:
You should see the following output in the device logs:
```none {:.devsite-disable-click-to-copy} [echo_client][I] Server response: Hello, Fuchsia!
The server component starts once the client makes a connection to the
`fidl.examples.routing.echo.Echo` capability and continues running to serve
additional FIDL requests.
Use `ffx component show` the see the echo server running in the component
instance tree:
```posix-terminal
ffx component show echo_server
```none {:.devsite-disable-click-to-copy} Moniker: /core/ffx-laboratory:echo-realm/echo_server URL: #meta/echo_server.cm Type: CML static component Component State: Resolved Incoming Capabilities: fuchsia.logger.LogSink Exposed Capabilities: diagnostics fidl.examples.routing.echo.Echo Execution State: Running Job ID: 474691 Process ID: 474712 Running for: 2026280474361 ticks Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b Outgoing Capabilities: diagnostics fidl.examples.routing.echo.Echo
### Destroy the instance
Clean up the `echo-realm` instance using the following command:
```posix-terminal
ffx component destroy /core/ffx-laboratory:echo-realm