From a user's perspective, a client is usually some interactive application program that connects to a server, such as a web browser or email client. Similarly, a server is usually seen as some program that runs on a remote machine and serves one or more clients, such as a web or email server. While this view is valid for simple request-response systems, in the context of Ice, it is too loose.
An Ice client is any program that invokes an operation on an Ice object. In other words, clients are active entities that issue requests for service to servers. Conversely, an Ice server is any program that implements an operation. In other words, servers are passive entities that provide service in response to operation invocations from clients.
On occasion, this definition of Ice client and server does not neatly line up with the more loose view. For example, IceStorm is generally thought of as a server. However, when IceStorm delivers an event to a subscriber, it is IceStorm that invokes an operation on the object registered for a topic, and it is the subscriber that implements the operation. In other words, a "client" program, such as a stock ticker that subscribes to stock updates distributed by IceStorm, is really an Ice server, and IceStorm is an Ice client.
The preceding example illustrates that the terms client and server really refer to the role that is played by each. Moreover, that role applies only within the context of a particular operation invocation. The client role is played by the invoking end, and the server role is played by the responding end.
A pure Ice client is a program that only ever invokes operations and never responds to them, and a pure Ice server is a program that only ever responds to operation invocations and never makes invocations of its own. (Any program that does not create an object adapter is guaranteed to be a pure client.) However, many Ice applications are not pure clients or servers. In particular, a server that makes an invocation on an Ice proxy in order to implement some operation (typically, on a helper object in some other server) is no longer a pure server. Instead, it acts as a server to the original client, but also acts as a client to the server implementing the helper object.
Of course, because Ice is location transparent, the server has no idea where the helper object is implemented. In fact, the helper object may be implemented by the server itself, in which case the server ends up being both its own client and server. Alternatively, it is possible for the helper object to be implemented by the original client. If so, the helper object is known as a callback object. Callback objects are no different from ordinary objects; it just so happens that callback objects are implemented by clients and that the server often (but not always) invokes an operation on a callback object as part of executing an operation invoked by the client that provided the callback object; such invocations are known as nested callbacks.
From an Ice perspective, there is nothing special about callbacks. They are simply operation invocations like any other invocation. (However, for nested callbacks, the threading model is important. How deeply callbacks can be nested depends on the size of the client- and server-side thread pools. You can look at the demo in
demo/Ice/callback if you are interested in experimenting with nested callbacks.)
In summary, an Ice client is any program that invokes an operation, and an Ice server is any program that responds to an operation invocation. If a program both invokes operations and responds to them, it is both a client and a server.