Bidirectional Connections

6 min read

6 min read

6 min read

5 min read

6 min read

6 min read

By default, requests flow over an Ice connection in one direction: from the client that establishes the connection to the server that accepts it. If the server needs to call back the client, it usually establishes a new connection to that client to send these callback requests, as shown below:

The client opens a connection to send requests to the server. The server opens a separate connection to send callbacks to the client.

Callbacks in an open network.

A firewall in front of the client, however, typically blocks this connection:

The client can open a connection to the server through its firewall, but the firewall blocks a separate incoming callback connection from the server.

A bidirectional connection solves this: requests flow over it in both directions, so the server can send callbacks over the connection the client established.

A client needs to perform the following steps in order to configure a bidirectional connection:

  1. Create an object adapter to receive callback requests.
  2. Set this object adapter as the default object adapter on the communicator. The communicator associates its default object adapter with the outgoing connections it creates afterward.
  3. Register the callback object or objects with the object adapter.

The code below illustrates these steps:

C++
Ice::ObjectAdapterPtr adapter = communicator->createObjectAdapter("");
communicator->setDefaultObjectAdapter(adapter);
auto mockAlarmClock = make_shared<Client::MockAlarmClock>();
adapter->add(mockAlarmClock, Ice::stringToIdentity("alarmClock"));
C#
Ice.ObjectAdapter adapter = communicator.createObjectAdapter("");
communicator.setDefaultObjectAdapter(adapter);
var mockAlarmClock = new Client.MockAlarmClock();
adapter.add(mockAlarmClock, new Ice.Identity { name = "alarmClock" });
Java
ObjectAdapter adapter = communicator.createObjectAdapter("");
communicator.setDefaultObjectAdapter(adapter);
var mockAlarmClock = new MockAlarmClock();
adapter.add(mockAlarmClock, new Identity("alarmClock", ""));
TypeScript
const adapter = await communicator.createObjectAdapter("");
communicator.setDefaultObjectAdapter(adapter);
const mockAlarmClock = new MockAlarmClock();
adapter.add(mockAlarmClock, new Ice.Identity("alarmClock"));
Python
adapter = communicator.createObjectAdapter("")
communicator.setDefaultObjectAdapter(adapter)
mockAlarmClock = MockAlarmClock()
adapter.add(mockAlarmClock, Ice.Identity(name="alarmClock"))
Swift
let adapter = try communicator.createObjectAdapter("")
communicator.setDefaultObjectAdapter(adapter)
let mockAlarmClock = MockAlarmClock()
try adapter.add(servant: mockAlarmClock, id: Ice.Identity(name: "alarmClock"))

The callback object (mockAlarmClock in the code above) will handle incoming requests for identity alarmClock.

To set an object adapter on a connection created before you set the default object adapter, or to give a connection a different object adapter, call setAdapter on this outgoing connection, for example the connection returned by a proxy's ice_getConnection. A connection without an object adapter rejects incoming requests with ObjectNotExistException.

These two approaches differ in scope. With a default object adapter, every outgoing connection that the communicator creates afterward dispatches the requests it receives to this adapter, so any server the client connects to can send callbacks to it. With setAdapter, only the connections you choose accept callbacks.

A server sends callbacks over the incoming connection with a fixed proxy, a proxy bound to this connection. The connection is the con member of the Current object passed to the operation implementation. You create a fixed proxy in one of two ways:

  • Call createProxy on the connection with the identity of the callback object, as the code below does.
  • Call ice_fixed with the connection on a proxy that the client sent, for example a callback proxy passed as an operation argument. The fixed proxy keeps the identity, facet, and other settings of this proxy.
C++
void
Server::BidirWakeUpService::wakeMeUp(
int64_t timeStamp,
const Ice::Current& current)
{
// The connection from the client to the server.
Ice::ConnectionPtr connection = current.con;
if (!connection)
{
// Unexpected colloc call.
throw std::invalid_argument{...};
}
// alarmClock is a fixed proxy.
auto alarmClock = connection->createProxy<AlarmClockPrx>(
Ice::stringToIdentity("alarmClock"));
...
}

The Ice bidir demo provides a complete client and server in C++ that communicate over a bidirectional connection.

C#
internal class BidirWakeUpService : WakeUpServiceDisp_
{
public override void WakeMeUp(long timeStamp, Ice.Current current)
{
// The connection from the client to the server.
Ice.Connection? connection = current.con;
if (connection is null)
{
// Unexpected colloc call.
throw new NotImplementedException("...");
}
// alarmClock is a fixed proxy.
AlarmClockPrx alarmClock = AlarmClockPrxHelper.uncheckedCast(
connection.createProxy(new Ice.Identity { name = "alarmClock" }));
...

The Ice bidir demo provides a complete client and server in C# that communicate over a bidirectional connection.

Java
class BidirWakeUpService implements WakeUpService {
@Override
public void wakeMeUp(long timeStamp, Current current)
throws FeatureNotSupportedException {
// The connection from the client to the server.
Connection connection = current.con;
if (connection == null) {
// Unexpected colloc call.
throw new FeatureNotSupportedException("...");
}
// alarmClock is a fixed proxy.
AlarmClockPrx alarmClock = AlarmClockPrx.uncheckedCast(
connection.createProxy(new Identity("alarmClock", "")));
...

The Ice bidir demo provides a complete client and server in Java that communicate over a bidirectional connection.

The Ice bidir demo provides a complete JavaScript client that receives callbacks over a bidirectional connection, from a bidir demo server written in another language.

Python
class BidirWakeUpService(WakeUpService):
def wakeMeUp(self, timeStamp: int, current: Ice.Current) -> None:
assert current.con is not None, "Unexpected colloc call."
# alarmClock is a fixed proxy.
alarmClock = AlarmClockPrx.uncheckedCast(
current.con.createProxy(Ice.Identity(name="alarmClock")))
...

The Ice bidir demo provides a complete client and server in Python that communicate over a bidirectional connection.

Swift
struct BidirWakeUpService: WakeUpService {
func wakeMeUp(timeStamp: Int64, current: Ice.Current) throws {
// The connection from the client to the server.
guard let connection = current.con else {
// Unexpected colloc call.
throw Ice.FeatureNotSupportedException("...")
}
// alarmClock is a fixed proxy.
let alarmClock = try uncheckedCast(
prx: connection.createProxy(
Ice.Identity(name: "alarmClock")), type: AlarmClockPrx.self)
...

The Ice bidir demo provides a complete client and server in Swift that communicate over a bidirectional connection.

A fixed proxy ceases to work once its connection is closed. Ice does not retry an invocation on a fixed proxy: once the connection is closed, invocations on the proxy fail with the exception that closed the connection, such as CloseConnectionException when the client closed it gracefully.

Bidirectional connections have certain limitations:

  • They can only be configured for connection-oriented transports such as TCP and SSL.
  • A fixed proxy is bound to an existing connection: you cannot change the settings that select a connection, such as its endpoints or its router, and you cannot marshal it or convert it to proxy properties. You can still change its invocation mode, for example to oneway.

Over a bidirectional connection, each side sends requests and dispatches the requests it receives, but the thread pool does not depend on the direction of a request: the client thread pool services an outgoing connection, even when the callback object adapter has its own thread pool, and the thread pool of the object adapter that accepted the connection services an incoming connection. This pool dispatches the requests and processes the replies received over the connection; see Thread Pools.