hellogroup

HelloGroup

In this tutorial we will exemplify SDDL's group communication capabilities. In particular, we will show how mobile clients can join a group and communicate through it. Similarly to one-to-one (unicast) communication (see Tutorial HelloMobile), also group communication is asynchronous, which means that message delivery is based on callback method invocation.

Our sample application will consist of two peer executing in mobile nodes: a group peer that will act as a client and another that act as a receiver, as illustrated in the figure aside. The application code share much similarity with the previous tutorials, with the addition that now the nodes implement the GroupMembershipListener, that trigger events for joining, leaving, and receiving messages from Groups.

We start the tutorial explaining the concept of Groups, that in SDDL can be explicit or implicit. After that, we explain the changes we made in the mobile client applications to accommodate those concepts. The tutorial then displayis the the modifications that allow a peer HelloGroupClient to explicity join a Group and send a message to it. The HelloGroupPeer peer will join this group to receive the message sent. After this example, we show an implicit group communication, which requires a mobile node application and a processing node GroupDefiner to map nodes to groups.

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. Moreover, all examples are included in the virtual machine made available.

In SDDL, groups are created/defined according to the group selection logic implemented in the GroupDefiner, and mobile clients can only become members of (any set of) existing groups. Group membership means that the mobile client will receive all the messages posted to its group. A mobile client may become member of any group in any of following two ways:

  1. Implicit: Some of its application message attribute satisfies the membership condition in the group selection logic. For example, its UUID is included in a static set of group member IDs, or otherwise, its geographic coordinate falls inside a specific region boundary;
  2. Explicit: The mobile client explicitly joins an existing group through the join() operation of the ClientLib GroupAPI.

It is worth noting that membership is the conjunction of the implicit and the explicit case: i.e., a mobile client which is an implicit member of a group (case 1) will remain so even after making the leave() operation, and a client that is not an implicit member of a group, can control its group membership through the join() and leave() operations of the GroupAPI.

Moreover, in SDDL groups are open. This means that regarding the sending of groupcast messages there is no need to be a group member. Instead, it is sufficient to use the correct GroupID as the message destination.

The HelloGroupClient application adds some modifications to the previous clients. Our client now specify a group that we want to join and communicate with it. In SDDL, each group is identified by its type and id, precisely, an integer pair. In our sample application, we want to communicate with the group that has group type of 250 and id of 300.

To communicate with groups using the GroupAPI, we need to instantiate a group manager in a node ⇔ gateway connection. This is done usually in the connection event function that is triggered when the node connects to the gateway. After that, we execute a join call in the manager passing the group that we want to communicate as parameter. After that, we can send messages to the group members (groupcast messages) using the group manager. Finally, we added a code in our newMessageReceived method to exemplify that sending to the group, all nodes receives the message, including the sender. The code is shown below:

HelloGroupClient.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.groups.Group;
import lac.cnclib.net.groups.GroupCommunicationManager;
import lac.cnclib.net.groups.GroupMembershipListener;
import lac.cnclib.net.mrudp.MrUdpNodeConnection;
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.message.Message;
 
public class HelloGroupClient implements NodeConnectionListener, GroupMembershipListener {
 
  private static String               gatewayIP    = "127.0.0.1";
  private static int                  gatewayPort  = 5500;
  private GroupCommunicationManager   groupManager;
  private Group                       aGroup;
 
  public HelloGroupClient() {
    InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
    try {
      MrUdpNodeConnection connection = new MrUdpNodeConnection();
      connection.connect(address);
      connection.addNodeConnectionListener(this);
 
      aGroup = new Group(250, 300);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
 
    new HelloGroupClient();
  }
 
  @Override
  public void connected(NodeConnection remoteCon) {
    groupManager = new GroupCommunicationManager(remoteCon);
    groupManager.addMembershipListener(this);
 
    try {
      groupManager.joinGroup(aGroup);
 
      ApplicationMessage appMsg = new ApplicationMessage();
      appMsg.setContentObject("Group Message");
 
      groupManager.sendGroupcastMessage(appMsg, aGroup);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  @Override
  public void reconnected(NodeConnection remoteCon, SocketAddress endPoint, boolean wasHandover, boolean wasMandatory) {}
 
  @Override
  public void disconnected(NodeConnection remoteCon) {}
 
  @Override
  public void newMessageReceived(NodeConnection remoteCon, Message message) {
    System.out.println("Group sender also received: " + message.getContentObject());
  }
 
  @Override
  public void unsentMessages(NodeConnection remoteCon, List<Message> unsentMessages) {}
 
  @Override
  public void internalException(NodeConnection remoteCon, Exception e) {}
 
  @Override
  public void enteringGroups(List<Group> groups) {
    System.out.println("Entered in the group");
  }
 
  @Override
  public void leavingGroups(List<Group> groups) {}
}

Since group clients and peers are identical, the HelloGroupPeer receiver code is pretty much the same. With the exception that this client does not send the message, only receives. The complete code is below:

HelloGroupPeer.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.groups.Group;
import lac.cnclib.net.groups.GroupCommunicationManager;
import lac.cnclib.net.groups.GroupMembershipListener;
import lac.cnclib.net.mrudp.MrUdpNodeConnection;
import lac.cnclib.sddl.message.Message;
 
public class HelloGroupPeer implements NodeConnectionListener, GroupMembershipListener {
 
  private static String              gatewayIP    = "127.0.0.1";
  private static int                 gatewayPort  = 5500;
  private GroupCommunicationManager  groupManager;
  private Group                      aGroup;
 
  public HelloGroupPeer() {
    InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
    try {
      MrUdpNodeConnection connection = new MrUdpNodeConnection();
      connection.connect(address);
      connection.addNodeConnectionListener(this);
 
      aGroup = new Group(250, 300);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
 
    new HelloGroupPeer();
  }
 
  @Override
  public void connected(NodeConnection remoteCon) {
    groupManager = new GroupCommunicationManager(remoteCon);
    groupManager.addMembershipListener(this);
 
    try {
      groupManager.joinGroup(aGroup);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  @Override
  public void reconnected(NodeConnection remoteCon, SocketAddress endPoint, boolean wasHandover, boolean wasMandatory) {}
 
  @Override
  public void disconnected(NodeConnection remoteCon) {}
 
  @Override
  public void newMessageReceived(NodeConnection remoteCon, Message message) {
    System.out.println("Group Peer received: " + message.getContentObject());
  }
 
  @Override
  public void unsentMessages(NodeConnection remoteCon, List<Message> unsentMessages) {}
 
  @Override
  public void internalException(NodeConnection remoteCon, Exception e) {}
 
  @Override
  public void enteringGroups(List<Group> groups) {
    System.out.println("Entered in the group");
  }
 
  @Override
  public void leavingGroups(List<Group> groups) {}
}

The execution of our application is identical to the others example, if there is no gateway executing we need to create one to instantiate the core infrastructure. To do that, open a shell, and run the following command:

$ gateway 127.0.0.1 5500

After that, we instantiate the mobiles nodes. First run the HelloGroupPeer class in Eclipse as a Java application. Finally, execute the HelloGroupClient class in Eclipse to start the mobile node application. You will see the printed messages in your console.

The GroupDefiner concept allows applications to implicitly aggregate nodes in groups. When used, the SDDL core redirect the mobile nodes messages to the GroupDefiner, that apply some application logic, which returns the group set of that node. To exercise this concept, in this tutorial, we are developing a simple logic that separates the nodes based in their identifier UUID. If their UUID least significant bits is even they are assigned to group 2, otherwise (it is odd) they are assigned to group 3. By default we also associate them to our application default group, which we consider to be group 1. In addition, every GroupDefiner only process information of a given group type. Therefore, every GroupDefiner must implement the GroupSelector interface, which contains two methods:

  • getGroupType() - which returns the definer group type
  • processGroup(Message message) - which classify and returns the node’s group ID

The entire code of our GroupDefiner and GroupSelector is listed below:

HelloGroupSelector.java
package br.pucrio.inf.lac.helloworld;
 
import java.util.HashSet;
import java.util.Set;
 
import lac.cnet.groupdefiner.components.groupselector.GroupSelector;
import lac.cnet.sddl.objects.Message;
 
public class HelloGroupSelector implements GroupSelector {
 
  @Override
  public int getGroupType() {
    return 3;
  }
 
  @Override
  public Set<Integer> processGroups(Message nodeMessage) {
    System.out.println("STARTED CLASSIFYING GROUP MESSAGE");
 
    // Add the node to two groups
    HashSet<Integer> groupCollection = new HashSet<Integer>(2, 1);
 
    // Some default group (ID = 1)
    groupCollection.add(1);
 
    // Even (2) or Odd Group (3)
    if (nodeMessage.getSenderId().getLeastSignificantBits() % 2 == 0) {
      groupCollection.add(2);
    } else {
      groupCollection.add(3);
    }
 
    System.out.println("ENDED CLASSIFYING GROUP MESSAGE\n");
    return groupCollection;
  }
}
HelloGroupDefiner.java
package br.pucrio.inf.lac.helloworld;
 
import lac.cnet.groupdefiner.components.GroupDefiner;
import lac.cnet.groupdefiner.components.groupselector.GroupSelector;
 
public class HelloGroupDefiner {
 
  public static void main(String[] args) {
 
    GroupSelector groupSelector = new HelloGroupSelector();
    new GroupDefiner(groupSelector);
 
    try {
      Thread.sleep(Long.MAX_VALUE);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
 
}

To test our GroupDefiner, we will implement two mobile nodes receiver. One will have its UUID least significant bit as an even number, while the other one has an odd one. They will respectively be classified in the groups ID <1, 2> and <1, 3> of group type 3. Each node will send a simple message just so the definer can classify them. After that, each node waits for the messages from their implicit group. The code of both mobile node receivers are below. Note, that the only difference is the line where the UUID is generated. We enforce that the number is even or odd accordingly to the given node.

HelloGroupDefinerEvenReceiver.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.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import lac.cnclib.net.NodeConnection;
import lac.cnclib.net.NodeConnectionListener;
import lac.cnclib.net.groups.Group;
import lac.cnclib.net.groups.GroupCommunicationManager;
import lac.cnclib.net.groups.GroupMembershipListener;
import lac.cnclib.net.mrudp.MrUdpNodeConnection;
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.message.Message;
 
public class HelloGroupDefinerEvenReceiver implements NodeConnectionListener, GroupMembershipListener {
  private final static String        gatewayIP    = "127.0.0.1";
  private final static int           gatewayPort  = 5500;
  private GroupCommunicationManager  groupManager;
 
  public HelloGroupDefinerEvenReceiver() {
    UUID uuid = UUID.randomUUID();
 
    while (uuid.getLeastSignificantBits() % 2 != 0) {
      uuid = UUID.randomUUID();
    }
    InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
    try {
      MrUdpNodeConnection connection = new MrUdpNodeConnection(uuid);
      connection.connect(address);
      connection.addNodeConnectionListener(this);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
    new HelloGroupDefinerEvenReceiver();
  }
 
  @Override
  public void connected(NodeConnection remoteCon) {
    groupManager = new GroupCommunicationManager(remoteCon);
    groupManager.addMembershipListener(this);
 
    try {
      ApplicationMessage message = new ApplicationMessage();
      String serializableContent = "Bogus Message for Group Definer";
      message.setContentObject(serializableContent);
 
      remoteCon.sendMessage(message);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
 
  @Override
  public void reconnected(NodeConnection remoteCon, SocketAddress endPoint, boolean wasHandover, boolean wasMandatory) {}
 
  @Override
  public void disconnected(NodeConnection remoteCon) {}
 
  @Override
  public void newMessageReceived(NodeConnection remoteCon, Message message) {
    System.out.println(message.getContentObject());
  }
 
  @Override
  public void unsentMessages(NodeConnection remoteCon, List<Message> unsentMessages) {}
 
  @Override
  public void internalException(NodeConnection remoteCon, Exception e) {}
 
  @Override
  public void enteringGroups(List<Group> groups) {}
 
  @Override
  public void leavingGroups(List<Group> groups) {}
}
HelloGroupDefinerOddReceiver.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.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import lac.cnclib.net.NodeConnection;
import lac.cnclib.net.NodeConnectionListener;
import lac.cnclib.net.groups.Group;
import lac.cnclib.net.groups.GroupCommunicationManager;
import lac.cnclib.net.groups.GroupMembershipListener;
import lac.cnclib.net.mrudp.MrUdpNodeConnection;
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.message.Message;
 
public class HelloGroupDefinerOddReceiver implements NodeConnectionListener, GroupMembershipListener {
  private final static String        gatewayIP    = "127.0.0.1";
  private final static int           gatewayPort  = 5500;
  private GroupCommunicationManager  groupManager;
 
  public HelloGroupDefinerOddReceiver() {
    UUID uuid = UUID.randomUUID();
 
    while (uuid.getLeastSignificantBits() % 2 == 0) {
      uuid = UUID.randomUUID();
    }
    InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
    try {
      MrUdpNodeConnection connection = new MrUdpNodeConnection(uuid);
      connection.connect(address);
      connection.addNodeConnectionListener(this);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
    new HelloGroupDefinerOddReceiver();
  }
 
  @Override
  public void connected(NodeConnection remoteCon) {
    groupManager = new GroupCommunicationManager(remoteCon);
    groupManager.addMembershipListener(this);
 
    try {
      ApplicationMessage message = new ApplicationMessage();
      String serializableContent = "Bogus Message for Group Definer";
      message.setContentObject(serializableContent);
 
      remoteCon.sendMessage(message);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
 
  @Override
  public void reconnected(NodeConnection remoteCon, SocketAddress endPoint, boolean wasHandover, boolean wasMandatory) {}
 
  @Override
  public void disconnected(NodeConnection remoteCon) {}
 
  @Override
  public void newMessageReceived(NodeConnection remoteCon, Message message) {
    System.out.println(message.getContentObject());
  }
 
  @Override
  public void unsentMessages(NodeConnection remoteCon, List<Message> unsentMessages) {}
 
  @Override
  public void internalException(NodeConnection remoteCon, Exception e) {}
 
  @Override
  public void enteringGroups(List<Group> groups) {}
 
  @Override
  public void leavingGroups(List<Group> groups) {}
}

Our client will act slightly the same as the HelloGroupClient. With the difference, that we now send messages to the group type 3, and groups ID 1, 2, and 3. The HelloGroupDefinerEvenReceiver will receives the messages of group ID 1 and 2, while the HelloGroupDefinerOddReceiver will receives the messages of groups ID 1 and 3. The complete code of our client is shown below:

HelloGroupClient.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.groups.Group;
import lac.cnclib.net.groups.GroupCommunicationManager;
import lac.cnclib.net.groups.GroupMembershipListener;
import lac.cnclib.net.mrudp.MrUdpNodeConnection;
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.message.Message;
 
public class HelloGroupClient implements NodeConnectionListener, GroupMembershipListener {
 
  private final static String        gatewayIP    = "127.0.0.1";
  private final static int           gatewayPort  = 5500;
  private GroupCommunicationManager  groupManager;
 
  public HelloGroupClient() {
    InetSocketAddress address = new InetSocketAddress(gatewayIP, gatewayPort);
    try {
      MrUdpNodeConnection connection = new MrUdpNodeConnection();
      connection.connect(address);
      connection.addNodeConnectionListener(this);
 
      try {
        Thread.sleep(30000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
 
    new HelloGroupClient();
  }
 
  @Override
  public void connected(NodeConnection remoteCon) {
    groupManager = new GroupCommunicationManager(remoteCon);
    groupManager.addMembershipListener(this);
 
    try {
      // i = 1 Default
      // i = 2 EVEN
      // i = 3 ODD
      for (int i = 1; i < 4; i++) {
        Group group = new Group(3, i);
 
        ApplicationMessage appMsg = new ApplicationMessage();
        appMsg.setContentObject("Message GroupID: " + i);
        groupManager.sendGroupcastMessage(appMsg, group);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  @Override
  public void reconnected(NodeConnection remoteCon, SocketAddress endPoint, boolean wasHandover, boolean wasMandatory) {}
 
  @Override
  public void disconnected(NodeConnection remoteCon) {}
 
  @Override
  public void newMessageReceived(NodeConnection remoteCon, Message message) {
    System.out.println(message.getContentObject());
  }
 
  @Override
  public void unsentMessages(NodeConnection remoteCon, List<Message> unsentMessages) {}
 
  @Override
  public void internalException(NodeConnection remoteCon, Exception e) {}
 
  @Override
  public void enteringGroups(List<Group> groups) {}
 
  @Override
  public void leavingGroups(List<Group> groups) {}
}

The execution of our application is identical to the others example, if there is no gateway executing we need to create one to instantiate the core infrastructure. To do that, open a shell, and run the following command:

$ gateway 127.0.0.1 5500

After that, we instantiate the GroupDefiner, followed by the mobile nodes. First run the HelloGroupDefiner class in Eclipse as a Java application. After that, execute both the HelloGroupDefinerEvenReceiver and HelloGroupDefinerOddReceiver classes. Finally, execute the HelloGroupClient class in Eclipse to start the mobile node application. You will see the printed messages in your console.

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