| # Implement a sync LLCPP FIDL client |
| |
| <!-- <<../../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 synchronous. There is an [alternate tutorial][async-client] for |
| asynchronous clients. |
| |
| If you want to write the code yourself, delete the following directories: |
| |
| ``` |
| rm -r examples/fidl/llcpp/client_sync/* |
| ``` |
| |
| ## Create a stub component |
| |
| 1. Set up a hello world component in `examples/fidl/llcpp/client_sync`. |
| You can name the component `echo-client`, and give the package a name of |
| `echo-llcpp-client-sync`. |
| |
| Note: If necessary, refer back to the [previous tutorial][server-tut]. |
| |
| 1. Once you have created your component, ensure that the following works: |
| |
| ``` |
| fx set core.x64 --with //examples/fidl/llcpp/client_sync |
| ``` |
| |
| 1. Build the Fuchsia image: |
| |
| ``` |
| fx build |
| ``` |
| |
| 1. In a separate terminal, run: |
| |
| ``` |
| fx serve |
| ``` |
| |
| 1. In a separate terminal, run: |
| |
| ``` |
| fx shell run fuchsia-pkg://fuchsia.com/echo-llcpp-client-sync#meta/echo-client.cmx |
| ``` |
| |
| ## Edit GN dependencies |
| |
| 1. Add the following dependencies: |
| |
| ```gn |
| {%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client_sync/BUILD.gn" region_tag="deps" %} |
| ``` |
| |
| 1. Then, include them in `main.cc`: |
| |
| ```cpp |
| {%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client_sync/main.cc" region_tag="includes" %} |
| ``` |
| |
| These dependencies are explained in the [server tutorial][server-tut]. The client requires far |
| fewer dependencies because it does not need to run any asynchronous code. |
| |
| ## Edit component manifest |
| |
| 1. Include the `Echo` protocol in the client component's sandbox by |
| editing the component manifest in `client.cmx`. |
| |
| ```cmx |
| {%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client_sync/client.cmx" %} |
| ``` |
| |
| ## 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. |
| |
| ### 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_sync/main.cc" region_tag="main" highlight="2,3,4,5,6,7,8,9" %} |
| ``` |
| |
| In practice, this 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. |
| In parallel, the component manager will route the requested service name and channel to the |
| server component, where the [`connect` function][server-handler] is called with these arguments, |
| binding the channel to the server implementation. The other end of this channel is then used to initialized the client object. |
| |
| 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. |
| |
| The `get_svc_directory()` function is defined as follows: |
| |
| ```cpp |
| {%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client_sync/main.cc" region_tag="connect" %} |
| ``` |
| |
| Similar to the `fdio_service_connect_at` call above, this function initializes a channel, |
| and passes the server end to `fdio_service_connect` to connect to the `/svc` directory, returning |
| the client end. |
| |
| Note: This pattern of making a request to connect one 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 the [request pipelining tutorial][pipelining-tut]. |
| |
| ### Send requests to the server |
| |
| The code makes two requests to the server: |
| |
| * An `EchoString` request |
| * A `SendString` request |
| |
| ```cpp |
| {%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client_sync/main.cc" region_tag="main" highlight="11,12,13,14,15,16,17,18,19,20,22,23" %} |
| ``` |
| |
| The protocol methods on the client object (`EchoString` and `SendString`) return a |
| [result object][resultof], which will contain either an error or the contents of the response |
| (if any). When a response is expected, the client will block until the response is received. |
| |
| A client object is generated for each protocol, which is described further in the |
| [LLCPP bindings reference][sync-client] |
| |
| ### Handle events |
| |
| The client object allows handling events by specifying an |
| [event handlers struct][event-handlers], where each |
| member corresponds to the handler for one of the events of the protocol. |
| |
| The code defines a handler which prints the contents of an `OnString` event, |
| then calls `client.HandleEvents()` to block until an event is received. The |
| return value of the callback becomes the return value of the `HandleEvents` call: |
| |
| ```cpp |
| {%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/client_sync/main.cc" region_tag="main" highlight="25,26,27,28,29,30,31,32,33,34,35" %} |
| ``` |
| |
| ## 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`][environment] for |
| the client that provides the server's protocol, then launches the client in it. |
| |
| 1. Configure your GN build: |
| |
| ``` |
| fx set core.x64 --with //examples/fidl/llcpp/server --with |
| //examples/fidl/client/client_sync --with //examples/fidl/test:echo-launcher |
| ``` |
| |
| 2. Build the Fuchsia image: |
| |
| ``` |
| fx build |
| ``` |
| |
| 3. 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/echo-launcher#meta/launcher.cmx fuchsia-pkg://fuchsia.com/echo-llcpp-client-sync#meta/echo-client.cmx fuchsia-pkg://fuchsia.com/echo-llcpp-server#meta/echo-server.cmx fuchsia.examples.Echo |
| ``` |
| |
| You should see the print output in the QEMU console (or using `fx log`). |
| |
| ``` |
| [189209.659] 859216:859218> Running echo server |
| [189209.778] 859216:859218> echo_server_llcpp: Incoming connection for fuchsia.examples.Echo |
| [189209.803] 859554:859556> Got response: hello |
| [189209.804] 859554:859556> Got event: hi |
| ``` |
| <!-- xrefs --> |
| [server-tut]: /docs/development/languages/fidl/tutorials/llcpp/basics/server.md |
| [async-client]: /docs/development/languages/fidl/tutorials/llcpp/basics/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 |
| [resultof]: /docs/reference/fidl/bindings/llcpp-bindings.md#resultof |
| [sync-client]: /docs/reference/fidl/bindings/llcpp-bindings.md#sync-client |
| [event-handlers]: /docs/reference/fidl/bindings/llcpp-bindings.md#event-handlers |