Ice is ZeroC’s opensource RPC framework which supports interoperability in multiple languages. With Ice framework, it takes care of server management, discovery, and serialization, so developer can concentrate on business logic.
When creating Ice application, the first step is create a slice file. This file sets up the contract/interface between client and server. In our hello world application, we use the following very simple Hello
interface.
module Demo {
interface Hello {
string SayHello(string username);
};
}di
The next step is to use ice tool slice2py
to compile the .ice
slice file. This generates codes required for both client and server application to use. To demonstrate how this is done, I have created a docker image, as docker is a great way to test and play software in an isolated environment without affecting existing development setup. I have built the image and published to dockerhub as well.
slice2py
generates Hello_ice.py
and Demo
module from Hello.ice
input:
$ docker run -it --rm mcai4gl2/ice-helloworld:latest /bin/bash
root@98884be4a9cb:/app# cd /dependencies
root@98884be4a9cb:/dependencies# ls -ltr
total 20
-rw-r--r-- 1 root root 91 Apr 17 08:27 environment.yml
-rw-r--r-- 1 root root 105 Apr 17 08:27 environment-dev.yml
-rw-r--r-- 1 root root 104 Apr 17 08:27 Hello.ice
-rw-r--r-- 1 root root 2668 Apr 17 08:30 Hello_ice.py
drwxr-xr-x 2 root root 4096 Apr 17 08:30 Demo
We can interactively connect to the docker image and run python -c 'import Demo'
to make sure everything is setup.
The server.py implements the Hello
code generated to provide concreate implementation of our demo interface:
import sys
import Ice
from Demo import Hello
class HelloServer(Hello):
def SayHello(self, input, current=None):
return f"Hello {input}"
if __name__ == "__main__":
communicator = Ice.initialize(sys.argv)
try:
adapter = communicator.createObjectAdapterWithEndpoints("HelloAdapter", "default -p 10000")
servant = HelloServer()
adapter.add(servant, communicator.stringToIdentity("hello"))
adapter.activate()
communicator.waitForShutdown()
finally:
communicator.destroy()
Here, we run our application on port 10000.
The client.py code to consume the server service is as follows:
import sys
import Ice
from Demo import HelloPrx
if __name__ == "__main__":
communicator = Ice.initialize(sys.argv)
try:
base = communicator.stringToProxy("hello:default -p 10000")
hello = HelloPrx.checkedCast(base)
if not hello:
raise RunTimeErrror("Invalid ice proxy")
result = hello.SayHello("World!")
print(f"Reply from server: {result}")
finally:
communicator.destroy()
To test the setup, we can run the docker image interactively with:
docker run -it --rm mcai4gl2/ice-helloworld:latest /bin/bash
root@6ecdd4ac9c63:/app# python server.py &
[1] 12
root@6ecdd4ac9c63:/app# python client.py
Reply from server: Hello World!
In this minimal example, we are leveraging Ice mainly for serialization. We are self-managing the service discovery and service placement. Leveraging Ice for IO can be especially useful in a multi languages project. As an example, in this Dockerfile, we have extended the helloworld application with C++ client but using the same python server implementation.
Instead of using slice2py
, we use slice2cpp
to generate required code. The client.cpp
is as follows:
#include <Ice/Ice.h>
#include <Hello.h>
using namespace std;
using namespace Demo;
int main(int argc, char* argv[])
{
int status = 0;
Ice::CommunicatorPtr ic;
try
{
ic = Ice::initialize(argc, argv);
Ice::ObjectPrx base = ic->stringToProxy("hello:default -p 10000");
HelloPrx hello = HelloPrx::checkedCast(base);
if (!hello)
{
throw "Invalid proxy";
}
string result = "";
result = hello->SayHello("test");
cout << "client's result: " << result << endl;
}
catch (const Ice::Exception& ex)
{
cerr << ex << endl;
status = 1;
}
catch (const char* msg)
{
cerr << msg << endl;
status = 1;
}
if (ic)
{
ic->destroy();
}
return status;
}
To test this setup, we can run the docker image interactively with:
docker run -it -v --rm mcai4gl2/ice-helloworld-cpp-client:latest /bin/bash
root@10ec86155d14:/app# python server.py &
[1] 11
root@10ec86155d14:/app# ./client
client's result: Hello test
root@10ec86155d14:/app#
Next time, we will extend this demo application to use IceGrid to manage both.
Last modified on 2022-04-17