| # Implement an LLCPP FIDL client |
| |
| <!-- TODO(fxbug.dev/58758) <<../../common/client/overview.md>> --> |
| |
| ## Prerequisites |
| |
| This tutorial builds on the [FIDL server][server-tut] tutorial. For the |
| full set of FIDL tutorials, refer to the [overview][overview]. |
| |
| ## Overview |
| |
| This tutorial implements a client for a FIDL protocol and runs it against the |
| server created in the [previous tutorial][server-tut]. 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: |
| |
| ```posix-terminal |
| rm -r examples/fidl/llcpp/client/* |
| ``` |
| |
| ## Create the component |
| |
| Create a new component project at `examples/fidl/llcpp/client`: |
| |
| 1. Add a `main()` function to `examples/fidl/llcpp/client/main.cc`: |
| |
| ```cpp |
| int main(int argc, const char** argv) { |
| std::cout << "Hello, world!" << std::endl; |
| } |
| ``` |
| |
| 1. Declare a target for the client in `examples/fidl/llcpp/client/BUILD.gn`: |
| |
| ```gn |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/BUILD.gn" region_tag="imports" %} |
| |
| # Declare an executable for the client. |
| executable("bin") { |
| output_name = "fidl_echo_llcpp_client" |
| sources = [ "main.cc" ] |
| } |
| |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/BUILD.gn" region_tag="rest" %} |
| ``` |
| |
| 1. Add a component manifest in `examples/fidl/llcpp/client/meta/client.cml`: |
| |
| Note: The binary name in the manifest must match the output name of the |
| `executable` defined in the previous step. |
| |
| ```json5 |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/meta/client.cml" region_tag="example_snippet" %} |
| ``` |
| |
| 1. Once you have created your component, ensure that you can add it to the |
| build configuration: |
| |
| ```posix-terminal |
| fx set core.qemu-x64 --with //examples/fidl/llcpp/client:echo-client |
| ``` |
| |
| 1. Build the Fuchsia image: |
| |
| ```posix-terminal |
| fx build |
| ``` |
| |
| ## Edit GN dependencies |
| |
| 1. Add the following dependencies: |
| |
| ```gn |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/BUILD.gn" region_tag="deps" %} |
| ``` |
| |
| 1. Then, include them in `main.cc`: |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="includes" %} |
| |
| These dependencies are explained in the [server tutorial][server-tut]. |
| |
| ## Connect to the server {#main} |
| |
| 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. |
| |
| ### Initialize the event loop |
| |
| As in the server, the code first sets up an async loop so that the client can |
| listen for incoming responses from the server without blocking. |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="main" highlight="2,3,20,25,34,36,52,53,65,66,80" %} |
| ``` |
| |
| The dispatcher is used to run two pieces of async code. It is first used to run |
| the `EchoString` method, and quits when the response is received. It is then run |
| after calling the `SendString` in order to listen for events, and quits when an |
| `OnString` event is received. The call to `ResetQuit()` in between these two |
| instances allows the client to reuse the loop. |
| |
| ### Connect to the server |
| |
| The client then connects to the service directory `/svc`, and uses it to connect |
| to the server. |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="main" highlight="5,6,7,8,9,11,12,13,14,15" %} |
| ``` |
| |
| The `service::OpenServiceRoot` function initializes a channel, then passes the |
| server end to `fdio_service_connect` to connect to the `/svc` directory, |
| returning the client end wrapped in a `zx::status` result type. We should check |
| for the `is_ok()` value on the result to determine if any synchronous error |
| occurred. |
| |
| Connecting to a protocol relative to the service directory is done by calling |
| `fdio_service_connect_at`, passing it the service directory, the name of the |
| service to connect to, as well as the channel that should get passed to the |
| server. The `service::ConnectAt` function wraps the low level `fdio` call, |
| providing the user with a typed client channel endpoint to the requested |
| protocol. |
| |
| In parallel, the component manager will route the requested service name and |
| channel to the server component, where the [`connect` function][server-handler] |
| implemented in the server tutorial is called with these arguments, binding the |
| channel to the server implementation. |
| |
| An important point to note here is that this code assumes that `/svc` already |
| contains an instance of the `Echo` protocol. This is not the case by default |
| because of the sandboxing provided by the component framework. A workaround will |
| be when [running the example](#run) at the end of the tutorial. |
| |
| Note: This pattern of making a request to connect the server end of the channel |
| to a service, then immediately using the client end to communicate with the |
| service is known as request pipelining. This topic is covered further in a |
| separate [tutorial][pipelining-tut]. |
| |
| ### Initialize the client {#proxy} |
| |
| In order to make `Echo` requests to the server, initialize a client using the |
| client end of the channel from the previous step, the loop dispatcher, as well |
| as an event handler delegate: |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="main" highlight="17,18,22,28,36,38,39" %} |
| ``` |
| |
| The event handler delegate should be an object that implements the |
| `fidl::AsyncEventHandler<Echo>` virtual interface, which has methods |
| corresponding to the events offered by the protocol (see |
| [LLCPP event handlers][event-handlers]). In this case, a local class is defined |
| with a method corresponding to the `OnString` event. The handler prints the |
| string and quits the event loop. The class also overrides the `on_fidl_error` |
| method, which is called when the client encounters an error and is going to |
| teardown. |
| |
| ### Send requests to the server |
| |
| The code makes four requests to the server: |
| |
| * An asynchronous `EchoString` call taking a result callback. |
| * An asynchronous `EchoString` call taking a response callback. |
| * A synchronous `EchoString` call. |
| * A one way `SendString` request (async vs sync is not relevant for this case |
| because it is a fire and forget method). |
| |
| The client object works by overriding the dereference operator to return a |
| [protocol specific client implementation][client-impl], allowing calls such as |
| `client->EchoString()`. |
| |
| #### Asynchronous call with result callback |
| |
| The first asynchronous method call requires the request parameters followed by |
| a *result callback*, which is called either when the method succeeds or an error |
| happens. |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="main" highlight="41,42,43,44,45,46,47,48,49,50,51" %} |
| ``` |
| |
| #### Asynchronous call with response callback |
| |
| The second asynchronous method call requires the request parameters followed by |
| a *response callback*, which is called when the response is received. If any |
| error happens during this call, the callback object is silently dropped. The |
| response callback takes a pointer to the response message directly. |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="main" highlight="55,56,57,58,59,60,61,62,63,64" %} |
| ``` |
| |
| #### Synchronous call |
| |
| The client object also allows synchronous calls, which will block until the |
| response is received and return the response object. These may be selected |
| using the `.sync()` accessor. (e.g. `client.sync()->EchoString()`). |
| |
| |
| ```cpp |
| {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client/main.cc" region_tag="main" highlight="68,69,70" %} |
| ``` |
| |
| In the synchronous case, a [result object][resultof] is returned, since the |
| method call can fail. In the asynchronous or fire-and-forget case, a lightweight |
| status object is returned, which communicates any synchronous errors. |
| |
| ## Run the client |
| |
| In order for the client and server to communicate using the `Echo` protocol, |
| component framework must route the `fuchsia.examples.Echo` capability from the |
| server to the client. For this tutorial, a [realm][glossary.realm] component is |
| provided to declare the appropriate capabilities and routes. |
| |
| Note: You can explore the full source for the realm component at |
| [`//examples/fidl/echo-realm`](/examples/fidl/echo-realm) |
| |
| 1. Configure your build to include the provided package that includes the |
| echo realm, server, and client: |
| |
| ```posix-terminal |
| fx set core.qemu-x64 --with //examples/fidl/llcpp:echo-llcpp-client |
| ``` |
| |
| 1. Build the Fuchsia image: |
| |
| ```posix-terminal |
| fx build |
| ``` |
| |
| 1. Run the `echo_realm` component. This creates the client and server component |
| instances and routes the capabilities: |
| |
| ```posix-terminal |
| ffx component run fuchsia-pkg://fuchsia.com/echo-llcpp-client#meta/echo_realm.cm |
| ``` |
| |
| 1. Start the `echo_client` instance: |
| |
| ```posix-terminal |
| ffx component start /core/ffx-laboratory:echo_realm/echo_client |
| ``` |
| |
| The server component starts when the client attempts to connect to the `Echo` |
| protocol. You should see output similar to the following in the device logs |
| (`ffx log`): |
| |
| ```none {:.devsite-disable-click-to-copy} |
| [echo_server][][I] Running echo server |
| [echo_server][][I] Incoming connection for fuchsia.examples.Echo |
| [echo_client][][I] Got response (result callback): hello |
| [echo_client][][I] Got response (response callback): hello |
| [echo_client][][I] Got synchronous response: hello |
| [echo_client][][I] Got event: hi |
| ``` |
| |
| Terminate the realm component to stop execution and clean up the component |
| instances: |
| |
| ```posix-terminal |
| ffx component destroy /core/ffx-laboratory:echo_realm |
| ``` |
| |
| <!-- xrefs --> |
| [glossary.realm]: /docs/glossary/README.md#realm |
| [bindings-ref]: /docs/reference/fidl/bindings/llcpp-bindings.md |
| [event-handlers]: /docs/reference/fidl/bindings/llcpp-bindings.md#events |
| [resultof]: /docs/reference/fidl/bindings/llcpp-bindings.md#resultof |
| [client-impl]: /docs/reference/fidl/bindings/llcpp-bindings.md#async-client |
| [server-handler]: /docs/development/languages/fidl/tutorials/llcpp/basics/server.md#server-handler |
| [server-tut]: /docs/development/languages/fidl/tutorials/llcpp/basics/server.md |
| [sync-client]: /docs/development/languages/fidl/tutorials/llcpp/basics/sync-client.md |
| [overview]: /docs/development/languages/fidl/tutorials/overview.md |
| [environment]: /docs/concepts/components/v2/environments.md |
| [pipelining-tut]: /docs/development/languages/fidl/tutorials/llcpp/topics/request-pipelining.md |