hellocore

HelloCore

This tutorial will guide you through the basic concepts and programming involved in the communication between a mobile node (MN) and a stationary node within the ContextNet SDDL core network architecture. This stationary node of the SDDL core will play the role of a server processing node, capable of processing application messages from the mobile node (MN) according to some application specific logic, and sending messages back to the MN. Hence, this tutorial will cover both paths of the communication, i.e., from the MN to the server processing node, and from the stationary node to the MN.

Our sample application is composed of two components: a mobile client node and a stationary node, as illustrated in the figure on the right. The MN creates a Hello World message which is sent to the SDDL core. Our processing node receives this message and execute a simple logic, keeps track on the number of messages received and prints them on the screen. To exemplify the other path of communication, the processing node sends a reply message to the MN containing the number of message that it received so far.

The tutorial first shows the code of the mobile client application, and then continues with the discussion of the processing node's code. As a matter of simplification, the entire application system, composed of the mobile client, gateway, and processing node, runs locally on a single machine, but it can be easily modified to run on distributed machines.

You do not need to copy the source code snippets shown in this tutorial. At the end of each section there is a link to download the entire node source code.

To create our mobile client application, create a Java Project in Eclipse called HelloCore. Be cautious and certify if you imported correctly the required libraries, which in this case is the contextnet.jar library. Alternatively, you can declare the ContextNet middleware as a Maven dependency . If you have any question on how to import this library, check the creating a SDDL project page. After these steps, create an empty Java HelloCoreClient Class using the br.pucrio.inf.lac.helloworld package in the HelloCore project.

In SDDL, nodes that are outside the core (either mobile or stationary) interact directly with the Gateway rather than the processing nodes, since it uses a different protocol (ClientLib and MRUDP) than the core (Data Distribution Service (DDS)). The Gateway translate the node message to the core protocol and vice-versa. Thus, the primary interaction of the client is with the gateway. Therefore, we first declare two variables to locate our gateway, its IP address and port number. Since in this tutorial we are running our entire application (mobile client node, gateway, and processing node) locally, we can specify the gateway IP to the loopback 127.0.0.1 and choose its default port 5500. In a real distributed scenario, the client would need to know at least one of the gateways IP and ports pair.

public class HelloCoreClient {
  private static String  gatewayIP   = "127.0.0.1";
  private static int     gatewayPort = 5500;
}

After that, we specify our main method. It set ups a debugging information and call the class constructor. The logger shows SDDL debug information; it is very useful if something goes wrong. Since we are doing a basic application, we can disable the debugging log feature.

public class HelloCoreClient {
  private static String       gatewayIP   = "127.0.0.1";
  private static int          gatewayPort = 5500;
  private MrUdpNodeConnection connection;
 
  public static void main(String[] args) {
      Logger.getLogger("").setLevel(Level.OFF);
      new HelloCoreClient();
  }
}

We will make the connection with the gateway in our constructor. The client connection is very straightforward; first, we create a MrUdpNodeConnection object. This connection object, encapsulate the details and protocol involved in the physical connection with the gateway, for instance, it uses unique ids to identify the client and the connection when the node changes IPs. If we do not specify an argument in the connection instantiation, it automatically generates an UUID to identify the client. This id is used to unique identify the client independent of his IP, which is useful for handover and abstracting the client location. We add a listener to the connection object (NodeConnectionListener), which provides methods to react to the node gateway connection. It will trigger functions to indicate that the node has successfully connected, disconnected, reconnected from the gateway alongside with operations to receive new messages, and unsent messages (messages that were timeouted and not sent due to disconnection). Finally, we establish the connection calling the connect() method passing the gateway address as parameter.

public class HelloCoreClient {
  // ...
 
  public HelloCoreClient() {
      InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
      try {
          connection = new MrUdpNodeConnection();
          connection.addNodeConnectionListener(this);
          connection.connect(address);
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
}

We added our object as a NodeCollectionListener listener, we will receive notifications when the node connects, disconnects, receives message, etc. Since we are doing a simple application, we will focus on connect and receive methods. When the middleware call our connect method it signalize to us that the node has successfully connected to the gateway and we can now proceed to send messages. Messages are created using the ApplicationMessage class, and allow us to send any Java serializable content. For simplicity, we are sending a String containing HelloWorld. To send a message we simply call the sendMessage() function of our connection, with the application message as parameter.

public class HelloCoreClient implements NodeConnectionListener {
  // ...
 
  @Override
  public void connected(NodeConnection remoteCon) {
      ApplicationMessage message = new ApplicationMessage();
      String serializableContent = "Hello World";
      message.setContentObject(serializableContent);
 
      try {
          connection.sendMessage(message);
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
}

Note that, the connect method receives a nodeConnection parameter which represents the node connection with the gateway where the connect event happened. This information is useful if the client has multiple and different gateway connections while using the same listener, for example, the client is connected to the gateway of the server application and uses a different connection to other part of the application using the same listener. Since in this case, we are only connecting to only one gateway, both connections are equal and we can use either one to send messages.

Finally, we need to implement our logic for receiving the messages of the processing nodes. The connection will trigger the newMessageReceived() method when a new message arrives. The function includes the connection from where the message was received and the message itself. This message object contains metadata, such as who sent the message, which gateway it cam from, etc. We can access the message content using the message.getContentObject() function. Our logic will print in the screen the received message content, which is the number of message received so far by the processing node. The full implementation of the mobile client node is displayed below, you can download the file by clicking in the HelloCoreClient.java tab.

HelloCoreClient.java
package br.pucrio.inf.lac.helloworld;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import lac.cnclib.net.NodeConnection;
import lac.cnclib.net.NodeConnectionListener;
import lac.cnclib.net.mrudp.MrUdpNodeConnection;
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.message.Message;
 
public class HelloCoreClient implements NodeConnectionListener {
 
  private static String		gatewayIP   = "127.0.0.1";
  private static int		gatewayPort = 5500;
  private MrUdpNodeConnection	connection;
 
  public static void main(String[] args) {
      Logger.getLogger("").setLevel(Level.OFF);
      new HelloCoreClient();
  }
 
  public HelloCoreClient() {
      InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
      try {
          connection = new MrUdpNodeConnection();
          connection.addNodeConnectionListener(this);
          connection.connect(address);
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
 
  @Override
  public void connected(NodeConnection remoteCon) {
      ApplicationMessage message = new ApplicationMessage();
      String serializableContent = "Hello World";
      message.setContentObject(serializableContent);
 
      try {
          connection.sendMessage(message);
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
 
  @Override
  public void newMessageReceived(NodeConnection remoteCon, Message message) {
      System.out.println(message.getContentObject());
  }
 
  // other methods
 
  @Override
  public void reconnected(NodeConnection remoteCon, SocketAddress endPoint, boolean wasHandover, boolean wasMandatory) {}
 
  @Override
  public void disconnected(NodeConnection remoteCon) {}
 
  @Override
  public void unsentMessages(NodeConnection remoteCon, List<Message> unsentMessages) {}
 
  @Override
  public void internalException(NodeConnection remoteCon, Exception e) {}
}

The core processing nodes establishes premises and protocols that are not suitable for mobile applications. To expand this processing power to mobile nodes, we added the concept of gateways that use our lightweight protocol to communicate with mobile nodes and translate their message to core messages. Developers can instantiate nodes to process the mobile nodes message.

To create our processing node application, create an empty Java HelloCoreServer Class using the br.pucrio.inf.lac.helloworld package. You can create this class in the existing HelloCore project or create a separated project, for now we will make the class in the existing project. The processing nodes communicate using SDDL, an abstraction that encapsulate the DDS communication.

The processing node uses the DDS protocol to communicate in the core. Thus, it needs to create readers and writers to manipulate data in the core. In addition, it needs to formally join the other core nodes, this is done by recovering an instance of the middleware and creating a participant. After that, it instantiate publishers and subscribers to enable reading and writing data. In our application, we are interested in reading the application messages sent to the core, thus, we create a data reader for the Message topic, which represents application messages. We are also interested on sending a reply to our mobile node, thus, we create a data writer for the private message topic, which allow us to send a message to a specific node. Our processing node will count the number of messages received, thus, we define a global count variable. Finally, we specify a very simple wait command to ensure that the processing nodes stays alive. This initial code is displayed bellow:

public class HelloCoreServer implements UDIDataReaderListener<ApplicationObject> {
  SddlLayer core;
  int       counter;
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
 
    new HelloCoreServer();
  }
 
  public HelloCoreServer() {
    core = UniversalDDSLayerFactory.getInstance();
    core.createParticipant(UniversalDDSLayerFactory.CNET_DOMAIN);
 
    core.createPublisher();
    core.createSubscriber();
 
    Object receiveMessageTopic = core.createTopic(Message.class, Message.class.getSimpleName());
    core.createDataReader(this, receiveMessageTopic);
 
    Object toMobileNodeTopic = core.createTopic(PrivateMessage.class, PrivateMessage.class.getSimpleName());
    core.createDataWriter(toMobileNodeTopic);
 
    counter = 0;
    System.out.println("=== Server Started (Listening) ===");
    synchronized (this) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

To receive the nodes message our processing node need to implement UDIDataReaderListener interface. This interface specify an onNewData() function that is triggered when a new message is received. The parameter of this function is the message received, which need to be deserialized to the correct language, since this information is application specific. We sent a Java object, so we can safely desserialize as a Java object using the Serialization.fromJavaByteStream() function. After that, we print the received message on screen.

Our processing node executes a simple logic of returning the number of message received. Therefore, we increment our message counter and create a private message to communicate with the mobile node that sent the message. In the private message, we specify a set of metadata to identify the mobile node, including its node ID and gateway ID that the node is connected. We create an application message that includes this counter and set as the content of our private message. Finally, we send the message to the core to redirect to the mobile node by writing in the PrivateMessage topic. The complete source code of our processing node is displayed below:

HelloCoreServer.java
package br.pucrio.inf.lac.helloworld;
 
import java.util.logging.Level;
import java.util.logging.Logger;
 
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.serialization.Serialization;
import lac.cnet.sddl.objects.ApplicationObject;
import lac.cnet.sddl.objects.Message;
import lac.cnet.sddl.objects.PrivateMessage;
import lac.cnet.sddl.udi.core.SddlLayer;
import lac.cnet.sddl.udi.core.UniversalDDSLayerFactory;
import lac.cnet.sddl.udi.core.listener.UDIDataReaderListener;
 
public class HelloCoreServer implements UDIDataReaderListener<ApplicationObject> {
  SddlLayer  core;
  int        counter;
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
 
    new HelloCoreServer();
  }
 
  public HelloCoreServer() {
    core = UniversalDDSLayerFactory.getInstance();
    core.createParticipant(UniversalDDSLayerFactory.CNET_DOMAIN);
 
    core.createPublisher();
    core.createSubscriber();
 
    Object receiveMessageTopic = core.createTopic(Message.class, Message.class.getSimpleName());
    core.createDataReader(this, receiveMessageTopic);
 
    Object toMobileNodeTopic = core.createTopic(PrivateMessage.class, PrivateMessage.class.getSimpleName());
    core.createDataWriter(toMobileNodeTopic);
 
    counter = 0;
    System.out.println("=== Server Started (Listening) ===");
    synchronized (this) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
 
  @Override
  public void onNewData(ApplicationObject topicSample) {
    Message message = (Message) topicSample;
    System.out.println(Serialization.fromJavaByteStream(message.getContent()));
 
    PrivateMessage privateMessage = new PrivateMessage();
    privateMessage.setGatewayId(message.getGatewayId());
    privateMessage.setNodeId(message.getSenderId());
 
    synchronized (core) {
      counter++;
    }
 
    ApplicationMessage appMsg = new ApplicationMessage();
    appMsg.setContentObject(counter);
    privateMessage.setMessage(Serialization.toProtocolMessage(appMsg));
 
    core.writeTopic(PrivateMessage.class.getSimpleName(), privateMessage);
  }
}

To execute our application, we need to first create the gateway, which will instantiate the core infrastructure if none exists. The gateway receives two parameters, its public IP and a given port number. Since we are this sample locally, we can choose the loopback IP 127.0.0.1 and the default middleware port 5500. To do that, open a shell, and run the following command:

$ java -jar contextnet-2.5.jar 127.0.0.1 5500 OpenSplice

After that, we instantiate our processing node. To do that, run the HelloCoreServer class in Eclipse as a Java application. Finally, execute the HelloCoreClient class in Eclipse to start the mobile node application. You will see the printed messages in your console.

This tutorial demonstrated how to connect a mobile node with the core processing nodes in the ContextNet architecture. The example used a basic code that illustrates the asynchronous send and receive primitives for both, the client and core, type of nodes. As an exercise try to extend the client node application to send several messages and use several core servers.

  • hellocore.txt
  • Last modified: 2017/07/21 03:08
  • (external edit)