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.
$ ./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.
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.
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
.
The ApplicationDelegate class is what we can think of as the heart of our “app”. It can implement three methods:
void Initialize(ApplicationImpl* app)
-- Called during setup.bool ConfigureIncomingConnection(ApplicationConnection* connection)
-- Configures what happens when a connection attempts to reach our application.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.
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.
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”.
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.
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.
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.
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.