Prerequisites
This tutorial builds on the FIDL server tutorial. For the full set of FIDL tutorials, refer to the overview.
Overview
This tutorial implements a client for a FIDL protocol and runs it against the server created in the previous tutorial. The client in this tutorial is asynchronous. There is an [alternate tutorial][sync-client] for synchronous clients.
If you want to write the code yourself, delete the following directories:
rm -r examples/fidl/dart/client/*
Create a stub component
Set up a hello world
dart_app
inexamples/fidl/dart/client
, with a name ofecho-dart-client
.Once you have created your component, ensure that the following works:
fx set core.x64 --with //examples/fidl/dart/client
Build the Fuchsia image:
fx build
In a separate terminal, run:
fx serve
In a separate terminal, run:
fx shell run fuchsia-pkg://fuchsia.com/echo-dart-client#meta/echo-dart-client.cmx
Edit GN dependencies
Add the following dependencies:
deps = [ "//examples/fidl/fuchsia.examples", dart_package_label.fidl, dart_package_label.fuchsia, dart_package_label.fuchsia_logger, dart_package_label.fuchsia_services, ]
Then, import them in
lib/main.dart
:import 'dart:async'; import 'package:fidl_fuchsia_examples/fidl_async.dart' as fidl_echo; import 'package:fuchsia_services/services.dart' as sys; import 'package:fuchsia/fuchsia.dart' show exit; import 'package:fuchsia_logger/logger.dart';
These dependencies are explained in the server tutorial.
Edit component manifest
Include the
Echo
protocol in the client component's sandbox by editing the component manifest inclient.cmx
.{ "include": [ "sdk/lib/diagnostics/syslog/client.shard.cmx" ], "program": { "binary": "bin/fidl_echo_rust_client" }, "sandbox": { "services": [ "fuchsia.examples.Echo" ] } }
Connect to the server
The steps in this section explain how to add code to the main()
function
that connects the client to the server and makes requests to it.
Bind a client object
The FIDL bindings generate a class for each protocol that can be used to make requests to a server, called a proxy class. To connect to the server, the client needs to initialize a proxy class and then bind it to the server:
Future<void> main(List<String> args) async {
setupLogger(name: 'echo-client');
// Bind. We bind EchoProxy, a generated proxy class, to the remote Echo
// service.
final client = fidl_echo.EchoProxy();
sys.StartupContext.fromStartupInfo().incoming.connectToService(client);
// Invoke echoString with a value and print its response.
final response = await client.echoString('hello');
log.info('Got response: $response');
// Invoke sendString, which does not have a response
await client.sendString('hi');
// Wait for one OnString event and print its value.
final event = await client.onString.first;
log.info('Got event: $event');
// Allow log messages to get piped through to the syslogger before exiting
// and killing this process
await Future(() => exit(0));
}
Similar to the server code, the client uses sys.StartupContext.fromStartupInfo()
to access the
component's context. The difference is that the incoming
property is used instead of the
outgoing
property, since the client is connecting to a protocol rather than offering one.
The connectToService
call does a number of things under the hood:
- First, it creates a channel and binds one end to the
EchoProxy
.EchoProxy
, similarly toEchoBinding
, binds to a channel and listens for incoming messages (and sends messages back) on the channel. The channel end bound to theEchoProxy
is afidl.InterfaceHandle<Echo>
, whereas the other end of the channel is afidl.InterfaceRequest<Echo>
. - It then makes a request to the component manager to connect to the
Echo
protocol. Specifically, it requests the other end of the channel (from the previous step) to be connected to the protocol located at the service name of theEcho
protocol.
In the background, this request triggers the follow sequence of events:
- The component framework routes this request to the server, where the requested service name matches the service offered by the server.
- The connection request handler defined in the server code is invoked on the channel end (the
fidl.InterfaceRequest<Echo>
) that was provided by the client. - The handler code binds the server implementation to the channel, and starts handling any incoming messages on the channel. If the client started making requests before this point, these requests are buffered until the server is bound and starts reading from the channel. This is a process called request pipelining, which is covered in more depth in a separate tutorial.
Send requests to the server
The code makes two requests to the server:
- An
EchoString
request - A
SendString
request
Future<void> main(List<String> args) async {
setupLogger(name: 'echo-client');
// Bind. We bind EchoProxy, a generated proxy class, to the remote Echo
// service.
final client = fidl_echo.EchoProxy();
sys.StartupContext.fromStartupInfo().incoming.connectToService(client);
// Invoke echoString with a value and print its response.
final response = await client.echoString('hello');
log.info('Got response: $response');
// Invoke sendString, which does not have a response
await client.sendString('hi');
// Wait for one OnString event and print its value.
final event = await client.onString.first;
log.info('Got event: $event');
// Allow log messages to get piped through to the syslogger before exiting
// and killing this process
await Future(() => exit(0));
}
The call to EchoString
returns a future, which resolves to the response
returned by the server. The returned future will resolve to an error if there is
either an error sending the request or receiving the response (e.g. when
decoding the message, or if an epitaph was received).
The call to SendString
returns a Future<void>
since it is a fire and forget
method.
The bindings reference describes how these proxy methods are generated.
Handle incoming events
The code then waits for a single OnString
event from the server:
Future<void> main(List<String> args) async {
setupLogger(name: 'echo-client');
// Bind. We bind EchoProxy, a generated proxy class, to the remote Echo
// service.
final client = fidl_echo.EchoProxy();
sys.StartupContext.fromStartupInfo().incoming.connectToService(client);
// Invoke echoString with a value and print its response.
final response = await client.echoString('hello');
log.info('Got response: $response');
// Invoke sendString, which does not have a response
await client.sendString('hi');
// Wait for one OnString event and print its value.
final event = await client.onString.first;
log.info('Got event: $event');
// Allow log messages to get piped through to the syslogger before exiting
// and killing this process
await Future(() => exit(0));
}
This is done by taking the event stream from the client object, then waiting for a single event from it.
Run the client
If you run the client directly, it will not connect to the server correctly because the
client does not automatically get the Echo
protocol provided in its
sandbox (in /svc
). To get this to work, a launcher tool is provided
that launches the server, creates a new Environment
for
the client that provides the server's protocol, then launches the client in it.
Configure your GN build as follows:
fx set core.x64 --with //examples/fidl/dart/server --with //examples/fidl/dart/client --with //examples/fidl/dart/launcher_bin
Build the Fuchsia image:
fx build
Run the launcher by passing it the client URL, the server URL, and the protocol that the server provides to the client:
fx shell run fuchsia-pkg://fuchsia.com/example-launcher-dart#meta/example-launcher-dart.cmx fuchsia-pkg://fuchsia.com/echo-dart-client#meta/echo-dart-client.cmx fuchsia-pkg://fuchsia.com/echo-dart-server#meta/echo-dart-server.cmx fuchsia.examples.Echo
You should see the print output in the QEMU console (or using fx log
).
[105541.570] 489493:489495> Listening for incoming connections...
[105541.573] 489493:489495> Received EchoString request for string "hello"
[105541.574] 489493:489495> Response sent successfully
[105541.574] 489272:489274> response: "hello"
[105541.575] 489493:489495> Received SendString request for string "hi"
[105541.575] 489493:489495> Event sent successfully
[105541.575] 489272:489274> Received OnString event for string "hi"