Archived

This forum has been archived. Please start a new discussion on GitHub.

Object Discovery Questions

I'm trying to create a distributed object system where a client can call a method on other clients' objects. For example take the following .ice file
module Example {
interface Client;
["java:type:java.util.Vector"] sequence<Client*> ClientsArray;
interface Client {
	void onClientsAdded(ClientsArray clients);
	void printMessage(string message);
};
interface Server {
	void registerClient(Client* proxy);
};
};

For each client that starts I would like it to register itself with the central server, and then have that server broadcast the existence of that client to all the other clients. In this example, when a client spins up, it calls the method "registerClient" on the Server proxy. The server then saves the passed proxy in an array and calls the method "onClientsAdded" for each other client that has already registered. Below is an example of ServerI.java
public class ServerI extends _ServerDisp {
	private Vector<ClientPrx> _clients = new Vector<ClientPrx>();
	@Override
	public void registerClient(ClientPrx proxy, Current __current) {
		for(ClientPrx p : _clients) {
			p.onClientsAdded(_clients);
		}
		_clients.add(proxy);
	}
}

The idea is that I would like any client to be able to call the method "printMessage" on any other client by invoking that method on a proxy. See the code for ClientI below
public class ClientI extends _ClientDisp {
	private List<ClientPrx> _clients;
	@Override
	public void onClientsAdded(List<ClientPrx> clients, Current __current) {
		_clients = clients;
	}
	@Override
	public void printMessage(String message, Current __current) {
		System.out.println(message);
	}
	public void sendRandomClientMessage(String message) {
		int index = (int) Math.round(Math.random() * _clients.size() - 1);
		_clients.get(index).printMessage(message);
	}
}

Obviously, this doesn't work. So my question is: how do I manage remote object discovery, and once an object is discovered how do I receive updates on that object's state, and also invoke methods on that object without writing custom server code that marshals these method invocations?

Coming from the MMO world, where object discovery and visibility is built-in functionality, Im not sure how to replicate this with Ice.

Thanks for any help in advance.

Comments

  • benoit
    benoit Rennes, France
    Hi Jonathan,

    Your example should work if the clients are configured to listen on endpoints and publish endpoints in the proxy they register with the server. However, if the clients are behind a firewall this won't work since they won't be able to accept network connections from the "outside".

    What are the requirements for your application environment?

    If your clients are behind firewalls, Glacier2 and some custom "relay" code in your server could be a solution. In short, each client would establish a session with Glacier2 and each client session in the server would support a "relay" servant that would relay invocations from the session's client to other clients. To write this relay, the simple solution is to write code for each method to relay. Another more maintainable solution would be to write a servant locator and blobject servant (with this solution, no need to write more code when you want to support other methods). See the Ice manual for more information on Ice servant locators and the blobject API.

    Another option to avoid a central server which relay invocations between clients would be to use hole punching , however Ice doesn't support it at this time. If you have a commercial need for this, please contact us at info@zeroc.com.

    Cheers,
    Benoit.
  • Thanks for the quick reply. It's much appreciated.

    Our environment does not have the restrictions of a typical production network. I'm evaluating Ice as a potential candidate to distribute computation required to operate a robot among any number of different computers, and as a comunication network between robots. Therefor, there are no firewalls.
    if the clients are configured to listen on endpoints and publish endpoints in the proxy they register with the server.

    Can you go into a bit more detail on this one? How do you configure the clients to do this with the proxy they register with the server?
  • benoit
    benoit Rennes, France
    Hi,

    How do you create the proxy for the Example::Client interface?

    I assume your client has an object adapter where you register the servant and create the proxy. If this object adapter already configures endpoints, there's nothing additional to do: the proxy contains the endpoints of the client object adapter and if you pass the proxy to other clients, those clients will be able to locate the Ice object.

    Object adapter endpoints are configured with the <adapter name>.Endpoints property. So if you created the object adapter with:
    communicator->createObjectAdapter("ClientAdapter");
    
    You can set the endpoints with the property:
    ClientAdapter.Endpoints=tcp -p 12345 -h localhost
    

    Note that you don't necessarily need to specify the port and/or hostname in the endpoints, if your clients always register itself with a server, it might be fine (and simpler) to listen on a system allocated port.

    Cheers,
    Benoit.
  • Thanks for the reply.

    I was actually hoping I wouldn't have to open more than one port. I'd like to have the server act as a message router, but have the actual routing of the message be transparent to all clients. I was briefly looking at Glacier2, but the overview emphasized dealing with firewalls so much, I wasn't sure if this was the right solution for me.

    I basically want to be able to pass client proxies around and just call methods on it and have the routing taken care of.

    Maybe I'm missing the point of Ice. What I'm really looking for is a distributed object system with object discovery and transparent method invokation.

    In my example, I'm want to create a proxy to send to the server, which will then distribute that proxy among the rest of the connected clients. On the client I try to create the proxy like this

    In ServerI
    //create the local client
    ClientI client = new ClientI();
    //create identity for this client
    Identity ident = new Identity();
    ident.name = java.util.UUID.randomUUID().toString();
    //create an adapter for two-way communication
    Ice.ObjectAdapter adapter = communicator().createObjectAdapter("");
    //connect the client to the identity
    adapter.add(client, ident);
    
    //create a proxy for this client from its identity
    ClientPrx proxy = ClientPrxHelper.checkedCast(
    	adapter.createProxy(ident)
    );
    //send this proxy to the server to register it
    server.registerClient(proxy);
    

    Unfortunately, I get an Ice.NoEndpointException thrown when I call adapter.createProxy. Would this be valid if I replaced
    Ice.ObjectAdapter adapter = communicator().createObjectAdapter("");
    

    with
    Ice.ObjectAdapter adapter = communicator().createObjectAdapterWithRouter("Client", someRouter);
    

    Would this allow me to pass that proxy to whatever client I wanted and have the router be able to route the message to the appropriate servant?
  • So I tried using glacier2 for client to client communication. Unfortunately, this did not work either. This is what I did.

    For each client I created a Client proxy like so. The Client ice definition looks like this
    interface Client;
    ["java:type:java.util.Vector"] sequence<Client*> ClientsArray;
    interface Client {
    	void onClientsAdded(ClientsArray clients);
    	void printMessage(string message);
    };
    

    The Main.java class extends Glacier2.Application
    public int runWithSession(String[] args) throws RestartSessionException {
    	Ice.Identity id = null;
    	ClientI client = null;
    	try {
    		id = createCallbackIdentity("client");
    		client = new ClientI();
    		ClientPrx clientPrx = ClientPrxHelper.uncheckedCast(
                        objectAdapter().add(client, id)
                    );
    		ServerPrxHelper.uncheckedCast(session()).registerClient(clientPrx);
    		communicator().waitForShutdown();
    	} 
    	catch (SessionNotExistException e) {
    		System.out.println("Session does not exist");
    	}
    	catch(Exception e) {
    		e.printStackTrace();
    	}
    	return 0;
    }
    

    in ServerI, which looks like this
    interface Server extends Glacier2::Session {
    	void registerClient(Client* proxy);
    };
    

    I tell every other client about this proxy. _clients is a static member of ServerI.
    public void registerClient(ClientPrx proxy, Current current) {
    	synchronized(_clients)
    	{
    		_clients.add(proxy);
                    for(ClientPrx client : _clients) {
            	       client.onClientsAdded(_clients);
                    }
    	}
    }
    

    Back in ClientI I want to broadcast a message to each other client like this
    public void onClientsAdded(List<ClientPrx> clients, Current __current) {
    	_clients = clients;
    	for(ClientPrx client : _clients) {
    		client.printMessage("Hello from " + this.clientId);
    	}
    }
    

    It turns out that a Client can send a printMessage to itself, but it cannot send to another Client through a proxy. When I attempt to do that I get this error
    Ice.ObjectNotExistException
        id.name = "client"
        id.category = "d9U?S3"fM&%2?0@#!/2/"
        facet = ""
        operation = "printMessage"
    	at IceInternal.Outgoing.invoke(Outgoing.java:147)
    	at Example._ServerDelM.registerClient(_ServerDelM.java:41)
    	at Example.ServerPrxHelper.registerClient(ServerPrxHelper.java:52)
    	at Example.ServerPrxHelper.registerClient(ServerPrxHelper.java:28)
    	at Client.runWithSession(Client.java:60)
    	at Glacier2.Application.doMain(Application.java:406)
    	at Glacier2.Application.doMain(Application.java:342)
    	at Ice.Application.main(Application.java:182)
    	at Ice.Application.main(Application.java:118)
    	at Client.main(Client.java:78)
    

    In the end, I'm not sure Ice is capable of doing the things I want it to do. Am I completely on the wrong track? I'd like to know if I need to look elsewhere for this functionality.

    Thank you.
  • benoit
    benoit Rennes, France
    Hi,
    golgobot wrote: »
    Thanks for the reply.
    I basically want to be able to pass client proxies around and just call methods on it and have the routing taken care of.

    Hmm, how do you expect the middleware platform to be able to figure how the routing should be done?
    Maybe I'm missing the point of Ice. What I'm really looking for is a distributed object system with object discovery and transparent method invokation.

    Ice provides remote method invocations (not transparent, remote method invocations shouldn't be treated as local operations in general). It also provides object discovery with the Ice location mechanism (implemented by the IceGrid registry). The communication occurs between 2 peers: the client invokes on a proxy which contains the endpoints of the server and the server dispatch the invocation to a servant. If you need some special routing to route requests through a middle-tier, you will need to implement the routing in this middle-tier (the server in your case).

    Using Glacier2 can also be an option but keep in mind that Glacier2 was primarily designed for client to server communications: its goal is to allow routing of requests from clients outside a corporate network to servers within the corporate network. It also enables the servers to callback on the clients using the same Ice connection that the client opened to the Glacier2 router (this way, there's no need for the client to allow incoming connection to receive requests).

    So for your use case you basically have 2 options:
    • Configure Glacier2 to allow connected clients to communicate with each others.
    • Implement the routing yourself in the server. This is of course more work and require more knowledge of some advanced Ice APIs and features (the blobject API and bi-directional connections for instance).

    You tried the first option but ran into an issue where the client raises Ice.ObjectNotExistException. I suspect this is caused by one of your client raising Ice.ObjectNotException when invoking printMessage on the ClientPrx proxy. You can verify this by wrapping the printMessage calls with a try/catch block:
    public void onClientsAdded(List<ClientPrx> clients, Current __current) {
    	_clients = clients;
    	for(ClientPrx client : _clients) {
                    try {
        		    client.printMessage("Hello from " + this.clientId);
                    } catch(Ice.ObjectNotExistException ex) {
                        System.err.println("invocation failed" + ex.toString());
                    }
    	}
    }
    

    It's failing because the client mistakenly "thinks" the object is hosted by the client and it's trying to invoke on it using collocation optimization. If you disable collocation optimization in your client, this exception should go away. You should also disable collocation optimization on the Glacier2 router for a similar reason. To disable collocation optimization, you should set the following:
    Ice.Default.CollocationOptimized=0
    

    in both the client and Glacier2 router configuration. This should in theory solve your issues and allow the clients to communicate with each other through the Glacier2 router.

    Cheers,
    Benoit.
  • benoit wrote: »
    Hmm, how do you expect the middleware platform to be able to figure how the routing should be done?

    There are a few ways to achieve this routing. The easiest is a centralized star configuration, the more robust being MxN configurations, of servers called Message Routers that implement a broadcast subscribe network. Their job is to keep track of who is listening to what. All messages are routed through them. In a system like this the idea of an endpoint becomes abstracted to just an ID, and the routers can derive the network path of a message base on the id.

    Btw, I got this working without Glacier. I have to create a physical endpoint on each client, but I don't have to specify the port, like you said
    Client.Endpoints=tcp
    

    It's pretty disturbing, though, that each client must open its own socket just to route a message from client to client. It's ok for the environment I'm working it, but still :p

    Thanks for all your help. If anyone is curious in more detail how this works, just ping this thread and I'd be happy to share.
  • benoit
    benoit Rennes, France
    Hi,

    I'm glad to hear you found a solution that works.

    Ice is a general purpose middleware, it isn't focused on a specific domain such as the ones you described. You can view Ice as a thin layer over sockets with an object model allowing a client and a server to communicate (possibly using different language mappings).

    IceStorm (which is built on top of Ice) provides a messaging service allowing N to M message distribution. You might want to take a look at it.

    An Ice client can only receive requests if it's listening on some endpoints or if it opened a connection to an Ice server and this same Ice server is using the established connection to send back requests to the client (we refer to this as "bi-directional" connections, see the manual for more information).

    If you have a very specific need for your messaging service, you can still most likely implement it with Ice with specific Slice interfaces. If you have a commercial need or if you are interested in consulting services regarding this, don't hesitate to contact us as sales@zeroc.com!

    Cheers,
    Benoit.