tree: c164c1c2fb734248c0b516331802f45c541a0613 [path history] [tgz]
  1. BUILD.gn
  2. echo.mojom
  3. echo_benchmark.cc
  4. echo_client.cc
  5. echo_client_sync.cc
  6. echo_server.cc
  7. README.md
examples/echo/README.md

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.

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. 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 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.