Writing a Greeter Client
9 min read
5 min read
6 min read
6 min read
6 min read
5 min read
6 min read
5 min read
5 min read
This page provides a step-by-step guide to writing the client-side of our C++ Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository. The code below is lightly simplified: it leaves out the demo’s error-reporting helper, and it passes fixed names to greet where the demo passes the name of the logged-in user.
Compile Slice File with Slice Compiler
The first step when writing a C++ application with Ice is to compile the Slice definitions for this application with the Slice to C++ compiler (slice2cpp).
Here, we compile the Greeter.ice Slice file we wrote earlier:
slice2cpp Greeter.iceThis produces two files: a header file, Greeter.h, and a C++ source file, Greeter.cpp. The header file provides the GreeterPrx class we instantiate in the code below, and Greeter.cpp is compiled into the client like any other source file. See Using the Slice Compiler for the options slice2cpp accepts.
In a real project you don’t run slice2cpp by hand. We recommend that you include this Slice compilation step in your build project, like we demonstrate for the C++ demo programs.
Client Implementation
The structure of our client is going to look like:
#include "Greeter.h"
#include <Ice/Ice.h>#include <future>#include <iostream>
using namespace std;
intmain(int argc, char* argv[]){ // ...}Before anything else, we need to include a few header files:
Greeter.h: The header file that the Slice compiler generated fromGreeter.ice.Ice/Ice.h: This header provides definitions that are necessary for accessing the Ice runtime.future,iostream: Standard library headers we use in this client.
Then we get to the interesting part: the main function which will run the client logic. This logic can be broken down into four pieces:
1. Create a Communicator
First, we create a Communicator with Ice::initialize:
Ice::CommunicatorPtr communicator = Ice::initialize(argc, argv);The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
When we no longer need a communicator, we must call destroy on this communicator. This destruction closes network connections and performs other important cleanups. An easy way to do this is by placing the communicator in a CommunicatorHolder. When the holder goes out of scope, its destructor calls destroy on the communicator:
Ice::CommunicatorHolder communicatorHolder{communicator};2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide functions to call operations on those objects.
We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:
VisitorCenter::GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};The constructor accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named greeter and can be reached via tcp on localhost on port 4061. If you run the server on another computer, replace localhost with that computer’s hostname or IP address.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and to print the greeting:
string greeting = greeter.greet("alice");cout << greeting << endl;The greet function does all the heavy lifting for us: the proxy creates a request with the name string, the communicator establishes a connection to localhost:4061, and the request is sent over it. When a response is received, the proxy will unmarshal its payload and finally return a string (the greeting).
Here, we called the synchronous version of greet, which means this function call will block until the response is received. And don’t let the simplicity of the syntax fool you: this is a remote call which will be much slower than a local call!
You can instead call greet asynchronously, with one of the two greetAsync overloads on the generated GreeterPrx class. The simpler overload returns a future:
future<string> futureGreeting = greeter.greetAsync("bob"); // Start the invocation.
// Wait for the response.greeting = futureGreeting.get();cout << greeting << endl;greetAsync starts the invocation and returns a future, so the client can do other work before it needs the greeting. Calling get on the future then blocks until the response arrives.
The other overload accepts callback functions instead of returning a future. The communicator calls these callbacks when it receives the response, or when it delivers an exception:
promise<void> promise;greeter.greetAsync( "carol", [&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();The callback overload is more flexible: you decide what runs when the response arrives, whereas a std::future gives you nowhere to attach a continuation, so the only thing you can do with it is wait. That flexibility costs you a fair amount of extra code, as you can see above.
Asynchronous invocations are more semantically correct for remote calls, and they alert readers to the potential delays inherent to these calls. In C++ though, they’re significantly more complicated to write, so the best invocation syntax depends on your situation.
4. Cleanup
The final step is the end of our main function. At this point, the CommunicatorHolder calls destroy on the communicator, and then our application exits.
return 0;Running the Client
After building the client (see the demo’s README for instructions), running it is as simple as running any other executable:
Linux and macOS:
./build/clientWindows:
build\clientWith a server running, the client prints one greeting per invocation and exits:
Hello, alice!Hello, bob!Hello, carol!Our client doesn’t catch exceptions, to keep it short. If you run it without a server, the greet call fails with Ice::ConnectionRefusedException and the process terminates on an unhandled exception. A real client would catch Ice::LocalException around its invocations.
This page presents a step-by-step guide to writing the client-side of our C# Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Compile Slice File with Slice Compiler
The first step when writing a C# application with Ice is to compile the Slice definitions for this application with the Slice to C# compiler (slice2cs).
Here we compile the Greeter.ice Slice file we wrote earlier. We recommend that you include this Slice compilation step in your build project, like we demonstrate for the C# demo programs.
This will generate a single file named Greeter.cs which provides APIs that we’ll call in our client code, so it’s essential to generate this file at the beginning of the development process.
Client Implementation
Since all the code generated by slice2cs was generated into the VisitorCenter namespace, we start with a using directive to let us reference it without qualification:
using VisitorCenter;Then the rest of the client logic can be broken down into four pieces:
1. Create a Communicator
We create a Communicator using its constructor:
await using var communicator = new Ice.Communicator(ref args);The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
It is important to make sure that your communicator is properly disposed when no longer needed. This ensures that network connections are gracefully closed, threads are joined, and other important clean-up occurs. The easiest way to do this is with an await using like we do here.
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by calling createProxy on the GreeterPrxHelper class generated by the Slice compiler. This returns a new instance of GreeterPrx (a Greeter proxy):
GreeterPrx greeter = GreeterPrxHelper.createProxy( communicator, "greeter:tcp -h localhost -p 4061");createProxy accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached via tcp on localhost on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and to print the greeting:
string greeting = await greeter.GreetAsync(Environment.UserName);Console.WriteLine(greeting);The GreetAsync method does all the heavy lifting for us: the proxy creates a request with the username string, the communicator establishes a connection to localhost:4061, and the request is sent over it. When a response is received, the proxy will unmarshal its payload and finally return a string (the greeting).
Using async/await for this invocation offers a couple advantages:
- The calling thread can continue doing other work while
GreetAsyncwaits for I/O. - The
awaitkeyword signals to the reader thatGreetAsyncis a remote call that may take time to complete.
4. Cleanup
Finally, at the end of our logic, our communicator goes out of scope and is disposed (because we used await using), and then our application exits.
Running the Client
After building the client (see the demo’s README for instructions), you can run it with dotnet:
cd Clientdotnet runThis page presents a step-by-step guide to writing the client-side of our Java Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Client Implementation
The structure of our client is going to look like:
package com.example.ice.greeter.client;
import com.example.visitorcenter.GreeterPrx;import com.zeroc.Ice.Communicator;import com.zeroc.Ice.Util;
import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;
class Client { public static void main(String[] args) { // ... }}Before anything else, we import the following packages:
com.example.visitorcenter: The package that the Slice compiler (slice2java) generated fromGreeter.ice.com.zeroc.Ice: This package provides types that are necessary for accessing the Ice runtime.java.util.concurrent: Standard library types we use in this client.
Then we get to the interesting part: the main method which runs the client logic. This logic can be broken down into four pieces:
1. Create a Communicator
First we create a Communicator using its constructor:
try (Communicator communicator = new Communicator(args)) { // ... client code}The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
It is important to make sure that your communicator is properly closed when no longer needed. This ensures that network connections are gracefully closed, threads are joined, and other important clean-up occurs. The easiest way to do this is with a try-with-resources statement like we do here.
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by calling createProxy on the GreeterPrx interface generated by the Slice compiler. This returns a new instance of type GreeterPrx (a Greeter proxy):
var greeter = GreeterPrx.createProxy( communicator, "greeter:tcp -h localhost -p 4061");createProxy accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached via tcp on localhost on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and to print the greeting:
String greeting = greeter.greet(System.getProperty("user.name"));System.out.println(greeting);The greet method does all the heavy lifting for us: the proxy creates a request with the username string, the communicator establishes a connection to localhost:4061, and the request is sent over it. When a response is received, the proxy will unmarshal its payload and finally return a string (the greeting).
Here, we called the synchronous version of greet, which means this method call will block until the response is received. And don’t let the simplicity of the syntax fool you: this is a remote call which will be much slower than a local call!
You can instead class greet asynchronously with greetAsync on our proxy like this:
CompletableFuture<String> futureGreeting = greeter.greetAsync("alice");
try { greeting = futureGreeting.get(); System.out.println(greeting);} catch (InterruptedException | ExecutionException e) { System.out.println("Could not get greeting: " + e.getMessage());}With the asynchronous version, the call doesn’t block. Instead the method returns a CompletableFuture. Calling get on this future waits for the invocation to complete and returns its result, as shown above.
Asynchronous invocations are more semantically correct for remote calls, and they alert readers to the potential delays inherent to these calls. But in Java they’re slightly more complicated to write… as a result, the best invocation syntax depends on your situation.
4. Cleanup
The final step is the end of our main method. At this point, our communicator goes out of scope and it is closed automatically (because we used a try-with-resources statement), and then our application exits.
Running the Client
After building the client (see the demo’s README for instructions), you can run it with the launcher script that the build generates:
./client/build/install/client/bin/clientThis page presents a step-by-step guide to writing the client-side of our TypeScript Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Compile Slice File with Slice Compiler
The first step when writing a TypeScript application with Ice is to compile the Slice definitions for this application with the Slice to JavaScript compiler (slice2js).
Here, we compile the Greeter.ice Slice file created earlier. We recommend including this compilation step directly in your project’s build process, as demonstrated in the TypeScript demo programs.
The Slice compiler generates two files from Greeter.ice: a TypeScript declaration file, Greeter.d.ts, and a JavaScript module, Greeter.js. The declaration file provides the APIs that our client code will call, so generating it is an essential first step in the development process.
Client Implementation
The structure of our client is going to look like:
import { Ice } from "@zeroc/ice";import { VisitorCenter } from "./Greeter.js";import process from "node:process";
...Before anything else, we need to import a few modules:
Iceis imported from the@zeroc/icepackage and gives us access to the Ice runtime.VisitorCenteris imported from the generatedGreeter.jsmodule, produced by theslice2jscompiler.processis imported from Node’s built-in process module and provides access to command-line arguments.
Then we get to the interesting part: the client logic. We can break this logic down into four pieces:
1. Create a Communicator
First, we create a Communicator using its constructor:
await using communicator = new Ice.Communicator(process.argv);The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
It is important to make sure that your communicator is properly destroyed when no longer needed. This ensures that network connections are gracefully closed, and other important clean-up occurs. The easiest way to do this is with an await using like we do here.
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:
const greeter = new VisitorCenter.GreeterPrx( communicator, "greeter:tcp -h hello.zeroc.com -p 4061");The constructor accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached via tcp on hello.zeroc.com on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and to print the greeting:
const username = ...const greeting = await greeter.greet(username);console.log(greeting);The greet method does all the heavy lifting for us: the proxy creates a request with the username string, the communicator establishes a connection to hello.zeroc.com:4061, and the request is sent over it. When a response is received, the proxy will unmarshal its payload and finally return a string (the greeting).
Note that greet returns a Promise that we await. This allows the JavaScript event loop to continue running other tasks while the invocation completes.
4. Cleanup
Finally, at the end of our logic, our communicator goes out of scope and it is disposed automatically (because we used await using), and then our application exits.
Running the Client
After building the client (see the demo’s README for instructions), you can run it with:
node client.jsThis page presents a step-by-step guide to writing the client-side of our MATLAB Greeter application.
This client is straightforward: it creates a proxy to a remote object that implements the Greeter interface, and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Compile Slice File with Slice Compiler
The first step when writing a MATLAB application with Ice is to compile the Slice definitions for this application with the Slice to MATLAB compiler (slice2matlab).
For our client application, we call slice2matlab on the Greeter.ice Slice file we wrote earlier:
slice2matlab Greeter.iceThis command generates the file +visitorcenter/GreeterPrx.m, which contains the proxy class used in our client code below.
Client Function
Our client is a small MATLAB function that loads the ice library:
function client(args) arguments (Repeating) args (1, :) char end
if ~libisloaded('ice') loadlibrary('ice', @iceproto); end ...endThe remainder of this function can be broken down into four pieces:
1. Create a Communicator
First, we create a Communicator using its constructor:
communicator = Ice.Communicator(args);The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
When we no longer need a communicator, we must call destroy on this communicator. This destruction closes network connections and performs other important cleanups. We use the onCleanup function to schedule the call to the destroy method:
cleanup = onCleanup(@() communicator.destroy());2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:
greeter = visitorcenter.GreeterPrx(communicator, ... 'greeter:tcp -h hello.zeroc.com -p 4061');The constructor accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says the target Ice object is named “greeter” and can be reached via tcp on hello.zeroc.com, on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy, and print the greeting:
greeting = greeter.greet('alice');fprintf('%s\n', greeting);The greet method does all the heavy lifting for us: the proxy creates a request with the user name, the communicator establishes a connection to hello.zeroc.com:4061, and the request is sent over this connection. When a response is received, the proxy unmarshals its payload and returns a string (the greeting).
Here, we called the synchronous version of greet, which means this call will block until the response is received. Don’t let the simplicity of the syntax fool you: this is a remote call which will be much slower than a local call!
You can instead call greet asynchronously with greetAsync on the generated GreeterPrx class:
futureGreeting = greeter.greetAsync('bob');
greeting = futureGreeting.fetchOutputs();fprintf('%s\n', greeting);With the asynchronous version, the method call doesn’t block. Instead, the method returns a future immediately and we later poll this future (with fetchOutputs) to wait until the result is available.
Asynchronous invocations are more semantically correct for remote calls, and they alert readers to the potential delays inherent to these calls. But they are more complicated to write and are of limited value in MATLAB since we have to poll the returned future – MATLAB does not support the more elegant async/await syntax. In practice, making synchronous invocations is simpler and more common in MATLAB.
4. Cleanup
The final step is the end of our client function. At this point, the onCleanup calls destroy on the communicator, and our function completes.
Running the Client
We can run client directly in the MATLAB console:
clientThis page presents a step-by-step guide to writing the client-side of our PHP Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface, and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Compile Slice File with Slice Compiler
The first step when writing a PHP application with Ice is to compile the Slice definitions for this application with the Slice to PHP compiler (slice2php).
Here, we compile the Greeter.ice Slice file we wrote earlier:
slice2php Greeter.iceThis produces a single PHP source file named Greeter.php. This file provides the proxy class that we’ll use in our client code, so it’s essential to generate this file at the beginning of the development process.
Client Script
Our client is a small PHP script that loads the Ice library and the Greeter.php file generated by the Slice compiler:
<?php
require_once 'Ice.php';require_once 'Greeter.php';The remainder of this script can be broken down into four pieces:
1. Create a Communicator
First we create a Communicator using Ice\initialize:
$communicator = Ice\initialize($argv);The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
It is important to make sure our communicator is properly destroyed (cleaned up) when it’s no longer needed. This ensures that network connections are gracefully closed, threads and joined, and other important clean-up occurs. In PHP, the communicator is destroyed automatically at the end of the script.
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by calling createProxy on the GreeterPrxHelper class generated by the Slice compiler. This returns a new instance of a Greeter proxy.
$greeter = VisitorCenter\GreeterPrxHelper::createProxy( $communicator, 'greeter:tcp -h hello.zeroc.com -p 4061');createProxy accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached over tcp on hello.zeroc.com on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and to print the greeting:
$greeting = $greeter->greet("alice");echo "$greeting\n";The greet function does all the heavy lifting for us: the proxy creates a request with the user name (“Alice”), the communicator establishes a connection to hello.zeroc.com:4061 and sends the request over this connection. Later the communicator receives the response, the proxy unmarshals the response’s payload and finally, returns a string (the greeting).
Here, we make a synchronous invocation, which means this call blocks until the response is received. Ice for PHP supports only synchronous invocations; other languages support asynchronous invocations as well.
4. Cleanup
The final step is the end of our block. At this point, our communicator goes out of scope causing it to be destroyed, and then our script completes.
Running the Client
We can run this client script with PHP as follows:
php Client.phpThis page presents a step-by-step guide to writing the client-side of our Python Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository on GitHub.
Compile Slice File with Slice Compiler
The first step when writing a Python application with Ice is to compile its Slice definitions using the Slice to Python compiler (slice2py).
Here we compile the Greeter.ice Slice file we wrote earlier.
This compilation generates a Python package named VisitorCenter, which matches the Slice module name. Inside this package, you’ll find the generated Greeter module corresponding to the Greeter interface defined in Slice. This module provides the APIs that our client code will call, so generating it is an essential first step in the development process.
Client Implementation
The structure of our client is going to look like:
import asyncioimport sys
import Ice
# Slice module VisitorCenter in Greeter.ice maps to Python module VisitorCenter.import VisitorCenter...Before anything else, we need to import a few packages:
asyncioTo write async code usingasync/awaitsyntax.sysFor accessing the command line arguments.IceFor accessing the Ice runtime.VisitorCenterThe package generated byslice2pyfromGreeter.ice.
Then we get to the interesting part: The client logic. This logic can be broken down into four pieces:
1. Create a Communicator
First, we create a Communicator using its constructor:
async def main(): async with Ice.Communicator( sys.argv, eventLoop=asyncio.get_running_loop()) as communicator:The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
We define main as an async def function to build an asynchronous application. By passing the current asyncio event loop to Ice.initialize, we ensure that the communicator integrates with this loop for all asynchronous operations.
It is important to always destroy the communicator before exiting the application. This guarantees that network connections are properly closed and other clean-up tasks are performed. In an asynchronous application, the simplest way to achieve this is to use the async with statement, as shown above.
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide functions to call operations on those objects.
We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:
greeter = VisitorCenter.GreeterPrx(communicator, "greeter:tcp -h localhost -p 4061")The constructor accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached via tcp on localhost on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and then print the returned greeting:
username = ...greeting = await greeter.greetAsync(username);print(greeting);The greetAsync function does all the heavy lifting for us: the proxy creates a request with the username string, the communicator establishes a connection to localhost:4061, and the request is sent over it. When a response is received, the proxy unmarshals its payload and returns a string (the greeting).
Note that greetAsync returns an Awaitable object that we await. This allows the event loop thread to do other work while it is waiting for the invocation to complete.
4. Cleanup
Finally, at the end of our logic, our communicator goes out of scope and is destroyed automatically (because we used the async with statement), and then our application exits.
Running the Client
After building the client (see the demo’s README for instructions), you can run it with:
uv run main.pyThis page presents a step-by-step guide to writing the client-side of our Ruby Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface, and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Compile Slice File with Slice Compiler
The first step when writing a Ruby application with Ice is to compile the Slice definitions for this application with the Slice to Ruby compiler (slice2rb).
Here, we compile the Greeter.ice Slice file we wrote earlier in a terminal:
slice2rb Greeter.iceThis compilation produces a Ruby source file, Greeter.rb. This file provides the proxy class that we’ll use in our client code, so it’s essential to generate this file at the beginning of the development process.
Client Script
Our client is a small Ruby script that loads the Ice library and the Greeter.rb file generated by the Slice compiler:
#!/usr/bin/env ruby
require 'Ice'
require_relative 'Greeter.rb'The remainder of this script can be broken down into four pieces:
1. Create a Communicator
First, we create a Communicator using Ice::initialize:
Ice::initialize(ARGV) do |communicator| ...endThe communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
When we no longer need a communicator, we must call destroy on this communicator. This destruction closes network connections and performs other important cleanups. We use Ruby’s “execute-around” pattern to destroy the communicator at the end of the block.
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:
greeter = VisitorCenter::GreeterPrx.new( communicator, "greeter:tcp -h hello.zeroc.com -p 4061")The constructor accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says the target Ice object is named “greeter” and can be reached via tcp on hello.zeroc.com, on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy, and print the greeting:
greeting = greeter.greet("alice")puts greetingThe greet method does all the heavy lifting for us: the proxy creates a request with the user name (“alice”), the communicator establishes a connection to hello.zeroc.com:4061 and sends the request over this connection. Later, the communicator receives the response, the proxy unmarshals the response’s payload and returns a string (the greeting).
Here, we make a synchronous invocation, which means this call blocks until we receive the greeting. Ice for Ruby supports only synchronous invocations; other languages support asynchronous invocations as well.
4. Cleanup
The final step is the end of our block. At this point the communicator is destroyed, and our script completes.
Running the Client
We can now run this script in the Ruby console:
ruby client.rbThis page presents a step-by-step guide to writing the client-side of our Swift Greeter application.
This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.
You can find the complete source code for this example in the ice-demos repository.
Compile Slice File with Slice Compiler
To write a Swift application with Ice, first configure SwiftPM to compile the Slice definitions. The Ice package contains a plugin for this purpose, CompileSlice, which can be added to the executableTarget in Package.swift.
.executableTarget( name: "Client", dependencies: [.product(name: "Ice", package: "ice")], plugins: [.plugin(name: "CompileSlice", package: "ice")]),The CompileSlice plugin compiles the .ice files in the target's sources, and the files listed in a slice-plugin.json file in the target's sources. In this example, Greeter.ice is in the slice directory at the root of the package, outside Sources/Client, so Sources/Client/slice-plugin.json lists it, with a path relative to the directory that contains slice-plugin.json:
{ "sources": ["../../slice/Greeter.ice"]}The plugin compiles Greeter.ice into Greeter.swift and adds it as a source file of the Client target. The generated code provides the APIs that we’ll call in our client code, so it’s an essential step of the development process.
Client Implementation
To get started we first need to import a few dependencies.
import Foundationimport IceFoundation- Used to obtain the username.Ice- For accessing the Ice runtime
Next is the client logic, which can be broken down into four pieces:
1. Create a Communicator
First, we create a Communicator with Ice.initialize:
var args = CommandLine.argumentslet communicator = try Ice.initialize(&args)
defer { communicator.destroy()}The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).
Our client, like most Ice applications, creates a single communicator.
It is important to properly clean up the communicator when done, which we do with the defer block that calls destroy().
2. Create a Greeter Proxy
Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide methods to call operations on those objects.
We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:
let greeter = try makeProxy( communicator: communicator, proxyString: "greeter:tcp -h localhost -p 4061", type: GreeterPrx.self)The makeProxy function accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached via tcp on localhost on port 4061.
3. Make an Invocation
The third step is to call greet on the remote Ice object using our proxy and to print the greeting:
let greeting = try await greeter.greet(NSUserName())print(greeting)The greet method does all the heavy lifting for us: the proxy creates a request with the username, the communicator establishes a connection to localhost:4061, and the request is sent over it. When a response is received, the proxy unmarshals its payload and returns a string (the greeting).
Using async/await for this invocation offers several advantages:
- The calling thread can continue doing other work while
greetwaits for I/O. - The
awaitkeyword signals to the reader thatgreetis a remote operation and may take time to complete.
4. Cleanup
When the main function exits, the defer statement destroys the communicator.
Running the Client
To run the client, execute the following command (the executable will be compiled if necessary):
swift run Client