blob: ab8340ef0a67fa3fb0efcc467a0e1e8faf0619ce [file] [log] [blame] [view]
Example Echo Client & Server
====
This echo client/server demonstrate how to create and use a mojom interface,
as well as demonstrating one way to communicate between mojo applications.
For a deeper dive into this code, refer to the [Mojo
Tutorial](https://docs.google.com/document/d/1mufrtxTk8w9qa3jcnlgqsYkWlyhwEpc7aWNaSOks7ug).
## Running the Echo Client & Server
```
$ ./mojo/tools/mojob.py gn
$ ./mojo/tools/mojob.py build
$ ./out/Debug/mojo_shell mojo:echo_client
```
You should see output along the lines of:
```
[1010/194919:INFO:echo_client.cc(21)] ***** Response: hello world
```
This means that our echo_client started, contacted the echo_server (which was
started by the shell), sent a string through a mojom interface, and got a
response.
## Echo Client Structure
By running the echo_client through mojo_shell, we run a [Mojo
Application](https://docs.google.com/document/d/1xjt_TPjTu0elix8fNdBgWmnjJdJAtqSr1XDS_C-Ct8E).
Mojo Applications have main threads (run as `MojoMain`), and they may
communicate with other applications using Mojo IPC. This section will describe
the steps taken to start up the echo client, and what is necessary
to make an IPC call to the echo server service.
### ApplicationRunner: It calls your application
In echo_client.cc's MojoMain function, a new `ApplicationRunner` called `runner`
is created. This class is used by the shell for setting up and communicating
with an application. This is a common pattern in Mojo apps: a runner takes an
implementation of an `ApplicationDelegate` and runs it.
In echo_client, the delegate is a class called `EchoClientDelegate`.
### ApplicationDelegate: Your application will be a subclass of this
The ApplicationDelegate class is what we can think of as the heart of our
"app". It can implement three methods:
1. `void Initialize(ApplicationImpl* app)` -- Called during setup.
2. `bool ConfigureIncomingConnection(ApplicationConnection* connection)` --
Configures what happens when a connection attempts to reach our application.
3. `void Quit()` -- Called before termination.
Our echo_client only implements the `Initialize` method, since it does not need
to accept any incoming connections (it just makes one outgoing connection).
This initialize method takes an `ApplicationImpl* app` as an argument, which can
be used to contact other services. Here, we contact the "mojo:echo_server"
service using the `ConnectToService` method. This method takes a URL as an
argument, and passes an interface back in an `InterfacePtr`.
```
app->ConnectToService("mojo:echo_server", &echo_);
```
Note: When the Mojo Shell notices the echo_server service is not running, it
will automatically start the server service. This is why only running the client
is necessary for this example to work.
### Mojom Interfaces: An mechanism for predictable message passing
Interfaces are defined in ".mojom" files, and they allow applications to
interact with each other in a procedure-call mechanism. In mojom interfaces,
a client invokes a method, the arguments are serialized and passed to the
receiver, and the receiver invokes the method (and returns any results).
The [Mojom
language](https://docs.google.com/document/d/1r7yCseBktlDEN9CKp_JWD0ZYxMi4GCsLXMvSN5sI04k)
is used to define the simple `EchoString` interface, defined in echo.mojom. To
compile the mojom interface, it must be built using the "mojom" template in a
BUILD.gn file. The "echo.mojom" file is compiled as a part of a target named
"bindings". This will autogenerate a few files, one of which we are including in
our echo_client.cc example: "examples/echo/echo.mojom.h". Since our mojom file
specifies `interface Echo`, we can refer to the `EchoPtr` type to access our
interface.
If you create an `interface FooBar`, then you can use a type `FooBarPtr` to
reference your interface.
Since our interface defines the method:
```
EchoString(string? value) => (string? value)
```
this creates the following method (and more code, not shown):
```
void EchoString(const mojo::String& value, mojo::Callback<void(mojo::String)>);
```
This method is callable on an `InterfacePtr` which properly implements our mojom
interface (so, in this case, an `EchoPtr`, like the one returned from our
call to `ConnectToService`).
The second argument to our interface is a `mojo::Callback` class, which is
just a Runnable with varying arguments. For the echo_client example, we created
a `ResponsePrinter` class to act as this callback. By implementing `Run`, which
merely prints out the string we get from the echo server, we are able to call
the `EchoString` method on the `EchoPtr` received from `ConnectToService`.
```
echo_->EchoString("hello world", ResponsePrinter());
```
In summary, the echo_client connects to the echo_server service using
the `ConnectToService` method, passing an interface defined in a mojom file. The
methods of this interface can then be invoked on the `EchoPtr`, with appropriate
callback implementations being passed where necessary.
## Echo Server Structure
The echo server, like the echo client, is implemented as an application. This
means it has a `MojoMain` function, an `ApplicationRunner`, and an
`ApplicationDelegate` actually implementing the core application.
echo_server.cc contains three different types of servers, though only one can be
used at a time. To try changing the server, uncomment one of the lines in
MojoMain. These different `ApplicationDelegate` derivations demonstrate
different ways in which incoming requests can be handled.
All three servers, being `ApplicationDelegate` derivations, implement
`ConfigureIncomingConnection` in the same way:
```
service_provider_impl->AddService<Echo>(
[this](const mojo::ConnectionContext& connection_context,
mojo::InterfaceRequest<Echo> echo_request) {
...
});
```
This should be read as "For any incoming connections to this server, use the
given lambda function use `this` to create the Echo interface".
### EchoImpl: The Interface Implementation
All three implementations use the `EchoImpl` class, implementing the `Echo`
interface we defined in our mojom file, which does what you would expect of an
echo server: it sends back the supplied value String back to the client.
```
callback.Run(value);
```
If we wanted the server to pass back something else, we would pass a different
value here. However, as defined by our interface, the echo server can only
return a String.
### Server 1: MultiServer
On calls to `Create`, this server creates a new `StrongBindingEchoImpl` object
for each request. This object is derived from `EchoImpl`, so it implements the
interface, but by using the `StrongBinding` class, it cleans up after itself
once the message pipe used for the request is closed.
### Server 2: SingletonServer
This server creates an `EchoImpl` object when it is constructed, and for each
call to `Create`, binds the request to this single implementation. A
`BindingSet` is used so that multiple requests can be bound to the same object.
### Server 3: OneAtATimeServer
This server creates an `EchoImpl` object, like the `SingletonServer`, but uses a
single `Binding`, rather than a `BindingSet`. This means that when a new client
connects to the OneAtATimeServer, the previous binding is closed, and a new
binding is made between the new client and the interface implementation.
The OneAtATimeServer demonstrates a pattern that should be avoided because it
contains a race condition for multiple clients. If a new client binds to the
server before the first client managed to call EchoString, the first client's
call would cause an error. Unless you have a specific use case for this
behavior, it is advised to avoid creating a server like this.