Writing a Greeter Client in C++
This page presents how to create a C++ client that that makes requests to a server that is hosting an Ice object which implements the greeter interface.
The client we're going to write is simple; it creates a proxy to a greeter server, and invokes the greet
operation on it.
Client Implementation
The structure of our client is going to look like:
#include "../../common/Env.h"
#include "Greeter.h"
#include <Ice/Ice.h>
#include <future>
#include <iostream>
using namespace std;
int
main(int argc, char* argv[])
{
// ...
}
Before anything else, we need to include a few header files:
Env.h
: So we can retrieve the current user's username to pass togreet
(not usually necessary).Greeter.h
: The header file the Slice compiler generated fromGreeter.ice
.Ice.h
: This header provides definitions that are necessary for accessing the Ice runtime.future
: For when we callgreet
asynchronously using futures.iostream
: So we can usecout
to print to the console.
We also add a using
declaration for the std
namespace to reduce clutter.
Then we get to the interesting part: the main
function which will run the client logic.
It can be broken down into 3 logical pieces.
First, we create a Communicator and place it in a CommunicatorHolder, which ensures it will be destroyed when it goes out of scope:
Ice::CommunicatorPtr communicator = Ice::initialize(argc, argv);
Ice::CommunicatorHolder communicatorHolder{communicator};
Next, we create a proxy to the 'greeter' object. For this example, we assume that the server runs on localhost
and listens using the default transport (TCP) on port 4061.
VisitorCenter::GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};
Lastly, we can make our invocations to the greeter server. Yep, that’s all it takes!
In this example we show all three ways the client could invoke greet
with Ice for C++: a synchronous call, an asynchronous call using futures, and an asynchronous call using callbacks.
The synchronous API is really only provided for convenience. Remote invocations are inherently asynchronous by nature, so it’s best practice to use calls which reflect that.
Synchronous Invocation
Invoking greet
synchronously means the client sends the request, and the call will block until a response is received from the server. Here's what it looks like:
string greeting = greeter->greet(Env::getUsername());
cout << greeting << endl;
Here, we use Env
to pass the current username into greet, but as you’ll see, it can be any string.
Asynchronous Invocation
Using asynchronous invocations keeps your threads free to run other code while waiting for the server’s response. Ice for C++ has 2 different APIs for making asynchronous invocations:
// Future based API
future<string> futureGreeting = greeter->greetAsync("alice"); // Send the request.
// Free to run other code in between...
greeting = futureGreeting.get(); // Block until a respond is received.
cout << greeting << endl;
// Callback based API
promise<void> promise;
greeter->greetAsync(
"bob",
[&promise](string_view greeting) // response callback
{
cout << greeting << endl;
promise.set_value();
},
[&promise](std::exception_ptr exceptionPtr) // exception callback
{ promise.set_exception(exceptionPtr); });
// Wait for the response/exception callback to be called.
promise.get_future().get();