![]() |
User Manual, Developers Guide and API Documentation |
![]() |
Classes | |
| class | Interface |
| Interface of Node. More... | |
| class | Node |
| Contains various Components to form a network element in a simulation. More... | |
| class | NodeSimulationModel |
Namespaces | |
| namespace | component |
| namespace | tests |
Typedefs | |
| typedef wns::container::Registry < std::string, node::Interface * > | Registry |
Each node contains several components that enable communication. In this example components are WiMAX, IP, TCP, Webserver, WebBrowser. Before we continue we will define what we mean when talking about nodes and components:
The component semantics have been borrowed from UML 2.0. In "The Unified Modeling Language Reference Manual, Second Edition" Rumbaugh et.al. state that a component is
"A modular part of a system design that hides its implementation behind a set of external interfaces. Within a a system, components satisfying the same interfaces may be substituted freely."
This is in accordance with our understanding of a component. In particular we have the following specializations:
The nodes (Node) are communicating via TCP/IP over WiMAX (802.16). Packets will be generated in either WebBrowser or WebServer Component and will travel down the node through the TCP, IP and WiMAX Component in order to be sent to the other Node where they travel the Node up again. The question is now: How to achieve such a setup? What do the config files look like and what are the respective classes in the C++ world?
To simplify the example a little further we're going to focus on TCP and IP only. First of all let's have a look how the TCP and the IP Component are going to exchange information/data.
For our example the TCP and IP are going to form a Node. How can we do that? Have a look at the following code snippet:
00001 00002 node::Interface* node = new Node(registry, nodeConfig); 00003 component::Interface* ci = new component::tests::ComponentStub(node, "component"); 00004 component::tests::TCPInterface* tcp = 00005 new component::tests::TCP(ci, tcpConfig); 00006 component::tests::IPInterface* ip = 00007 new component::tests::IP(ci, ipConfig); 00008 00009 node->addService(tcp->getName(), tcp); 00010 node->addService(ip->getName(), ip);
First we're creating a Node (node) and two components (tcp, ip). After that we're putting the two components into the Node. The node contains now the two components (tcp and ip). The three variables nodeConfig, tcpConfig and ipConfig contain the configuration (see wns::pyconfig) for these classes.
Let's explore how the Node and those Components work together. The Node has the following interface:
If we take a closer look at the method Node::addComponent(component::Interface*) we can observe that it takes a pointer to a component::Interface as argument. The code detective inside us suggests that TCP and IP must be of type component::Interface. Bingo! So the class hierachy looks something like this:
What does the component::Interface look like?
However this is only half the truth, because as we can see from the code snippet above the TCP instancs is assigned to a pointer of type TCPInterface. This means there is not only a component::Interface but also a TCPInterface. TCPInterface must be derived from component::Interface otherwise Node::addComponent can't be called with this type.Further, TCP must be derived from TCPInterface otherwise the assignment to TCPInterface would not be possible. Hence, our hierachy looks now like this:
The "Interfaces" (IPInterface, TCPInterface, component::Interface) are abstract (which means they contain only definitions of the methods but no implementation). What is the benefit of these Interfaces? Consider the following: TCP will very likely use an IP instance. But it should not rely on one specific implementation. So it will use the IPInterface instead of IP direct:
And we can have more than one IP implementation. As long as it conforms to the IPInterface everything is fine:
Still with me? Good! We still have room for optimization! The classes TCP, IPv4 and IPv6 are derived from component::Interface (through TCPInterface and IPInterface). Since the component::Interface is abstract we have to implement all the methods required by component::Interface (see above which methods are required by component::Interface). They are very similar for all three classes (TCP, IPv4, IPv6). To be exact: they are the same. Lucky as we are, we don't need to implement these methods. There is a default implementation available: Component. To get the implementation from Component we derive TCP, IPv4 and IPv6 from Component:
Et voila! We've a way to define interfaces for different types of Components and they can be stored in a Node. Now, how does all this look like in C++? We're going to examine only the TCP class. Let's look a the class hierachy again:
A simple TCPInterface
Assume the following interface has been defined for an TCP component:
00001 00002 // Define a new Interface 00003 class TCPInterface : 00004 virtual public service::Service 00005 { 00006 public: 00007 virtual int 00008 getFreePort() = 0; 00009 00010 virtual std::string 00011 getAddressOfIPInstance() = 0; 00012 00013 virtual std::string 00014 getName() = 0; 00015 };
These methods are again abstract and need to be implemented.
An TCP implementation
The implementation of TCP might then look like this:
00001 00002 // Implementation of TCPInterface 00003 class TCP : 00004 virtual public TCPInterface 00005 { 00006 public: 00007 TCP(component::Interface* ci, const pyconfig::View& _pyco) : 00008 pyco(_pyco), 00009 ip(NULL), 00010 componentInterface(ci), 00011 name(_pyco.get<std::string>("name")) 00012 { 00013 } 00014 00015 // TCPInterface 00016 int getFreePort() 00017 { 00018 // Return always 31337 (only for tests) 00019 return 31337; 00020 } 00021 00022 // TCPInterface 00023 std::string 00024 getAddressOfIPInstance() 00025 { 00026 return ip->getAddress(); 00027 } 00028 00029 // ComponentInterface 00030 void onNodeCreated() 00031 { 00032 ip = componentInterface->getService<IPInterface*>(pyco.get<std::string>("ipInstance")); 00033 } 00034 00035 void onWorldCreated() 00036 { 00037 // No inter-node dependecies. 00038 } 00039 00040 virtual std::string 00041 getName() 00042 { 00043 return name; 00044 } 00045 00046 private: 00047 wns::pyconfig::View pyco; 00048 // The IP instance below this TCP instance 00049 IPInterface* ip; 00050 component::Interface* componentInterface; 00051 std::string name; 00052 };
Let's see what we can learn from the three methods that have been implemented here:
TCP::getFreePort():
This one is easy. For testing purpose we return an integer. Nothing special.
TCP::getAddressOfIPInstance():
This one is a little bit more difficult. From the name we can guess that we need an instance of something conforming to the IPInterface since we need to ask this instance for its address.
The question is now: How do we find such an instance? We know that the instance must be part of the same Node that the instance of TCP is currently being part of.
At this point the method TCP::onNodeCreated() comes into play. Normally components are not added from outside to a Node but the Node creates all necessary components in its constructor. After all components have been created the method component::Interface::onNodeCreated() of each component is called. Hence, this is the point where a component can try to find other components within its parent Node. You resolve inter Node dependencies only after all Nodes have been constructed and all components have executed their onNodeCreated method. This is the time instance when the onWorldCreated() method is called.
The Node provides special methods to retrieve components from itself.
Retrieve a Component from a Node
A Component can be retrieved from a Node by its name (like a IP instance):
00001 00002 // retrieve TCP instance from Node 00003 component::tests::TCPInterface* tcp = 00004 node->getService<component::tests::TCPInterface*>("TCP");
| typedef wns::container::Registry<std::string, node::Interface*> wns::node::Registry |
Definition at line 35 of file Registry.hpp.
1.5.5