An Ice connection normally allows requests to flow in only one direction. If an application’s design requires the server to make callbacks to a client, the server normally establishes a new connection to that client in order to send callback requests, as shown in
Figure 33.1.
Unfortunately, network restrictions often prevent a server from being able to create a separate connection to the client, such as when the client resides behind a firewall as shown in
Figure 33.2.
For situations such as these, a bidirectional connection offers a solution. Requests may flow in both directions over a bidirectional connection, enabling a server to send callback requests to a client over the client’s existing connection to the server.
There are two ways to make use of a bidirectional connection. First, you can use a Glacier2 router, in which case bidirectional connections are used automatically. If you do not require the functionality offered by Glacier2 or you do not want an intermediary service between clients and servers, you can configure bidirectional connections manually.
5.
Invoke setAdapter on the connection, passing the callback object adapter. This associates an object adapter with the connection and enables callback requests to be dispatched.
Ice::ObjectAdapterPtr adapter =
communicator‑>createObjectAdapter("CallbackAdapter");
Ice::Identity ident;
ident.name = IceUtil::generateUUID();
ident.category = "";
CallbackPtr cb = new CallbackI;
adapter‑>add(cb, ident);
adapter‑>activate();
proxy‑>ice_getConnection()‑>setAdapter(adapter);
proxy‑>addClient(ident);
The last step may seem unusual because a client would typically pass a proxy to the server, not just an identity. For example, you might be tempted to give the proxy returned by the adapter operation
add to the server, but this would not have the desired effect: if the callback object adapter is configured with endpoints, the server would attempt to establish a separate connection to one of those endpoints, which defeats the purpose of a bidirectional connection. It is just as likely that the callback object adapter has no endpoints, in which case the proxy is of no use to the server.
Similarly, you might try invoking createProxy on the connection to obtain a proxy that the server can use for callbacks. This too would fail, because the proxy returned by the connection is for local use only and cannot be used by another process.
void addClient(const Ice::Identity& ident,
const Ice::Current& curr)
{
CallbackPrx client =
CallbackPrx::uncheckedCast(curr.con‑>createProxy(ident));
client‑>notify();
}
The proxy returned by a connection’s createProxy operation is called a
fixed proxy. It can only be used in the server process and cannot be marshaled or stringified by
proxyToString; attempts to do so raise
FixedProxyException.
The fixed proxy is bound to the connection that created it, and ceases to work once that connection is closed. If the connection is closed prematurely, either by active connection management or by explicit action on the part of the application (see
Section 33.4), the server can no longer make callback requests using that proxy. Any attempt to use the proxy again usually results in a
CloseConnectionException.
Many aspects of a fixed proxy cannot be changed. For example, it is not possible to change the proxy’s endpoints or timeout. Attempting to invoke a method such as
ice_timeout on a fixed proxy raises
FixedProxyException.
The Ice run time normally creates two thread pools for processing network traffic on connections: the client thread pool manages outgoing connections and the server thread pool manages incoming connections. All of the object adapters in a server share the same thread pool by default, but an object adapter can also be configured to have its own thread pool. The default size of the client and server thread pools is one.
The client thread pool is normally waiting for the replies to pending requests. When a client configures an outgoing connection for bidirectional requests, the client thread pool also becomes responsible for processing requests received over that connection.
Similarly, the server thread pool normally dispatches requests from clients. If a server uses a connection to send callback requests, then the server thread pool must also process the replies to those requests.
You must increase the size of the appropriate thread pool if you need the ability to dispatch multiple requests in parallel, or if you need to make nested twoway invocations. For example, a client that receives a callback request over a bidirectional connection and makes nested invocations must increase the size of the
client thread pool. See
Section 28.9.5 for more information on nested invocations, and
Section 28.9 for details on the Ice threading model.