PART - Pervasive Applications RunTime


Networking and messaging

On the lowest level, PART provides an API for setting up connections between game processes, which can then be used for data exchange. This API is based on the concept of point-to-point connections. That is, connections are established between pairs of processes. A process may set up as many connections to other processes as it likes, as long as it doesn’t try to set up more than one connection to the same process. If this happens, PART will report an error.

PART does not enforce any particular connection model (e.g., client-server), and therefore allows arbitrary connection topologies, illustrated by the figure below.

connection topologies

Connection API

A connection is a communications channel that connects two game processes. In PART, connections are represented by the class IpConnection. Once a connection has been established it can be used to exchange data bi-directionally between the processes. 

The PART connection establishment model is similar to the one used by TCP. In order for a connection to be established between two processes, one of the processes must have told PART that it accepts incoming connections via some protocol, and the other process must tell PART to set up a connection using the same protocol. These tasks can be achieved using the listen and connect methods of the IpSystem class.

When the game code calls listen and connect, a URL parameter that specifies a protocol and a network address must be supplied. The URL is basically a string with the following structure:

protocol://device:<protocol_specific>

The URL fields are:
  • The protocol field should be the name of a networking protocol. Supported protocol names in PART release 1.2 is tcp, http and btspp (Bluetooth Serial Port Profile).
  • The device field should be a name or address that identifies the hardware device where the local or remote process is running (depending on whether the URL is used in calls to listen or connect). This field is typically protocol specific. For instance, for an IP protocol ushc as HTTP or TCP, the device field should be a host name or an IP number, while in case of the Bluetooth serial port profile protocol the address should be a Bluetooth address. For both IP and the Bluetooth serial port profile protocols, it is acceptable to use the name localhost to refer to the host on which the process is running. Furthermore, if no device field is given at all when using these protocols, PART will assume a device name of localhost.
  • The protocol-specific field is optional and may contain extra information needed by the protocol to establish the connection. For instance, an IP based protocol would also need a port number in order to connect. If the protocol is btspp, the protocol specific field should be a service UUID when calling listen, and the corresponding service channel id when calling connect.

Listening for incoming connections

To listen for incoming connections, the listen method of the IpSystem class is used. The method takes a URL parameter that specifies the protocol to use when listening and the local address on which to listen.

For example, to listen for incoming TCP connections on port 1234, a game process would call:

IpSystem.listen(new IpUrl("tcp://localhost:1234"));

The same process could also (simultaneously) listen for incoming Bluetooth connections to a particular service id. 

String myServiceId = "5892fef2466547179b6501c74c118c23";
IpUrl listenUrl = new IpUrl("btspp://localhost: " + myServiceId);
IpSystem.listen(listenUrl);


A process can listen to as many local addresses simultaneously as are supported by the protocols used.

Whenever a remote process sets up a connection to an address listened to by the local process, PART automatically accepts the incoming connection and delivers an IpProcessEvent event of type CONNECTION_ESTABLISHED to the application’s event handler. Via the event, the application can retrieve the identifier of the remote process. For instance:

public void handleEvent(IpEvent event)
{
  if (event.getType().equals(IpProcessEvent.CONNECTION_ESTABLISHED))
  {
     IpProcessEvent e = (IpProcessEvent)event;
     // The identifier of the remote process
     IpIdentifier id = e.getProcessId();
     System.out.println("Connected to process " + id);
  }
}


The PART runtime will keep on listening for incoming connections until explicitly told to stop. This means that many incoming connections can be accepted on a particular local address. To stop listening to a particular URL, the application needs to call the stopListen method, supplying the same url as was given to listen:

// stop listening for incoming connections
IpSystem.stopListen(listenUrl);

Connecting to remote processes

To set up a connection to a remote process, the connect method of the IpSystem class is used. This method also takes a URL parameter, describing the protocol to use, and the address to which a connection should be set up.

The connect method is synchronous, meaning that it blocks the current thread until the connection attempt succeeds or fails. Failures may for instance be caused by network problems, or simply by the fact that no process is listening to the address described by the connection URL. If the connection succeeds, the identifier of the remote process is returned, and the local and remote processes are able to exchange messages over the newly created connection. Should the connection fail, an exception is thrown.

The connect method takes a timeout parameter that determines how long the local process should try to connect before giving up. If no connection can be established within the time given by the parameter, an IpTimeoutException exception is thrown. If the value of the timeout parameter is 0, the connect call will never timeout (it may still throw other exceptions though, for instance indicating an invalid url).

try {
    IpUrl url = new IpUrl("btspp://0012ee021fe8:9");
    // Will timeout in 10 seconds if not connected
    IpRemoteProcess p = thisProcess.connect(url, 10000, false);
} catch (Exception e) {
    // bummer!
}


Apart from the URL and timeout parameters, connect also takes a (third) parameter that determines if the connection should be persistent or not. Persistency in this case means that if the connection can’t be established within the time determined by the timeout parameter, or because some error occurs, PART will wait a while and then retry to set up the connection. This behaviour continues until the connection is successfully set up, or the connection attempt is cancelled by the application. If the connection succeeds at some later point in time, an IpProcessEvent of type CONNECTION_ESTABLISHED is delivered to the application’s event handler.

try {
    IpUrl url = new IpUrl("btspp://0012ee021fe8:9");
    // Will timeout in 10 seconds if not connected
    IpRemoteProcess p = thisProcess.connect(url, 10000,
                                            true);      <= persistent
} catch (Exception e) {
    // 1st attempt failed but PART will retry
    // connection in a while
}

 
The rate at which PART tries to connect if the first attempt fails is determined by the connection retry interval. The interval can be read and modified via the getConnectRetryInterval and setConnectRetryInterval methods of the IpSystem class.

System.out.println("The connection retry interval is "
                   + IpSystem.getConnectRetryInterval()
                   + " seconds");

// Set the interval to 30 seconds
IpSystem.setConnectRetryInterval(30);

                                               
PART will also try to re-connect persistent connections that are terminated as a result of some network error, i.e., whose status is broken (see next section).    

Terminating connections

Network connections set up to remote processes can be terminated by the application using the disconnect method.
     
IpSystem.disconnect(remoteProcId);

Using disconnect will cause a connection to be terminated in a controlled manner, and will cause an IpProcessEvent of type IpProcessEvent.CONNECTION_TERMINATED to be sent to the application’s event handlers. However, network connections can also be terminated as a result of some network problem. This will also cause an IpProcessEvent of type IpProcessEvent.CONNECTION_TERMINATED to be sent to the event handler. In order for the application to be able to determine the status of a network connection, and for instance determine whether a termination was caused by a call to disconnect or some (unknown) network problem, PART provides the getConnectionStatus method:

int status = IpSystem.getConnectionStatus(remoteProcId);

The returned status value can be one of the following:
  • IpConnection.UNCONNECTED means that the local and remote processes are not directly connected. Note that this doesn’t say whether there is an existing network route between the two processes or not.
  • IpConnection.CONNECTING means that a connection between the local and remote process is being set up, but is not yet completed. This value is returned if the local process has called connect, but the outcome has not yet been decided.
  • IpConnection.CONNECTED means that the local and remote process are connected and the connection seems to be working ok.
  • IpConnection.CLOSED_LOCALLY means that the connection was closed by the local process. This type is only returned if the connection was closed by the local process and the status is checked in an event handler when receiving an event of type IpProcessEvent.CONNECTION_TERMINATED.
  • IpConnection.CLOSED_REMOTELY means that the connection was closed by the remote process. This type is only returned if the connection was closed by the remote process and the status is checked in an event handler when receiving an event of type IpProcessEvent.CONNECTION_TERMINATED.
  • IpConnection.BROKEN that the connection is not working properly for some reason. This type is only returned if the status is checked in an event handler when receiving an event of type IpProcessEvent.CONNECTION_TERMINATED.

Sending data between processes

Connections are a prerequisite for being able to send data between PART processes. However, PART does not require direct connections between any two processes in order for them to be able to communicate. As long as there is some available route between them (using PART connections), any two processes can communicate. For example, even though there exists no direct connection between processes A and C in the figure below, they can still communicate since data can be routed via process B.

messaging

Data is sent between processes in the form of events. PART introduces a specific event class, IpNetworkEvent, which should be used for this purpose.

When creating a network event, three types of information needs to be provided:
  • The type of the event. This is an arbitrary string that will be set as the event type. Allows the application to type its events, which is often necessary for receivers to know what type of data an event carries.
  • The event destination. This is an identifier that identifies the process to which the event should be delivered.
  • The payload. This is the actual application data that the event will carry. The payload is represented by a sequential byte stream of type IpIoStream, which can be used to encode arbitrary data. Events do not have to carry a payload.

Unicast Events

Once an event has been created, it can be sent to its destination using the send method:

IpNetworkEvent event = new IpNetworkEvent(type, destId, payload);
event.send();


When send is called, PART tries to determine a possible route via which the event can be sent to reach the destination. If the sending process is directly connected to the destination process this is easy, the sending process just sends the event over the connection. However, should the sending process not be directly connected to the destination process, the sending process will send the event to its neighbour processes, which are then responsible for sending the event further.

Events that are received from remote processes are delivered to the application via the event handler. The event handler may then look at the event type and determine how to handle the event, and possibly unpack the event payload. Using the getSender method of the IpNetworkEvent class, it is possible for the receiving process to determine the identity of the sending process.

In the following example, a process sends an event to a remote process as a result of accepting an incoming connection:

public void handleEvent(IpEvent event)
{
    if (event.getType().equals(IpProcessEvent.CONNECTION_ESTABLISHED))
    {
        // The identifier of the process that initiated the connection
        IpIdentifier id = ((IpProcessEvent)event).getProcessId();
        // Send a hello message
        IpIoStream s = new IpIoStream();
        s.writeString("nice to see you!");
        // We just make up an event type, let's call it "hello"
        IpNetworkEvent e = new IpNetworkEvent("hello", id, s);
        e.send();
    }
}


The remote process would then receive this event in its handleEvent method:

public void handleEvent(IpEvent event)
{
    if (event.getType().equals("hello")) {
        IpNetworkEvent e = (IpNetworkEvent)event;
        IpIoStream s = e.getPayload();
        System.out.println("Message from remote process is: "
                           + s.readString());
    }
}

Event broadcasting

Apart from allowing events to be sent to individual destinations (processes), PART also provides a method for doing event broadcasting.

IpNetworkEvent event = new IpNetworkEvent(type, null, payload);
event.broadcast();


When an event is broadcasted, it is sent to all processes that can be reached from the local process. Note that this does not only include the sending process’ closest neighbours, but also the neighbours of those processes, and so on. PART uses a algorithm to detect looping messages, so event broadcasting can be used even if processes are connected in loops.

Waiting for replies

Sometimes the sender of an event expects the receiver to send a reply. The IpNetworkEvent class provides a number of methods that can be used to synchronise the sender and receiver in such cases. Basically there are two mechanisms that the sender can use to wait for a reply, blocking or polling. Both methods require the sender to set a reply timeout via the setReplyTimeout method of the the IpNetworkEvent class. A reply timeout is the maximum time (in milliseconds) that PART will wait for a reply to an event sent to a destination. If the timeout is reached, PART will wait no longer meaning that the reply is ignored, even if later received.

The blocking reply mechanism requires the sender to set a reply timeout, and then call a wait method which will return the reply or throw a timeout exception if no reply has been received within the timeout interval:
 
IpNetworkEvent e = new IpNetworkEvent(type, destId, payload);
e.setReplyTimeout(10000); // 10 second timeout
e.send();
try {
  // block waiting for reply or timeout
  IpNetworkEvent reply = e.waitForReply();
  // We got a reply!
  ...
} catch (IpTimeoutException ex) {
  // no reply was received within 10 seconds
    …
}


The polling mechanism involves continuously checking whether a reply has been received or not. After sending the event, the sender can call the timeUntilReplyTimeout and getReplyEvent to check if the timeout time has expired and if a reply has been received.  The timeUntilReplyTimeout method returns the number of milliseconds left before PART will timeout and ignore the reply. The getReplyEvent can be called to check if a reply has been received. The method doesn’t block and returns null if no event has yet been received.

IpNetworkEvent e = new IpNetworkEvent("test", destId, payload);
e.setReplyTimeout(10000); // 10 second timeout
e.send();
IpNetworkEvent reply;
while (e.timeUntilReplyTimeout() > 0) {
   reply = e.getReplyEvent();
   if (reply != null) {
    // We got a reply!
    break;
   } else {
     // do something else for a while
     ...
   }
 }
 if (reply == null) {
   // no reply was received within 10 seconds
   ...
 }


In order for both the blocking as well as the polling reply mechanisms to work, the process sending the reply must use the createReply method to create an event to be sent back as a reply to the original sender:
 
public void handleEvent(IpEvent event)
{
    if (event.getType().equals("test")) {
        IpNetworkEvent e = (IpNetworkEvent)event;
        // do something, for example check event content
        …
        // create and send a reply
        IpNetworkEvent reply = e.createReply(type, payload);
        reply.send();
    }
}


If createReply is not used to create the reply event, PART won’t be able to determine that a reply has been received, which will cause the sender to timeout.

The reply mechanisms do actually work even if the broadcasting mechanism is used to send the event for which a reply is expected. However, it is only possible to catch one of the replies sent back by the receiving processes, which will be the first reply received.

theme by Chris M