1868 lines
78 KiB
Plaintext
1868 lines
78 KiB
Plaintext
@c ========================================================================
|
|
@c Other Network Topologies
|
|
@c ========================================================================
|
|
|
|
@node Other-network-topologies
|
|
@chapter Other Network Topologies
|
|
@cindex topology
|
|
@cindex Channel
|
|
@cindex NetDevice
|
|
@cindex topology!bus
|
|
@cindex topology!point-to-point
|
|
@cindex PointToPointChannel
|
|
@cindex PointToPointNetDevice
|
|
|
|
@emph{Network topology} is the study of the arrangement of of the elements
|
|
(in @command{ns-3} represented by the classes @code{Channel} and @code{Node})
|
|
of a network. Two fundamental types of physical topologies are the
|
|
@emph{point-to-point} and @emph{bus} topologies. We have already been exposed
|
|
to the @command{ns-3} channel specialization named @code{CsmaChannel}. This is
|
|
a simulation of a bus network. We also provide a simulation of a
|
|
point-to-point channel with associated net devices. As described previously,
|
|
the associated C++ classes specialize the @command{ns-3} base classes
|
|
@code{NetDevice} and @code{Channel} and are called @code{PointToPointNetDevice}
|
|
and @code{PointToPointChannel} respectively.
|
|
|
|
We will use combinations of these bus and point-to-point topology elements
|
|
to show how to create several commonly seen network topologies.
|
|
|
|
@section A Point-to-Point Network
|
|
We're going to take what might be seen as a step backward and look at a simple
|
|
point-to-point network. We will be building the simplest network you can
|
|
imagine. A serial link (point to point) between two computers. When you
|
|
see this point-to-point network, you can think of an RS-422 (or RS-232 for
|
|
you old-timers) cable. This topology is shown below.
|
|
|
|
@sp 1
|
|
@center @image{pp,,,,png}
|
|
|
|
We have provided a file for you in the @code{tutorial}
|
|
directory called @code{point-to-point.cc}. You should now be familiar enough
|
|
with the system to pick out fairly easily what has been changed. Let's focus
|
|
on the following lines:
|
|
|
|
@verbatim
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
Ptr<Node> n1 = Create<InternetNode> ();
|
|
|
|
Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
|
|
n0, n1, DataRate (38400), MilliSeconds (20));
|
|
|
|
PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
|
|
n1, "10.1.1.2");
|
|
@end verbatim
|
|
|
|
You can see that we created two @code{InternetNode} objects in the usual way.
|
|
Then, instead of creating a @code{CsmaChannel} we create a
|
|
@code{PointToPointChannel}. This point-to-point channel, which we call
|
|
@code{link}, connects node zero (@code{n0}) and node one (@code{n1}) over a
|
|
simulated link that runs at 38400 bits per second and has a 20 millisecond
|
|
simulated speed-of-light delay. This call also creates appropriate net devices
|
|
and attaches them to nodes zero and one.
|
|
|
|
We then add IP addresses to the net devices we just created using the topology
|
|
helper @code{AddIpv4Addresses}. Node zero gets the IP address 10.1.1.1 and
|
|
node one gets the IP address 10.1.1.2 assigned.
|
|
|
|
The alert tutorial user may wonder what the network number or prefix is of
|
|
those IP addresses. The point-to-point topology assumes that you want a
|
|
@code{/30} subnet and assigns an appropriate net mask for you. It then then
|
|
@emph{asserts} that the network numbers of the two net devices match. So there
|
|
is an implicit network mask created down in the topology code that looks like,
|
|
|
|
@verbatim
|
|
Ipv4Mask netmask("255.255.255.252");
|
|
@end verbatim
|
|
|
|
The rest of the code you should recognize and understand. We are just going
|
|
to echo one packet across the point-to-point link. You should be now be able
|
|
to build and run this example and to locate and interpret the ASCII trace
|
|
file. This is left as an exercise for you.
|
|
|
|
The file @code{point-to-point.cc} is reproduced here for your
|
|
convenience:
|
|
|
|
@verbatim
|
|
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation;
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "ns3/log.h"
|
|
#include "ns3/ptr.h"
|
|
#include "ns3/internet-node.h"
|
|
#include "ns3/point-to-point-channel.h"
|
|
#include "ns3/mac48-address.h"
|
|
#include "ns3/point-to-point-net-device.h"
|
|
#include "ns3/point-to-point-topology.h"
|
|
#include "ns3/udp-echo-client.h"
|
|
#include "ns3/udp-echo-server.h"
|
|
#include "ns3/simulator.h"
|
|
#include "ns3/nstime.h"
|
|
#include "ns3/ascii-trace.h"
|
|
#include "ns3/pcap-trace.h"
|
|
#include "ns3/global-route-manager.h"
|
|
|
|
NS_LOG_COMPONENT_DEFINE ("PointToPointSimulation");
|
|
|
|
using namespace ns3;
|
|
|
|
// Network topology
|
|
//
|
|
// point to point
|
|
// +--------------+
|
|
// | |
|
|
// n0 n1
|
|
//
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
LogComponentEnable ("PointToPointSimulation", LOG_LEVEL_INFO);
|
|
|
|
NS_LOG_INFO ("Point to Point Topology Simulation");
|
|
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
Ptr<Node> n1 = Create<InternetNode> ();
|
|
|
|
Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
|
|
n0, n1, DataRate (38400), MilliSeconds (20));
|
|
|
|
PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
|
|
n1, "10.1.1.2");
|
|
|
|
uint16_t port = 7;
|
|
|
|
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
|
|
1, Seconds(1.), 1024);
|
|
|
|
Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);
|
|
|
|
server->Start(Seconds(1.));
|
|
client->Start(Seconds(2.));
|
|
|
|
server->Stop (Seconds(10.));
|
|
client->Stop (Seconds(10.));
|
|
|
|
AsciiTrace asciitrace ("tutorial.tr");
|
|
asciitrace.TraceAllQueues ();
|
|
asciitrace.TraceAllNetDeviceRx ();
|
|
|
|
Simulator::Run ();
|
|
Simulator::Destroy ();
|
|
}
|
|
@end verbatim
|
|
|
|
@section A Star Network
|
|
A point-to-point network is considered a special case of a star network. As
|
|
you might expect, the process of constructing a star network is an extension
|
|
of the very simple process used for a point-to-point link. We have provided
|
|
a file for you in the @code{tutorial} directory called @code{star.cc}
|
|
that implements a simple star network as seen below.
|
|
|
|
@sp 1
|
|
@center @image{star,,,,png}
|
|
|
|
In order to create a star network, we need to be able to instantiate some
|
|
number (greater than one) of net devices on a node. In the name of simplicity
|
|
of use, the @code{PointToPointTopology} topology helper does not allow one to
|
|
do this. We provided a separate topology helper class, the
|
|
@code{PointToPointIpv4Topology} helper class that provides the slightly finer
|
|
granularity we need to accomplish a star network. In order to use this new
|
|
helper we have to load the definitions by including the appropriate file.
|
|
|
|
@verbatim
|
|
#include "ns3/point-to-point-ipv4-topology.h"
|
|
@end verbatim
|
|
|
|
The star that we're going to create has a node in the center (@code{n0}) with
|
|
six nodes surrounding (@code{n1} - @code{n6}). You should be able to easily
|
|
find and understand the code that creates these nodes.
|
|
|
|
@verbatim
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
Ptr<Node> n1 = Create<InternetNode> ();
|
|
Ptr<Node> n2 = Create<InternetNode> ();
|
|
Ptr<Node> n3 = Create<InternetNode> ();
|
|
Ptr<Node> n4 = Create<InternetNode> ();
|
|
Ptr<Node> n5 = Create<InternetNode> ();
|
|
Ptr<Node> n6 = Create<InternetNode> ();
|
|
@end verbatim
|
|
|
|
Next, we get into the differences between the @code{PointToPointTopology}
|
|
helper and the @code{PointToPointIpv4Topology} helper. The
|
|
@code{PointToPointIpv4Topology} helper looks and feels a little like the
|
|
@code{CsmaIpv4Topology} helper. Just like you created a CSMA channel
|
|
previously, you need to create a point-to-point channel. The following
|
|
code creates a @code{PointToPointChannel} and calls it @code{link01}. You can
|
|
interpret this name as being the channel (or @emph{link}) from node zero to
|
|
node one.
|
|
|
|
@verbatim
|
|
Ptr<PointToPointChannel> link01 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
@end verbatim
|
|
|
|
You need to provide a data rate for the channel which we set at 38400 bits
|
|
per second. You must also provide a speed-of-light delay which we set at
|
|
20 milliseconds.
|
|
|
|
Just as you added a net device to the nodes in the CSMA tutorial section, you
|
|
do the same here but with a point-to-point net device. The following code
|
|
illustrates how we do that:
|
|
|
|
@verbatim
|
|
uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
|
|
link01);
|
|
@end verbatim
|
|
|
|
We call the @code{PointToPointIpv4Topology} helper and ask it to add a net
|
|
device to node zero (@code{n0}) and connect it to the appropriate
|
|
point-to-point link (@code{link01}) which you will recall is the serial link
|
|
from node zero to node one.
|
|
|
|
If you look at the following code, you will see the same calls are repeated
|
|
to create the remaining five point-to-point channels and connect them
|
|
to net devices on node zero.
|
|
|
|
The next new code is found after the ``spokes'' of the star have been created.
|
|
It looks like the following:
|
|
|
|
@verbatim
|
|
uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
|
|
uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
|
|
uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
|
|
uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
|
|
uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
|
|
uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);
|
|
@end verbatim
|
|
|
|
Here we are creating the net devices on the nodes surrounding the center node.
|
|
In the first call, we are adding a net device on node one (@code{n1}) and
|
|
connecting that net device to the channel named @code{link01}. Remember that
|
|
we created the channel @code{link01} as the channel connecting node zero and
|
|
node one. We previously created a net device on node zero and attached that
|
|
device to @code{link01}. Here we are connecting the other side of that link
|
|
to node one. The return value from this call is the net device index of the
|
|
created net device.
|
|
|
|
The next section of code adds addresses to the net devices we just created.
|
|
The first call adds the IP address 10.1.1.1 to the net device going from
|
|
node zero to node one. Recall that we first created a node named @code{n0}
|
|
and a channel called @code{link01}. We added a net device to @code{n0} and
|
|
remembered the net device index as the @code{uint32_t nd01}. This meant
|
|
the net device @emph{nd} on node @emph{0} that we connected to node @emph{1}.
|
|
We call @code{AddAddress} to add an IP address (10.1.1.1) to the net device
|
|
on node zero identified by the net device index @code{nd01}. We provide a
|
|
net mask suitable for a point to point network. This is typically a /30
|
|
address but we don't force that in this API.
|
|
|
|
After setting up the address on node zero, we do the same for the node on
|
|
the other end of the ``spoke'' --- in this case node one, with its single
|
|
net device. Note that the network number is the same on both sides of this
|
|
network.
|
|
|
|
@verbatim
|
|
PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
|
|
``255.255.255.252'');
|
|
|
|
PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
|
|
``255.255.255.252'');
|
|
@end verbatim
|
|
|
|
The following code repeats this pattern assining similar IP addresses to the
|
|
remaining net devices. Note that there are no @code{Mac48Address} address
|
|
assignments --- they are not required.
|
|
|
|
The rest of the code you should recognize and understand. We are just going
|
|
to echo one packet across the point-to-point link. You should be now be able
|
|
to build and run this example and to locate and interpret the ASCII trace
|
|
file. This is left as an exercise for you.
|
|
|
|
The file @code{star.cc} is reproduced here for your convenience:
|
|
|
|
@verbatim
|
|
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation;
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "ns3/log.h"
|
|
#include "ns3/ptr.h"
|
|
#include "ns3/internet-node.h"
|
|
#include "ns3/point-to-point-channel.h"
|
|
#include "ns3/mac48-address.h"
|
|
#include "ns3/point-to-point-net-device.h"
|
|
#include "ns3/point-to-point-ipv4-topology.h"
|
|
#include "ns3/udp-echo-client.h"
|
|
#include "ns3/udp-echo-server.h"
|
|
#include "ns3/simulator.h"
|
|
#include "ns3/nstime.h"
|
|
#include "ns3/ascii-trace.h"
|
|
#include "ns3/pcap-trace.h"
|
|
#include "ns3/global-route-manager.h"
|
|
|
|
NS_LOG_COMPONENT_DEFINE ("StarSimulation");
|
|
|
|
using namespace ns3;
|
|
|
|
// Network topology
|
|
//
|
|
// n3 n2
|
|
// | /
|
|
// | /
|
|
// n4 --- n0 --- n1
|
|
// / |
|
|
// / |
|
|
// n5 n6
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
LogComponentEnable ("StarSimulation", LOG_LEVEL_INFO);
|
|
|
|
NS_LOG_INFO ("Star Topology Simulation");
|
|
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
Ptr<Node> n1 = Create<InternetNode> ();
|
|
Ptr<Node> n2 = Create<InternetNode> ();
|
|
Ptr<Node> n3 = Create<InternetNode> ();
|
|
Ptr<Node> n4 = Create<InternetNode> ();
|
|
Ptr<Node> n5 = Create<InternetNode> ();
|
|
Ptr<Node> n6 = Create<InternetNode> ();
|
|
|
|
Ptr<PointToPointChannel> link01 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
|
|
uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
|
|
link01);
|
|
|
|
Ptr<PointToPointChannel> link02 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
|
|
uint32_t nd02 = PointToPointIpv4Topology::AddNetDevice (n0,
|
|
link02);
|
|
|
|
Ptr<PointToPointChannel> link03 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
|
|
uint32_t nd03 = PointToPointIpv4Topology::AddNetDevice (n0,
|
|
link03);
|
|
|
|
Ptr<PointToPointChannel> link04 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
|
|
uint32_t nd04 = PointToPointIpv4Topology::AddNetDevice (n0,
|
|
link04);
|
|
|
|
Ptr<PointToPointChannel> link05 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
|
|
uint32_t nd05 = PointToPointIpv4Topology::AddNetDevice (n0,
|
|
link05);
|
|
|
|
Ptr<PointToPointChannel> link06 =
|
|
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
|
|
MilliSeconds (20));
|
|
|
|
uint32_t nd06 = PointToPointIpv4Topology::AddNetDevice (n0, link06);
|
|
|
|
uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
|
|
uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
|
|
uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
|
|
uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
|
|
uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
|
|
uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);
|
|
|
|
PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n0, nd02, "10.1.2.1",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n2, nd2, "10.1.2.2",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n0, nd03, "10.1.3.1",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n3, nd3, "10.1.2.2",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n0, nd04, "10.1.4.1",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n4, nd4, "10.1.4.2",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n0, nd05, "10.1.5.1",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n5, nd5, "10.1.5.2",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n0, nd06, "10.1.6.1",
|
|
"255.255.255.252");
|
|
|
|
PointToPointIpv4Topology::AddAddress (n6, nd6, "10.1.6.2",
|
|
"255.255.255.252");
|
|
|
|
uint16_t port = 7;
|
|
|
|
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n0, "10.1.1.2", port,
|
|
1, Seconds(1.), 1024);
|
|
|
|
Ptr<UdpEchoServer> server = Create<UdpEchoServer> (n1, port);
|
|
|
|
server->Start(Seconds(1.));
|
|
client->Start(Seconds(2.));
|
|
|
|
server->Stop (Seconds(10.));
|
|
client->Stop (Seconds(10.));
|
|
|
|
AsciiTrace asciitrace ("tutorial.tr");
|
|
asciitrace.TraceAllQueues ();
|
|
asciitrace.TraceAllNetDeviceRx ();
|
|
|
|
Simulator::Run ();
|
|
Simulator::Destroy ();
|
|
}
|
|
@end verbatim
|
|
|
|
@subsection Routing
|
|
If you are really excited about this simulator you may have already tried to
|
|
modify the scripts outside the tutorial. I know that one of the first things
|
|
that would have occurred to me when I saw the star network would have been to
|
|
start trying to add applications to echo packets from nodes other than zero.
|
|
If you tried, for example, to start the echo client on node one instead of
|
|
node zero, you would have found an empty trace file. The reason for this
|
|
is that you have now created an internetwork. This means you will need to
|
|
enable internetwork routing.
|
|
|
|
We have provided a file for you in the @code{tutorial} directory called
|
|
@code{star-routing.cc} to show you how this is done. This extremely
|
|
tricky and difficult change is shown below:
|
|
|
|
@verbatim
|
|
GlobalRouteManager::PopulateRoutingTables ();
|
|
@end verbatim
|
|
|
|
This one-line addition, located just before the simulation runs, tells the
|
|
@command{ns-3} @emph{global route manager} to walk the topology you created and
|
|
build internetwork routing tables for all of the nodes in the simulation.
|
|
We changed the client application so that it runs on node four:
|
|
|
|
@verbatim
|
|
Ptr<UdpEchoClient> client = Create<UdpEchoClient> (n4, "10.1.1.2", port,
|
|
1, Seconds(1.), 1024);
|
|
@end verbatim
|
|
|
|
Now if you build and run @code{star-routing.cc} you can examine the
|
|
@code{tutorial.tr} file and see that your UDP echo packets are now correctly
|
|
routed through the topology.
|
|
|
|
@section A Dumbbell Network
|
|
One of the most interesting simple topologies (from a phenomenological point of
|
|
view) is commonly called a dumbbell network. The name derives from a
|
|
superficial similarity in form to a piece of exercise equipment.
|
|
|
|
The dumbbell model is typically composed of two bus or star network elements
|
|
connected via a point-to-point link. The point-to-point link is usually
|
|
configured with a lower bandwidth than the bus elements to provide a
|
|
@emph{choke point}.
|
|
|
|
The following is a representation of the topology.
|
|
|
|
@sp 1
|
|
@center @image{dumbbell,,,,png}
|
|
|
|
We have provided a file that constructs this dumbbell network and creates
|
|
enough data flowing across the choke point that some packets will be dropped.
|
|
The file is called @code{linear-dumbbell.cc} and is located in the
|
|
@code{tutorial} directory. We have already covered all of the code used to
|
|
create this network, so we will just quickly go over the main sections of the
|
|
script.
|
|
|
|
The first section creates a CSMA lan that will become the left side of the
|
|
dumbbell network. This code should be very familiar since we used the same
|
|
process to create our first example.
|
|
|
|
@verbatim
|
|
//
|
|
// Create the lan on the left side of the dumbbell.
|
|
//
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
Ptr<Node> n1 = Create<InternetNode> ();
|
|
Ptr<Node> n2 = Create<InternetNode> ();
|
|
Ptr<Node> n3 = Create<InternetNode> ();
|
|
|
|
Ptr<CsmaChannel> lan1 =
|
|
CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));
|
|
|
|
uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan1,
|
|
"08:00:2e:00:00:00");
|
|
|
|
uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan1,
|
|
"08:00:2e:00:00:01");
|
|
|
|
uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan1,
|
|
"08:00:2e:00:00:02");
|
|
|
|
uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan1,
|
|
"08:00:2e:00:00:03");
|
|
|
|
CsmaIpv4Topology::AddIpv4Address (n0, nd0, "10.1.1.1", "255.255.255.0");
|
|
CsmaIpv4Topology::AddIpv4Address (n1, nd1, "10.1.1.2", "255.255.255.0");
|
|
CsmaIpv4Topology::AddIpv4Address (n2, nd2, "10.1.1.3", "255.255.255.0");
|
|
CsmaIpv4Topology::AddIpv4Address (n3, nd3, "10.1.1.4", "255.255.255.0");
|
|
@end verbatim
|
|
|
|
The code to generate the CSMA lan on the right side is similar; only the names
|
|
have been changed.
|
|
|
|
@verbatim
|
|
//
|
|
// Create the lan on the right side of the dumbbell.
|
|
//
|
|
Ptr<Node> n4 = Create<InternetNode> ();
|
|
Ptr<Node> n5 = Create<InternetNode> ();
|
|
Ptr<Node> n6 = Create<InternetNode> ();
|
|
Ptr<Node> n7 = Create<InternetNode> ();
|
|
|
|
Ptr<CsmaChannel> lan2 =
|
|
CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));
|
|
|
|
uint32_t nd4 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n4, lan2,
|
|
"08:00:2e:00:00:04");
|
|
|
|
uint32_t nd5 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n5, lan2,
|
|
"08:00:2e:00:00:05");
|
|
|
|
uint32_t nd6 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n6, lan2,
|
|
"08:00:2e:00:00:06");
|
|
|
|
uint32_t nd7 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n7, lan2,
|
|
"08:00:2e:00:00:07");
|
|
|
|
CsmaIpv4Topology::AddIpv4Address (n4, nd4, "10.1.2.1", "255.255.255.0");
|
|
CsmaIpv4Topology::AddIpv4Address (n5, nd5, "10.1.2.2", "255.255.255.0");
|
|
CsmaIpv4Topology::AddIpv4Address (n6, nd6, "10.1.2.3", "255.255.255.0");
|
|
CsmaIpv4Topology::AddIpv4Address (n7, nd7, "10.1.2.4", "255.255.255.0");
|
|
@end verbatim
|
|
|
|
Next, we create a point to point link to connect the two lans. We connect
|
|
the point-to-point channel between nodes three (on the left lan) and four
|
|
(on the right lan). You should recoginze this as substantially similar to
|
|
the link setup from the @code{point-to-point} example.
|
|
|
|
@verbatim
|
|
//
|
|
// Create the point-to-point link to connect the two lans.
|
|
//
|
|
Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
|
|
n3, n4, DataRate (38400), MilliSeconds (20));
|
|
|
|
PointToPointTopology::AddIpv4Addresses (link, n3, "10.1.3.1",
|
|
n4, "10.1.3.2");
|
|
@end verbatim
|
|
|
|
Then we configure data flows. We create four echo clients that send UDP
|
|
packets from the left side lan to servers created on the right side lan.
|
|
Notice that we send 100 packets with an inter-packet gap of ten milliseconds
|
|
instead of the single packet we have previously used. This data rate is
|
|
sufficient to saturate the point-to-point link and will cause packets to be
|
|
dropped when the queue on the link net devices overflows (the default maximum
|
|
queue depth is 100 packets). Note that we stagger the start of the echo
|
|
clients to slowly bring up the data rates.
|
|
|
|
@verbatim
|
|
//
|
|
// Create data flows across the link:
|
|
// n0 ==> n4 ==> n0
|
|
// n1 ==> n5 ==> n1
|
|
// n2 ==> n6 ==> n2
|
|
// n3 ==> n7 ==> n3
|
|
//
|
|
uint16_t port = 7;
|
|
|
|
Ptr<UdpEchoClient> client0 = Create<UdpEchoClient> (n0, "10.1.2.1", port,
|
|
100, Seconds(.01), 1024);
|
|
Ptr<UdpEchoClient> client1 = Create<UdpEchoClient> (n1, "10.1.2.2", port,
|
|
100, Seconds(.01), 1024);
|
|
Ptr<UdpEchoClient> client2 = Create<UdpEchoClient> (n2, "10.1.2.3", port,
|
|
100, Seconds(.01), 1024);
|
|
Ptr<UdpEchoClient> client3 = Create<UdpEchoClient> (n3, "10.1.2.4", port,
|
|
100, Seconds(.01), 1024);
|
|
|
|
Ptr<UdpEchoServer> server4 = Create<UdpEchoServer> (n4, port);
|
|
Ptr<UdpEchoServer> server5 = Create<UdpEchoServer> (n5, port);
|
|
Ptr<UdpEchoServer> server6 = Create<UdpEchoServer> (n6, port);
|
|
Ptr<UdpEchoServer> server7 = Create<UdpEchoServer> (n7, port);
|
|
|
|
server4->Start(Seconds(1.));
|
|
server5->Start(Seconds(1.));
|
|
server6->Start(Seconds(1.));
|
|
server7->Start(Seconds(1.));
|
|
|
|
client0->Start(Seconds(2.));
|
|
client1->Start(Seconds(2.1));
|
|
client2->Start(Seconds(2.2));
|
|
client3->Start(Seconds(2.3));
|
|
|
|
server4->Stop (Seconds(10.));
|
|
server5->Stop (Seconds(10.));
|
|
server6->Stop (Seconds(10.));
|
|
server7->Stop (Seconds(10.));
|
|
|
|
client0->Stop (Seconds(10.));
|
|
client1->Stop (Seconds(10.));
|
|
client2->Stop (Seconds(10.));
|
|
client3->Stop (Seconds(10.));
|
|
@end verbatim
|
|
|
|
The remainder of the file should be quite familiar to you. Go ahead and
|
|
run @code{linear-dumbbell}. Now take a look at the trace (@code{tutorial.tr})
|
|
file. You will now see trace lines that begin with @code{d}. Alternatively
|
|
you can search for the string ``queue-drop'' which is the expansion of the
|
|
drop code.
|
|
|
|
Interpretation of a dropped packet is straightforward. We have expanded
|
|
the first @code{queue-drop} trace for you below. See the section on ASCII
|
|
tracing for details.
|
|
|
|
@verbatim
|
|
00 d
|
|
01 2.40938
|
|
02 nodeid=3
|
|
03 device=1
|
|
04 queue-drop
|
|
05 pkt-uid=124
|
|
06 LLCSNAP(type 0x800)
|
|
07 IPV4(
|
|
08 tos 0x0
|
|
09 ttl 63
|
|
10 id 20
|
|
11 offset 0
|
|
12 flags [none]
|
|
13 length: 1052) 10.1.1.3 > 10.1.2.3
|
|
14 UDP(length: 1032)
|
|
15 49153 > 7
|
|
16 DATA (length 1024)
|
|
@end verbatim
|
|
|
|
We leave it as an exercise to examine the trace files in more detail.
|
|
|
|
@c ========================================================================
|
|
@c Nonlinear Thinking
|
|
@c ========================================================================
|
|
|
|
@node Nonlinear-Thinking
|
|
@chapter Nonlinear Thinking
|
|
|
|
One thing that all of our examples so far have in common is that they are
|
|
composed of a linear collection of calls into the @command{ns-3} system. The
|
|
programmers among the readers may have wondered why there is not as much
|
|
as a for-loop in all of the examples. The answer is that we wanted to
|
|
introduce you to @command{ns-3} scripting with a minimum of conceptual
|
|
overhead. We're going to remedy that situation shortly.
|
|
|
|
We have written a number of @command{ns-3} scripts in C++. Although we have
|
|
been perfectly linear in our script implementations, just like any other C++
|
|
program, an @command{ns-3} script can use any features of the language you
|
|
desire. If you will look back at the @code{linear-dumbbell.cc}
|
|
example, you may notice that the code to create the left and right sides of
|
|
the dumbbell is operationally identical --- only the names change. An obvious
|
|
improvement of this program would be to use subroutines to create the sides.
|
|
Since we are working with C++, we should probably do this in an
|
|
object-oriented way. Since object-oriented design is somewhat of a black art
|
|
to some people, we'll take some time here and outline a simple methodology
|
|
you can follow.
|
|
|
|
@section Object Design 101 --- Class Ipv4BusNetwork
|
|
If you are a master of object oriented design, feel free to skip or skim this
|
|
section, in which we derive a simplistic but fully operational bus network
|
|
class.
|
|
|
|
So you want to create a BusNetwork class. Often the biggest hurdle in a
|
|
design is figuring out how to get started. One of the simplest and most
|
|
straightforward ways to do an object decomposition of a problem is to simply
|
|
write down a description of the problem and take a look at the words
|
|
you used. Let's take some time and do that, first at a very high level.
|
|
|
|
@example
|
|
A bus network is an implementation of a particular network topology that
|
|
contains some number of nodes. Each of these nodes is attached to a single
|
|
multi-drop channel. The network itself has some attributes independent of
|
|
the topology such as a network mask, network number (prefix) and base IP
|
|
address.
|
|
@end example
|
|
|
|
The first thing to do is to focus on the nouns and adjectives. These will
|
|
give you a starting point for required classes and member variables.
|
|
|
|
Immediately we can notice that at the highest level we are talking about the
|
|
noun @emph{network}. This probably won't surprise you. We also have an
|
|
adjective that modifies the noun --- @emph{bus}. This should lead us to our
|
|
first class defintion. Usually class names are constructed in the same way
|
|
as an English language sentence would be spoken. For example, one would speak
|
|
of a @emph{bus network} in conversation, so we would normally create a
|
|
@code{class BusNetwork} to represent it.
|
|
|
|
One thing to note is that we have used two words in our description quite
|
|
naturally: @emph{is} and @emph{has}. When you see these words should should
|
|
immediately think of the object-oriented concepts of @emph{ISA} (inheritance)
|
|
and @emph{HASA} (containment) respectively. We wrote that a bus network
|
|
@emph{is} an implementation of a particular network topology. Perhaps you
|
|
will agree that there is a natural base class called @code{Network} that
|
|
@emph{has} the attributes discussed above. The fact that a @code{BusNetwork}
|
|
@emph{ISA} kind of @code{Network} suggests inheritance. Let's capture that
|
|
thought right away remembering that we're focused on IP version four here:
|
|
|
|
@verbatim
|
|
class Ipv4Network
|
|
{
|
|
public:
|
|
Ipv4Address m_network;
|
|
Ipv4Mask m_mask;
|
|
Ipv4Address m_baseAddress;
|
|
};
|
|
|
|
class Ipv4BusNetwork : public Ipv4Network
|
|
{
|
|
};
|
|
@end verbatim
|
|
|
|
Let's take a look at the @emph{HASA} relationships of the bus network. Clearly
|
|
it will @emph{have} a reference to the underlying channel that implements the
|
|
actual communications medium. We use smart pointers for those references, so
|
|
one member variable is obvious:
|
|
|
|
@verbatim
|
|
Ptr<CsmaChannel> m_channel;
|
|
@end verbatim
|
|
|
|
A bus network will also need to contain references to all of the nodes we
|
|
eventually want to create. If you are working in C++ and see the words contain
|
|
or container, you should immediately think of the Standard Template Library
|
|
or STL. A quick search of the available containers there will probably lead
|
|
you to consider the vector class. A vector is a container that looks like an
|
|
array. This is just what we need here. Again, we want to use smart pointers
|
|
to reference our nodes, so the declaration of the vector would look like,
|
|
|
|
@verbatim
|
|
std::vector<Ptr<Node> > m_nodes;
|
|
@end verbatim
|
|
|
|
It will save you headaches in the future if you notice that the space between
|
|
the two right brackets is required to differentiate this situation from a
|
|
right-shift operator. So we have a pretty good start already after just a
|
|
little work. Now we need to turn our attention to actions. Let's write
|
|
another little description of the things you consider doing to a Bus network.
|
|
|
|
@example
|
|
We need to be able to create a bus network. We need to be able to delete a
|
|
bus network. We need to be able to get a handle to a node in order to add
|
|
applications. We need to be able to set the network, mask and base address
|
|
somehow, specify how many nodes to create and provide the underlying channel
|
|
its required bandwidth and delay parameters.
|
|
@end example
|
|
|
|
We now look at the @emph{verbs} in that sentence. These will give a good
|
|
starting point for the methods of the classes. For example, the verbs
|
|
@emph{create} and @emph{delete} should suggest @emph{constructor} and
|
|
@emph{destructor}. The verb @emph{get} leads us to providing a method called
|
|
@code{GetNode}. We have to provide a number of parameters so we can either
|
|
provide @emph{setters} or we can simply pass them in as parameters to our
|
|
constructors. Since this is a simple example, we won't bother to implement
|
|
getters and setters (methods to get and set member variables to enhance data
|
|
hiding). Let's use this guidance to finish up our class declarations:
|
|
|
|
@verbatim
|
|
class Ipv4Network
|
|
{
|
|
public:
|
|
Ipv4Network (Ipv4Address network, Ipv4Mask mask, Ipv4Address address);
|
|
virtual ~Ipv4Network ();
|
|
|
|
Ipv4Address m_network;
|
|
Ipv4Mask m_mask;
|
|
Ipv4Address m_baseAddress;
|
|
};
|
|
|
|
class Ipv4BusNetwork : public Ipv4Network
|
|
{
|
|
public:
|
|
Ipv4BusNetwork (
|
|
Ipv4Address network,
|
|
Ipv4Mask mask,
|
|
Ipv4Address startAddress,
|
|
DataRate bps,
|
|
Time delay,
|
|
uint32_t n);
|
|
|
|
virtual ~Ipv4BusNetwork ();
|
|
|
|
Ptr<Node> GetNode (uint32_t n);
|
|
|
|
private:
|
|
std::vector<Ptr<Node> > m_nodes;
|
|
Ptr<CsmaChannel> m_channel;
|
|
};
|
|
@end verbatim
|
|
|
|
That's it. We have actually already walked through almost all of the code
|
|
required to construct a bus network in our @code{csma-echo.cc}
|
|
example, so let's just jump forward and take a look at an implementation
|
|
of this thing. We provide an implementation for you in the files
|
|
@code{ipv4-bus-network.h} and @code{ipv4-bus-network.cc} located in the
|
|
@code{tutorial} directory. We also provide an example that uses the new
|
|
class in the file @code{bus-network.cc}.
|
|
|
|
The interesting method from our current perspective is the Ipv4BusNetwork
|
|
constructor, shown below:
|
|
|
|
@verbatim
|
|
Ipv4BusNetwork::Ipv4BusNetwork (
|
|
Ipv4Address network,
|
|
Ipv4Mask mask,
|
|
Ipv4Address baseAddress,
|
|
DataRate bps,
|
|
Time delay,
|
|
uint32_t n)
|
|
:
|
|
Ipv4Network (network, mask, baseAddress)
|
|
{
|
|
Ipv4AddressGenerator::SeedNetwork (mask, network);
|
|
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
|
|
|
|
m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);
|
|
|
|
for (uint32_t i = 0; i < n; ++i)
|
|
{
|
|
Ptr<Node> node = Create<InternetNode> ();
|
|
uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
|
|
Mac48Address::Allocate ());
|
|
Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
|
|
network);
|
|
CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
|
|
m_nodes.push_back (node);
|
|
}
|
|
}
|
|
@end verbatim
|
|
|
|
Notice that we do the simple and straightforward thing and pass all of our
|
|
parameters to the constructor. For those unfamiliar with C++, the line after
|
|
the colon and before the opening brace (shown below),
|
|
|
|
@verbatim
|
|
:
|
|
Ipv4Network (network, mask, baseAddress)
|
|
{
|
|
@end verbatim
|
|
|
|
Passes the appropriate parameters to the constructor of the base class
|
|
@code{Ipv4Network}. There are two new calls that we haven't seen immediately
|
|
after this initialization. They are:
|
|
|
|
@verbatim
|
|
Ipv4AddressGenerator::SeedNetwork (mask, network);
|
|
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
|
|
@end verbatim
|
|
|
|
We provide an IP address generator class to allow us to programatically
|
|
allocate IP addresses. The first call to @code{SeedNetwork} gives the
|
|
address generator a starting network number to use when generating addresses.
|
|
The second call to @code{SeedAddress} gives the address generator a starting
|
|
IP address to use. There is a starting network and starting address for each
|
|
of the 32 possible network masks. Later in the for loop, you will see a
|
|
call to @code{AllocateAddress} in which the IP address for each node created
|
|
in the loop is actually generated.
|
|
|
|
The only unfamiliar call in the reset of the constructor will be:
|
|
|
|
@verbatim
|
|
m_nodes.push_back (node);
|
|
@end verbatim
|
|
|
|
This is the STL code to add the newly created node to the vector of nodes
|
|
attached to the bus.
|
|
|
|
For your convenience, we reproduce the entire bus network implementation below:
|
|
|
|
@verbatim
|
|
/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
|
|
/*
|
|
* Copyright (c) 2007 University of Washington
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation;
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "ns3/mac48-address.h"
|
|
#include "ns3/csma-net-device.h"
|
|
#include "ns3/csma-topology.h"
|
|
#include "ns3/csma-ipv4-topology.h"
|
|
|
|
#include "ipv4-bus-network.h"
|
|
#include "ipv4-address-generator.h"
|
|
|
|
namespace ns3 {
|
|
|
|
Ipv4Network::Ipv4Network (
|
|
Ipv4Address network,
|
|
Ipv4Mask mask,
|
|
Ipv4Address address)
|
|
:
|
|
m_network (network), m_mask (mask), m_baseAddress (address)
|
|
{
|
|
}
|
|
|
|
Ipv4Network::~Ipv4Network ()
|
|
{
|
|
}
|
|
|
|
Ipv4BusNetwork::Ipv4BusNetwork (
|
|
Ipv4Address network,
|
|
Ipv4Mask mask,
|
|
Ipv4Address baseAddress,
|
|
DataRate bps,
|
|
Time delay,
|
|
uint32_t n)
|
|
:
|
|
Ipv4Network (network, mask, baseAddress)
|
|
{
|
|
Ipv4AddressGenerator::SeedNetwork (mask, network);
|
|
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
|
|
|
|
m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);
|
|
|
|
for (uint32_t i = 0; i < n; ++i)
|
|
{
|
|
Ptr<Node> node = Create<InternetNode> ();
|
|
uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
|
|
Mac48Address::Allocate ());
|
|
Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
|
|
network);
|
|
CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
|
|
m_nodes.push_back (node);
|
|
}
|
|
}
|
|
|
|
Ipv4BusNetwork::~Ipv4BusNetwork ()
|
|
{
|
|
}
|
|
|
|
Ptr<Node>
|
|
Ipv4BusNetwork::GetNode (uint32_t n)
|
|
{
|
|
return m_nodes[n];
|
|
}
|
|
|
|
}; // namespace ns3
|
|
@end verbatim
|
|
|
|
@section Using Ipv4BusNetwork
|
|
If all you ever want to do with a bus network can be captured in a topology
|
|
with four nodes on the bus, the preceeding section may seem like a colossal
|
|
waste of time. This is probably not the case, though. Now that we have a
|
|
relatively abstract bus class, we can create bus networks with 4, 40 or 4000
|
|
nodes with no additional effort.
|
|
|
|
A use of the bus network class is shown in the file
|
|
@code{bus-netowrk.cc} located in the @code{tutorial} directory. The
|
|
interesting code is,
|
|
|
|
@verbatim
|
|
Ipv4BusNetwork bus ("10.1.0.0", "255.255.0.0", "0.0.0.3",
|
|
DataRate(10000000), MilliSeconds(20), 10);
|
|
@end verbatim
|
|
|
|
Here we create a bus network with the network number ``10.1.0.0'' and the
|
|
network mask ``255.255.0.0'' that completes the IP network definition. You
|
|
can consider these together as ``10.1.0.0/16'' if you prefer. The next
|
|
parameter tells the bus to start numbering IP addresses of contained nodes at
|
|
``10.1.0.3'' (remember the network number will be combined). We provided a
|
|
data rate of 10 megabits per second and a latency of 20 milliseconds.
|
|
Finally, we ask the @code{Ipv4BusNetwork} object to create ten nodes in the
|
|
network.
|
|
|
|
If you are feeling brave, go ahead and change the number of nodes to be 100,
|
|
1000, 10,000 or more to generate larger and larger networks. Before you go
|
|
too far, remember that a trace file will be generated when you run your
|
|
resulting program and ee asked the trace facility to trace all net device
|
|
receive events. This will include the reception of the broadcast ARP request
|
|
by all of the nodes in the simulation, so this can add up quickly.
|
|
|
|
@c ========================================================================
|
|
@c Summary
|
|
@c ========================================================================
|
|
|
|
@node Summary
|
|
@chapter Summary
|
|
|
|
This concludes the first part of the tutorial. We have focused on
|
|
using the @command{ns-3} system to construct various network topologies and to
|
|
simulate sendng data across the networks; and we've shown you how to use the
|
|
trace facility to get access to simulation results.
|
|
|
|
We now encourage you to play with the system a little. Experiment with what
|
|
we have provided. Build a hierarchical network simulation. Perhaps exercise
|
|
your object design skills and create a new @code{Ipv4DumbbellNetwork} class
|
|
to create dumbbell networks using the Ipv4BusNetwork class we just created.
|
|
Hint: An Ipv4DumbbellNetwork @emph{has} two @code{Ipv4BusNetwork} objects;
|
|
a left side and a right side.
|
|
|
|
In the next part of the tutorial we are going to drop down a level and begin
|
|
examining the lower levels of the system in more detail. We are going to
|
|
explain how to change the behavior of the system and eventually how to write
|
|
new models and applications. This is a good time to make sure that you
|
|
thorougly understand what we've gone over so far.
|
|
|
|
@c ========================================================================
|
|
@c Object Model
|
|
@c ========================================================================
|
|
|
|
@node Object-Model
|
|
@chapter Object Model
|
|
|
|
There are two distinctly different meanings associated with the term Object
|
|
Model. The first speaks to the implementation of an object system --- a system
|
|
view; and the second speaks to the application programming interface (classes
|
|
or objects) one uses to access some service or system --- an application view.
|
|
|
|
As an example of the system view sense of the term, the C++ language has an
|
|
associated object model that describes how objects are laid out in memory,
|
|
how virtual functions work, how inheritance is implemented, constructor and
|
|
destructor execution ordering, template instantiation, etc.
|
|
|
|
In the case of the application view, the Document Object Model is a good
|
|
example. In the words of W3C, the Document Object Model (DOM) is an
|
|
application programming interface (API) for HTML and XML documents. It defines
|
|
the logical structure of documents and the way a document is accessed and
|
|
manipulated.
|
|
|
|
The Component Object Model (COM) from Microsoft actually spans both meanings
|
|
of the term and extends further into policy statements. From a system
|
|
perspective, COM specifies an interface definition language, the layout of
|
|
objects virtual function tables, the formats of Globally Unique Identifiers
|
|
and also specifies lifetime management mechanisms for objects via reference
|
|
counting. From the point of view of the API, COM specifies a number of
|
|
Interfaces as well as functions such as CoCreateInstance and various
|
|
threading models. The COM specification extends to policy by disallowing
|
|
implementation inheritance.
|
|
|
|
The @command{ns-3} object model takes the C++ language (system level) object
|
|
model as its basis, and extends that model by providing an API for software
|
|
componentry. You will find terms like Component, Interface and QueryInterface
|
|
in the following discussion. It is important to understand from the outset
|
|
that this is the @command{ns-3} object model, and not any other object model.
|
|
Richard Feynman (an American physicist) once described the behavior of matter
|
|
and light on a very small scale in the following way,
|
|
|
|
@quotation
|
|
``They do not behave like waves, they do not behave like particles, they do
|
|
not behave like clouds, or billiard balls, or weights on springs, or like
|
|
anything that you have ever seen.''
|
|
@end quotation
|
|
|
|
Just as students of quantum mechanics must rid themselves of preconceptions
|
|
regarding the behavior of matter at small scales, you should rid yourself of
|
|
any preconceptions you may have about components, interfaces and APIs for
|
|
software componentry before continuing. To paraphrase Feynman, @command{ns-3}
|
|
components do not behave like COM Components, or Java Beans, or CORBA
|
|
objects, or clouds or weights on springs, or like anything that you have
|
|
ever seen they are @command{ns-3} components.
|
|
|
|
@section The C++ Object Model is the Root of all Things
|
|
@command{Ns-3} is primarily a C++ system. The system is written in C++ and
|
|
one can use standard C++ mechanisms for creating and using ns-3 objects. We
|
|
do not change this at all, nor do we make any pronouncements about the
|
|
superiority of one mechanism or another. What we will do is provide
|
|
convenience functions that we think will make creating and managing simulation
|
|
objects easier.
|
|
|
|
Previously, you have seen objects created using the template function
|
|
@code{Create} as in the following example:
|
|
|
|
@verbatim
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
@end verbatim
|
|
|
|
This line of code, while it may be unfamiliar to some, is pure C++. If you
|
|
were to look in the header file ptr.h, you would find the following definition
|
|
of the @code{Create} template.
|
|
|
|
@verbatim
|
|
template <typename T>
|
|
Ptr<T> Create (void)
|
|
{
|
|
T *obj = new T ();
|
|
Ptr<T> p = obj;
|
|
obj->Unref ();
|
|
return p;
|
|
}
|
|
@end verbatim
|
|
|
|
As you can see, this template creates objects of type @code{T} using the
|
|
operator @code{new}. Its a little harder to find the corresponding delete ---
|
|
it's in the file @code{object.cc} inside the method @code{Object::MaybeDelete},
|
|
but when that @code{Ptr} which you see above goes out of scope it will call
|
|
@code{Unref} and ultimately the C++ @code{delete} operator will be called.
|
|
|
|
The ns-3 system uses the C++ @code{new} and @code{delete} operators, so there
|
|
is really no reason that you as a user of the ns-3 system are forbidden from
|
|
using these or any other C++ mechanism. If you so desire, you can take on
|
|
the responsibility for managing object lifetime (i.e., do not use the
|
|
@code{Ptr} smart pointer), work directly with the @code{new} and @code{delete}
|
|
operators and call methods like any C++ object as in the following example:
|
|
|
|
@verbatim
|
|
MyClass *obj = new MyClass ();
|
|
obj->Method();
|
|
delete obj;
|
|
@end verbatim
|
|
|
|
You, as a competent model author, are encouraged to use whatever methods you
|
|
think are appropriate in your private code. Remember, however, that the
|
|
public ns-3 APIs do use smart pointers to pass objects around in an effort to
|
|
reduce the burden of object lifetime management. If you do intend to export
|
|
an API publicly, you should use the same object lifetime management approaches
|
|
as those found in the ns-3 public API if only for consistency.
|
|
|
|
These APIs are there for convenience and consistency, but do not change the
|
|
fact that in ns-3 all of the objects are really just C++ objects, ultimately
|
|
created using the C++ new operator with C++ constructor semantics and are
|
|
ultimately deleted using the C++ delete operator, following C++ destructor
|
|
semantics. Although it may sometimes appear so, there is really no system-
|
|
level magic going on in ns-3. Ns-3 components and interfaces are C++ objects
|
|
just like any other object and our object model is simply a collection of APIs
|
|
built on the normal C++ object model.
|
|
|
|
@section Interfaces
|
|
There are many different ideas floating around of what exactly the term
|
|
@emph{Interface} means. Originally an interface just meant a communication
|
|
boundary between two entities. As the concepts of object oriented programming
|
|
(OOP) were surfacing in the 1980s, the term interface was applied to the
|
|
collection of access methods for the modular entities that were being defined.
|
|
|
|
Two distinct approaches developed regarding specifying access mechanisms for
|
|
objects. The OOP purists were very concerned about object reuse and were led
|
|
to Abstract Data Types (ADT). These were eventually implemented in the case
|
|
of C++, as pure virtual methods in Abstract Base Classes (ABC). Another group
|
|
of folks was more interested in simply specifying object access methods in one
|
|
place and using inheritance as the primary reuse mechanism.
|
|
|
|
Bjarne Stroustroup, the creator of C++, embraced both approaches. He makes
|
|
the following interesting observation:
|
|
|
|
@quotation
|
|
``Many classes [@dots{}] are useful both as themselves and also as bases for
|
|
derived classes. [@dots{}] Some classes, such as class @strong{Shape},
|
|
represent abstract concepts for which objects cannot exist.''
|
|
@end quotation
|
|
|
|
@command{Ns-3} does not pick and enforce a particular approach. In
|
|
@command{ns-3} an interface is determined completely by a class declaration
|
|
just as any C++ object interface is declared. If you think of an object as
|
|
an abstract concept that should be implemented by derived classes, by all
|
|
means, use the Abstract Base Class approach to interface declaration. If you
|
|
think that an object should be completely concrete and you foresee no need
|
|
to ever modify its behavior, feel free to avoid declaring any methods virtual.
|
|
If you think that an object could be useful as a base class, feel free to
|
|
declare its methods virtual. If you like to use the PIMPL idiom, again, feel
|
|
free. If you want to use any combination of these techniques, feel free.
|
|
We make no restrictions.
|
|
|
|
When we speak of an ns-3 interface, we do not worry about interface definition
|
|
languages, or pure virtual classes, or registries we just think about C++
|
|
object declarations and their associated methods. When we instantiate an
|
|
@command{ns-3} Interface, it is the C++ object model that dictates how that
|
|
object is brought into existence. When a method is called on an @command{ns-3}
|
|
Interface, it is the C++ object model that dictates how that method is
|
|
dispatched.
|
|
|
|
The only difference between a vanilla C++ object and an ns-3 Interface, is
|
|
that an object acting as an ns-3 Interface must inherit from the base class
|
|
Object. This inheritance gives the Interface object a very useful capability.
|
|
|
|
@section The Ns-3 Capital I Interface and QueryInterface
|
|
One thing that Microsoft got right in the Component Object Model was the idea
|
|
of Interface aggregation and discovery via QueryInterface. We have embraced
|
|
these ideas in @command{ns-3}. This was done primarily to address a common
|
|
problem in large software systems. A good example of this problem happens
|
|
in the @command{ns-3} Node class.
|
|
|
|
If one were to take the standard OOP view of specializing a @code{Node} into
|
|
an internet host, for example, one would typically inherit from the @code{Node}
|
|
base class and include functionality to implement such things as internet
|
|
routing and a TCP / IP protocol stack. Other types of @code{Node}s might
|
|
inherit from the node class and specialize in different ways, or further
|
|
specialize the internet host class, treating it as a base class. This can
|
|
result in a complicated inheritance tree in which some specializations are
|
|
simply not available to other branches of the tree which can make reuse
|
|
difficult or impossible. This is known as the @emph{weak base class} problem
|
|
and creates pressure to drive functionality up the inheritance tree into the
|
|
base classes. This, in turn, results in @emph{base class bloat} and the
|
|
resulting @emph{swiss army knife} base classes which end up trying to do
|
|
everything in one place.
|
|
|
|
Even if one successfully avoided these swiss army knife base classes, one
|
|
would also want to be able to treat new specializations of @code{Node}
|
|
generically in the system. This means one would pass references to the base
|
|
class (@code{Node}) across public APIs. This introduces @emph{upcasts} prior
|
|
to passing across public APIs and corresponding @emph{downcasts} on the other
|
|
side in order to gain access to required specialized functions. As the
|
|
inheritance tree becomes more complicated, this approach can cause another
|
|
related problem known as the @emph{fragile base class} problem. This happens
|
|
when changes to the base class cause unexpected problems in the various and
|
|
sundry subclasses.
|
|
|
|
These effects seem always to result in a positive feedback loop driving
|
|
everything into the base class and destroying much of the encapsulation which
|
|
is a hallmark of the object oriented approach.
|
|
|
|
@subsection Interface Composition
|
|
There is a completely different way to address the Node specialization
|
|
problem. Instead of approaching the situation using inheritance, one can
|
|
look at the problem as one of composition. We can look at the @code{Node}
|
|
class as a container of sorts that holds other objects. In this case, the
|
|
objects would be instances of the classes implementing the internetwork
|
|
routing code, or the TCP / IP protocol stack described above. This approach
|
|
preserves the encapsulation and solves the weak base class, base class bloat
|
|
and fragile base class problems; but the question of method dispatch
|
|
immediately comes to mind.
|
|
|
|
In many systems, @emph{delegation} is used. The base class, @code{Node},
|
|
in this approach would provide methods that simply forward to the objects
|
|
implementing the desired functionality. This situation clearly does not
|
|
address the base class bloat problem since dispatch methods must be added
|
|
to the base class. The situation is mitigated somewhat by pushing the
|
|
implementation of the dispatch methods to contained objects, but the
|
|
fundamental problems are still present. What is really needed is a way
|
|
to compose objects but at the same time keep the interfaces to those
|
|
objects separated.
|
|
|
|
Composition, usually called @emph{aggregation}, along with runtime Interface
|
|
discovery is the solution that Microsoft originally championed and that
|
|
@command{ns-3} has adopted. In our example a @code{Node} would contain
|
|
separate Interface objects implementing internetwork routing and TCP/IP.
|
|
These contained objects have interfaces in the C++ sense of collections of
|
|
method signatures. When objects are capable of participating in this
|
|
aggregation process, they are called @command{ns-3} Interfaces and they
|
|
receive the functionality required for this participation by inheriting
|
|
from the base class @code{Object}.
|
|
|
|
@subsection Object, interfaces and Interfaces
|
|
As mentioned above, the class that implements the aggregation mechanism for
|
|
@command{ns-3} objects is called @code{Object}. The class named @code{Object}
|
|
is simply a base class that you will inherit from if you want your objects
|
|
to support aggregation and QueryInterface. Many systems have a base class
|
|
that implements common functionality and these base classes are typically
|
|
called Object. The @command{ns-3} version of this object base class relates
|
|
primarily to Interface aggregation, although it does provide methods to help
|
|
with intrusive reference counting and tracing as well.
|
|
|
|
When a C++ object inherits from the ns-3 Object base class, it is conceptually
|
|
promoted to an ns-3 Interface (note the capital I in Interface) irrespective
|
|
of how the object was declared (e.g., as an abstract base class, concrete
|
|
class, with virtual methods, etc.). In ns-3, you should associate
|
|
inheritance from the class named @code{Object} with promotion of an object to
|
|
the status of Interface rather than the form of the Interface declaration.
|
|
|
|
When you inherit from @code{Object}, you will get new methods and an
|
|
Interface Identifier. The Interface Identifer, or @emph{iid}, is the
|
|
@command{ns-3} version of the @emph{Universally Unique ID} (UUID) or
|
|
@emph{Globally Unique ID} (GUID) found in other systems. Unlike the GUID, it
|
|
is really a dynamically created process-local ID. For now, consider it as
|
|
simply a number which the system will generate for you that uniquely
|
|
identifies an Interface class within the ns-3 system and allows you to
|
|
specify an interface type to @code{QueryInterface}.
|
|
|
|
To summarize, when you instantiate an object that inherits from the
|
|
@code{Object} class, you will have a C++ object that has four important
|
|
properties:
|
|
|
|
@itemize @bullet
|
|
@item The object has a C++ interface defined by the collection of method signatures in its inheritance tree;
|
|
@item The object has an Interface ID that uniquely identifies the C++ interface of its class;
|
|
@item The object is a container that has the ability to aggregate other interfaces;
|
|
@item The object exports a method that allows for discovery of aggregated interfaces (@code{QueryInterface}) according to Interface ID.
|
|
@end itemize
|
|
|
|
It is crucially important to understand what we have described here. A given
|
|
C++ class has an object access interface that is essentially the collection
|
|
of method signatures specified in its inheritance tree. This is a C++ object
|
|
model thing. Ns-3 provides a base class from which the class in question can
|
|
inherit and be promoted to the status of Interface. Once a class becomes
|
|
an Interface it has inherited the ability to set its own interface identifier
|
|
(@code{iid}), and exports methods to aggregate and search other Interfaces
|
|
that are added to its aggregation.
|
|
|
|
That last detail is important. In @command{ns-3} Interfaces are both
|
|
containers and specifications for object method access. We have previously
|
|
mentioned the @code{Node} class acts as a container. In fact, the @code{Node}
|
|
class inherits from @code{Object} and is itself also an @command{ns-3}
|
|
Interface. When the @code{Node} object is created it is really an aggregation
|
|
of one Interface, the @code{Node} Interface. This is generally true ---
|
|
Interfaces are both containers and Interfaces.
|
|
|
|
@subsection Aggregations
|
|
The figure below shows how an Interface could be illustrated in detail. The
|
|
line with the circle at the top of the diagram represents the appearance of the
|
|
Interface to the external world. This circle and line are called a lollipop
|
|
because of its superficial similarity to a kind of childs candy.
|
|
|
|
@sp 1
|
|
@center @image{oneif,,,,png}
|
|
|
|
You could declare this interface quite simply using a non-virtual class as
|
|
follows,
|
|
|
|
@verbatim
|
|
class A : public Object {
|
|
public:
|
|
static const InterfaceId iid;
|
|
void MethodA (void);
|
|
};
|
|
@end verbatim
|
|
|
|
The methods that are then available via the Interface labeled @code{A} in the
|
|
figure above are the methods inherited from the @code{Object} base class (
|
|
@code{QueryInterface}, @code{Ref}, and @code{Unref}) and those from class
|
|
@code{A} (@code{MethodA}). Note that you must declare an @code{InterfaceId}
|
|
for your Interface class, and it must be declared static to make it class-wide
|
|
in scope. This @code{iid} can be thought of as a kind of type information
|
|
that uniquely identifies objects as being instantiated from this class.
|
|
|
|
You can think of the arc and arrow device coming off each side of the
|
|
Interface as part of a connector. These connectors allow @code{QueryInterface}
|
|
to search aggregations for a particular @code{iid}. The figure below shows an
|
|
aggregation of three Interfaces: A, B and C. The class declarations for
|
|
classes @code{B} and @code{C} are substantially similar to that of class
|
|
@code{A}.
|
|
|
|
@sp 1
|
|
@center @image{threeif,,,,png}
|
|
|
|
You can visualize these Interfaces as being snapped together like Lego
|
|
building blocks if you like. When the Interfaces are aggregated, a
|
|
@code{QueryInterface} search path is formed through the connectors. In order
|
|
to create this aggregation we first need to create the Interface objects.
|
|
These are just normal, everyday C++ objects that we can create using the
|
|
@code{Create} template function and manage using smart pointers. The
|
|
following code should be obvious to you by now:
|
|
|
|
@verbatim
|
|
Ptr<A> a = Create<A> ();
|
|
Ptr<B> b = Create<B> ();
|
|
Ptr<C> c = Create<C> ();
|
|
@end verbatim
|
|
|
|
When you create an aggregation, you pick one of the Interfaces to act as
|
|
the container. In this case well pick Interface A. In order to aggregate
|
|
an Interface, you simply call the method @code{AddInterface} that your class
|
|
inherited from @code{Object}. The following code will aggregate Interface
|
|
@code{B} and Interface @code{C} onto the Interface (and container) @code{A}.
|
|
|
|
@verbatim
|
|
a->AddInterface (b);
|
|
a->AddInterface (c);
|
|
@end verbatim
|
|
|
|
Thats all there is to it. Now that you have those connectors snapped
|
|
together, you can ask each of the Interfaces in the aggregation for any of
|
|
the Interfaces in the aggregation. Lets look at a simple example:
|
|
|
|
@verbatim
|
|
Ptr<B> newB = a->QueryInterface<B> (B:iid);
|
|
@end verbatim
|
|
|
|
The left hand side of this assignment declares a smart pointer to the class
|
|
@code{B} to help with memory management of the returned Interface pointer.
|
|
Object lifetime management is very important when dealing with Interfaces
|
|
and our smart pointer will simply take care of it all for you.
|
|
|
|
The right hand side illustrates the basic idea of @code{QueryInterface}. We
|
|
take a take a (smart) pointer to Interface @code{A} and ask it to search the
|
|
aggregation for an interface associated with an interface identifier with
|
|
the value of @code{B:iid} which is passed as a parameter. Recall that
|
|
@code{B::iid} is the @code{static InterfaceId} of the Interface class
|
|
@code{B}. Observe that @code{QueryInterface} is a template function and the
|
|
type specified in the angle brackets, here @code{<B>}, tells it what kind of
|
|
smart pointer to return. In this case @code{QueryInterface} will find an
|
|
Interface object of type @code{B::iid} in its list of Interfaces and return a
|
|
smart pointer to @code{B} as instructed.
|
|
|
|
Now that you have those connectors snapped together, you can ask each of
|
|
the Interfaces in the aggregation for any of the Interfaces in the
|
|
aggregation. For example we could walk the Interfaces asking each for the
|
|
next in the aggregation. First we would ask the Interface pointed to by the
|
|
smart pointer a to look for the InterfaceId representing @code{B}:
|
|
|
|
@verbatim
|
|
Ptr<B> newB = a->QueryInterface<B> (B:iid);
|
|
@end verbatim
|
|
|
|
Next, we can ask the Interface pointed to by the smart pointer @code{newB}
|
|
to look for the @code{InterfaceId} representing @code{C}:
|
|
|
|
@verbatim
|
|
Ptr<C> newC = newB->QueryInterface<C> (C:iid);
|
|
@end verbatim
|
|
|
|
Then, we can ask the Interface pointed to by the smart pointer @code{newC}
|
|
to look for the InterfaceId representing A and complete our circuit of the
|
|
aggregation:
|
|
|
|
@verbatim
|
|
Ptr<A> newA = newC->QueryInterface<A> (A:iid);
|
|
@end verbatim
|
|
|
|
@code{QueryInterface} (often abbreviated QI) has some important properties
|
|
that we need to go over. Technically, QI is a @emph{symmetric},
|
|
@emph{reflexive} and @emph{transitive} operation with respect to the set of
|
|
aggregated Interfaces.
|
|
|
|
@subsubsection Symmetry
|
|
The symmetric nature of QI guarantees that if one performs a QI on a given
|
|
Interface for the Interface Id of that same interface, that
|
|
@code{QueryInterface} must succeed. The existence of interface A in the
|
|
aggregation implies the reachability of Interface A in the aggregation. This
|
|
is usually written (by Microsoft) as,
|
|
|
|
@center must succeed (A >> A)
|
|
|
|
We can illustrate this property with the code snippet,
|
|
|
|
@verbatim
|
|
Ptr<A> symmetricA = a->QueryInterface<A> (A:iid);
|
|
NS_ASSERT (symmetricA);
|
|
@end verbatim
|
|
|
|
Here we take as given an interface (smart) pointer named a on which we
|
|
perform a QI looking for the InterfaceId of that same Interface. This call
|
|
must always succeed and a smart pointer to the Interface a is returned by QI.
|
|
|
|
@subsubsection Reflexivity
|
|
Calls to QI must also be reflexive. This means that if you successfully QI
|
|
for interface B from interface A, then you must always be able to QI for A
|
|
from B. This is usually written as,
|
|
|
|
@center must succeed (A >> B, then B >> A)
|
|
|
|
This property can be illustrated with the code snippet,
|
|
|
|
@verbatim
|
|
Ptr<B> b = a->QueryInterface<B> (B:iid);
|
|
Ptr<A> reflexiveA = b->QueryInterface<A> (A:iid);
|
|
NS_ASSERT (reflexiveA);
|
|
@end verbatim
|
|
|
|
If the first @code{QueryInterface} on Interface A looking for Interface B
|
|
succeeds, then a @code{QueryInterface} on Interface B looking for Interface A
|
|
must succeed.
|
|
|
|
@subsubsection Transitivity
|
|
@code{QueryInteface} must also be transitive. This means that if one can
|
|
find Interface B from Interface A, and Interface C from Interface B, then one
|
|
must also be able to find interface C from Interface A. This is usually
|
|
written as,
|
|
|
|
@center must succeed (A >> B, and B >> C, then A >> C)
|
|
|
|
This property can be illustrated with the code snippet,
|
|
|
|
@verbatim
|
|
Ptr<B> b = a->QueryInterface<B> (B:iid);
|
|
Ptr<C> c = b->QueryInterface<C> (C:iid);
|
|
Ptr<C> transitiveC = a->QueryInterface<C> (C:iid);
|
|
NS_ASSERT (transitiveC);
|
|
@end verbatim
|
|
|
|
If you can get to Interface B from Interface A, and you can get to Interface C
|
|
from Interface B, then a QueryInterface on Interface A looking for Interface C
|
|
must also succeed.
|
|
|
|
@subsection Creating the InterfaceId
|
|
The final piece of this puzzle is to locate where the interface Ids actually
|
|
come from. The answer is from a static initializer that must be located in
|
|
the @code{.cc} file corresponding to the Interface. For example, to
|
|
initialize the Interface Id for the class A above, you would simply add the
|
|
following code to the source file that implements class A,
|
|
|
|
@verbatim
|
|
const InterfaceId A::iid =
|
|
MakeInterfaceId (``A'', Object::iid);
|
|
@end verbatim
|
|
|
|
This code is guaranteed by the C++ language definition to be executed before
|
|
your main procedure is entered. The call to MakeInterfaceId will assign a
|
|
process-local unique identifier to your class and associate your interface
|
|
with the name (string) ``A.'' This allows you to look up an InterfaceId by
|
|
a human readable string.
|
|
|
|
An advanced ns-3 specific feature of QueryInterface is exposed here.
|
|
@code{MakeInterfaceId} takes an @code{InterfaceId} as a parameter. This is
|
|
the @code{iid} of the base class from which you inherited. In most cases
|
|
this will be @code{Object::iid}, which is the @code{InterfaceId} of the
|
|
@code{Object} base class. In @command{ns-3}, the @code{Object} base class
|
|
has its own @code{iid} and you can QI for that @code{iid}. The @code{Object}
|
|
base class has a rough equivalence to the @emph{IUnknown} Interface in
|
|
Microsofts COM, so you can QI for @code{Object::iid} in @command{ns-3}f just
|
|
as you might QI for IID_IUnknown in COM.
|
|
|
|
The InterfaceId you pass to @code{MakeInterfaceId} is used to create an
|
|
inheritance tree in the ns-3 interface manager. This inheritance tree is also
|
|
walked in @code{QueryInterface} Interface searches. Consider a simple case
|
|
of a base class and a derived class as shown below,
|
|
|
|
@verbatim
|
|
class Base : public Object
|
|
{
|
|
public:
|
|
static const InterfaceId iid;
|
|
...
|
|
};
|
|
|
|
class Derived : public Base
|
|
{
|
|
public:
|
|
static const InterfaceId iid;
|
|
...
|
|
};
|
|
@end verbatim
|
|
|
|
To assign the InterfaceId for each of these classes, we could add two calls
|
|
to @code{MakeInterfaceId} reflecting the class hierarchy we just created.
|
|
|
|
@verbatim
|
|
const InterfaceId Base::iid =
|
|
MakeInterfaceId (``Base'', Object::iid);
|
|
|
|
const InterfaceId Derived::iid =
|
|
MakeInterfaceId (``Derived'', Base::iid);
|
|
@end verbatim
|
|
|
|
The first Interface is shown to inherit from class @code{Object} and the
|
|
second inherits from class @code{Base}. We could create these interfaces
|
|
as we usually do,
|
|
|
|
@verbatim
|
|
Ptr<Base> base = Create<Base> ();
|
|
Ptr<Derived> derived = Create<Derived> ();
|
|
@end verbatim
|
|
|
|
The derived and base @code{InterfaceIds} are either present or not present
|
|
based on the inheritance tree. For example, a QI for the @code{Base
|
|
InterfaceId} must succeed when done against a @code{Ptr<Base>}; but a QI for
|
|
the @code{Derived InterfaceId} must fail when done against a @code{Ptr<Base>}.
|
|
However, a QI for the @code{Base InterfaceId} must succeed when done against a
|
|
@code{Ptr<Derived>}; and a QI for the @code{Derived InterfaceId} must succeed
|
|
when done against a @code{Ptr<Derived>}.
|
|
|
|
This feature allows you to use implementation inheritance to easily create
|
|
new Interfaces. You are prevented from doing so in Microsoft COM, but this
|
|
was almost universally identified as a problem.
|
|
|
|
@subsection A Real Example
|
|
At this point you may be asking yourself what the point of all of this is,
|
|
since you already had those pointers laying around when you created the
|
|
objects. The typical case is that you would forget about the pointers to the
|
|
contained objects and only export a single Interface. Other Interfaces could
|
|
be discovered using QI.
|
|
|
|
Generally one tends to think of one of the Interfaces in the aggregation
|
|
as being the container and other Interfaces being aggregated to that
|
|
container. In the case of a Node, for example, it is quite natural to think
|
|
of the Node as being the container which contains Interfaces for the protocol
|
|
stacks, internet routing, etc. So, lets start developing an example by
|
|
calling the container Interface Node instead of A. The creation of this
|
|
Interface is found all over our example programs. For example, you will
|
|
find code like the following in @code{samples/simple-point-to-point.cc}:
|
|
|
|
@verbatim
|
|
Ptr<Node> n = Create<InternetNode> ();
|
|
@end verbatim
|
|
|
|
This code is described in detail in previous sections, but the important thing
|
|
to realize here is that the resulting @code{Node} is an @command{ns-3}
|
|
Interface. This is not at all obvious -- you must look at the source code to
|
|
see that this is true. Take a look at @code{src/node/node.h} and find the
|
|
class declaration for class @code{Node}. There you will find,
|
|
|
|
@verbatim
|
|
class Node : public Object
|
|
{
|
|
public:
|
|
static const InterfaceId iid;
|
|
...
|
|
};
|
|
@end verbatim
|
|
|
|
Class @code{Node} inherits from class @code{Object} and provides an
|
|
@code{InterfaceId}, therefore it is an @command{ns-3} interface. You now
|
|
know you can use @code{AddInterface} for aggregation and @code{QueryInterface}
|
|
for Interface discovery against any @code{Node} in the system.
|
|
|
|
We spoke of a protocol stack that is aggregated to a @code{Node} in our
|
|
discussions above, what we see in the real @command{ns-3} code is that this
|
|
is represented by the @code{Ipv4} Interface. If you look in
|
|
@code{src/node/ipv4.h} you will find,
|
|
|
|
@verbatim
|
|
class Ipv4 : public Object
|
|
{
|
|
public:
|
|
static const InterfaceId iid;
|
|
...
|
|
};
|
|
@end verbatim
|
|
|
|
Since class @code{Ipv4} inherits from class @code{Object} and has a
|
|
@code{static InterfaceId}, it is an @command{ns-3} Interface. If you look in
|
|
@code{src/node/ipv4.cc} you will find,
|
|
|
|
@verbatim
|
|
const InterfaceId Ipv4::iid =
|
|
MakeInterfaceId (``Ipv4'', Object::iid);
|
|
@end verbatim
|
|
|
|
After all of this reading you now know that this code snippet is asking the
|
|
system to create a unique @code{InterfaceId} for the @code{Ipv4} class and
|
|
declares that @code{Ipv4} inherits from class @code{Object}.
|
|
|
|
It turns out that the Ipv4 class is an abstract base class (ABC). There are
|
|
a number of pure virtual methods declared in that class. This means that
|
|
an @code{Ipv4} object may not be instantiated. What is instantiated is an
|
|
implementation class, called @code{Ipv4Impl}. This class inherits from
|
|
@code{Ipv4} and provides the required virtual methods. This is where
|
|
understanding what is an Interface and what is not gets tricky. The
|
|
Interface is the @code{Ipv4} class since that is where the @code{InterfaceId}
|
|
is found. The fact that you see @code{ipv4::iid} tells you that the
|
|
@code{Ipv4} class is the Interface and has the associated @code{InterfaceId}.
|
|
The class @code{Ipv4Impl} provides an implementation for the pure virtual
|
|
methods in @code{Ipv4}. Since class @code{Ipv4} cannot be instantiated, one
|
|
instantiates the @code{Ipv4Impl} class to create an @code{Ipv4} Interface.
|
|
Once the @code{Ipv4Impl} class is instantiated, the pointer to it is
|
|
immediately cast to an @code{Ipv4} pointer. Clients will then use the
|
|
@code{Ipv4} object access methods (see @code{ipv4.h}) to talk to the
|
|
@code{Ipv4Impl} object over the @code{Ipv4} Interface. I urge you to not go
|
|
any further until you thoroughly understand what youve just read.
|
|
|
|
If you now look in the file, @code{src/internet-node/internet-node.cc} you
|
|
will see the following code in @code{InternetNode::Construct} that creates the
|
|
@code{Ipv4} Interface and aggregates it to the @code{Node} interface (recall
|
|
that class @code{Node} is an Interface and class @code{InternetNode} inherits
|
|
from class @code{Node}):
|
|
|
|
@verbatim
|
|
Ptr<Ipv4Impl> ipv4Impl = Create<Ipv4Impl> (ipv4);
|
|
...
|
|
Object::AddInterface (ipv4Impl);
|
|
@end verbatim
|
|
|
|
Note that the parameter @code{ipv4} passed to the @code{Create} template
|
|
function is actually a pointer to an @code{Ipv4L3Protocol} which you can
|
|
ignore at this point --- it doesn't really have anything to do with the
|
|
@code{Ipv4} Interface.
|
|
|
|
This last example does illustrate that the fact that whether an @command{ns-3}
|
|
object is or is not an Interface can be quite well hidden. The designers of
|
|
the system had long and involved discussions on this issue and in the end
|
|
decided that mnemonic aids such as Hungarian notation were a stylistic thing
|
|
and you should just refer to the system documentation to determine what
|
|
objects are ns-3 Interfaces and what those Interfaces actually are (RTFM ---
|
|
Read the Fine Manual).
|
|
|
|
In this case, you know that the class @code{Ipv4Impl} inherits from some
|
|
Interface since there is a call to @code{AddInterface} that refers to it.
|
|
You can go to the header file @code{src/internet-node/ipv4-impl.h} and find
|
|
that @code{Ipv4Impl} inherits from class @code{Ipv4}. You then go to file
|
|
@code{src/node/ipv4.h} and see that it inherits from @code{Object} and
|
|
contains an @code{InterfaceId}. Thus the Interface added is really the
|
|
@code{Ipv4} Interface with the interface Id @code{Ipv4::iid}.
|
|
|
|
Returning to some @command{ns-3} example code, lets take a look at
|
|
@code{src/examples/simple-point-to-point.cc} again. You will find the
|
|
following code:
|
|
|
|
@verbatim
|
|
Ptr<Node> n0 = Create<InternetNode> ();
|
|
...
|
|
Ptr<Ipv4> ipv4;
|
|
ipv4 = n0->QueryInterface<Ipv4> (Ipv4::iid);
|
|
ipv4->SetDefaultRoute (Ipv4Address (``10.1.1.2''), 1);
|
|
@end verbatim
|
|
|
|
The first line creates an @code{InternetNode} object and casts the resulting
|
|
smart pointer to a @code{Node}. The next line declares a smart pointer to an
|
|
@code{Ipv4} object. Because youve been through the code with us, you know
|
|
that both the @code{Node} and the @code{Ipv4} objects are Interfaces. They
|
|
should be able to participate in a @code{QueryInterface}.
|
|
|
|
The next line confirms it. We do a @code{QueryInterface} on the @code{Node},
|
|
looking for the @code{Ipv4} Interface (@code{Ipv4::iid}).
|
|
@code{QueryInterface} then returns a smart pointer to its aggregated
|
|
@code{Ipv4} Interface. [Recall that this Interface was aggregated in
|
|
@code{InternetNode::Construct}. We knew to start looking for the aggregation
|
|
in @code{InternetNode} since we originally created an @code{InternetNode} in
|
|
the @code{Create} template function and then implicitly cast it to a
|
|
@code{Node}.]
|
|
|
|
Once you have the @code{Ipv4} smart pointer, you simply use it as if it were
|
|
any other C++ object. The last line shows this by setting the default route
|
|
for the node.
|
|
|
|
@section Caveats
|
|
There are a few things that you should remember but which may not be
|
|
immediately obvious.
|
|
|
|
@subsection Interface Ids are Associated with Classes not Objects
|
|
Interfaces are identified by an @code{InterfaceId} that is associated with
|
|
the Interface class, not the Interface object. That is indicated by the
|
|
@code{static} keyword in the declaration of the @code{iid} in the class. The
|
|
interface Id for a given Interface class exists independently of any objects
|
|
of that class that you may instantiate; and all objects of a given Interface
|
|
type share the same @code{InterfaceId}.
|
|
|
|
You cannot add more than one Interface of a given type (@code{iid}) to an
|
|
aggregation. If you need to contain a number of Interfaces of the same type
|
|
in the same aggregation, you will need to provide a separate container over
|
|
which you can iterate. For example, the @code{Node} class provides methods,
|
|
|
|
@verbatim
|
|
uint32_t GetNDevices (void) const;
|
|
Ptr<NetDevice> GetDevice (uint32_t index) const;
|
|
@end verbatim
|
|
|
|
that are used iterate over the multiple @code{NetDevice} Interfaces associated
|
|
with it.
|
|
|
|
@emph{Interface Ids do not identify objects.}
|
|
|
|
@subsection Dont use QI to Check Your Own Type.
|
|
It is tempting to use @code{QueryInterface} as a form of runtime type
|
|
information. Dont do it. You have no control over what other object may be
|
|
added to your aggregation and this may cause problems. Someone else may have
|
|
appropriated (reimplemented) your type and aggregated themselves onto your
|
|
aggregation.
|
|
|
|
Consider a socket factory implementation. Sockets can be either UDP sockets
|
|
or TCP sockets. A socket factory will have a generic @code{SocketFactory}
|
|
Interface and either a UDP specific interface for setting UDP parameters or a
|
|
similar TCP-specific interface.
|
|
|
|
Consider what might happen if you declared your socket factory as a partially
|
|
abstract base class, and then provided separate implementations for UDP and
|
|
TCP specific methods of this factory in separate concrete classes. Now
|
|
consider what might happen if you used QueryInterface in your base class
|
|
to determine if you were a UDP or a TCP factory.
|
|
|
|
If a factory, say the UDP version, were not aggregated to any other Interface,
|
|
the base class could QueryInterface on itself for the UDP-specific interface.
|
|
It could then infer that it was a UDP implementation and would then do any
|
|
UDP-specific tasks it could. [Experienced C++ folks are cringing about how
|
|
horrible this design is, but bear with me --- its a simple illustration of
|
|
a specific and perhaps not-too-obvious problem.]
|
|
|
|
If another factory, say the TCP version, were not aggregated to any other
|
|
Interface, the base class could QueryInterface on itself for the UDP-specific
|
|
interface. If this failed, it could then infer that it had a TCP
|
|
implementation and would then do any TCP-specific tasks it could.
|
|
|
|
Now, what happens when these two working objects are aggregated together.
|
|
Since the Interfaces are conceptually snapped together the TCP implementation
|
|
would suddenly begin finding the UDP Interface from the other class factory
|
|
and fail.
|
|
|
|
@emph{Interface Ids should not be used as run-time type information.}
|
|
|
|
@section Connecting the Dots
|
|
This may all sound very complicated to you if this is your first exposure to
|
|
these concepts. It may be annoying if I tell you that its really not as hard
|
|
as it sounds. Rest assured that if you take some time, look at and understand
|
|
the examples and write a little test code it will all come together for you.
|
|
Grep around the system for AddInterface and QueryInterface and take a look at
|
|
how we have used them. This will also give you a good idea of what our core
|
|
Interfaces are. If you grep for @code{::iid} you will find most, if not all
|
|
of the interface declarations in the system. The more you see this idiom in
|
|
use, the more comfortable you will be with the idea and the more you will see
|
|
how this addresses the weak base class, swiss army knife base class, and
|
|
fragile base class problems I explained at the beginning.
|
|
|
|
As I alluded to earlier, the developers had long discussions regarding how to
|
|
make navigating the QueryInterface environment easier. The primary issue was
|
|
how we could make it easier to convey to you, the model writer, that an object
|
|
was an Interface. One suggestion was to adopt the convention that classes
|
|
that implement Interfaces begin with the letter I. Microsoft does this, as
|
|
exemplified by the class IUnknown. We also toyed with the idea of beginning
|
|
our header files with i- as in i-ipv4.h. We considered forcing some structure
|
|
on Interfaces with a pure virtual class specification, the names of which
|
|
begin with an I; and corresponding implementations, the names of which begin
|
|
with a C.
|
|
|
|
In the end we decided that we were really discussing issues of programming
|
|
style, and we really could not come up with a strong reason to impose any
|
|
particular solution. In the end, we decided that we would not impose any
|
|
structure on the source code, nor impose any naming convention. We will
|
|
rely on our documentation system (Doxygen) to break out all objects with
|
|
InterfaceIds in their class hierarchy into a separate section. For now,
|
|
until this is implemented, grep is your friend.
|
|
|
|
@c ========================================================================
|
|
@c Doxygen
|
|
@c ========================================================================
|
|
|
|
@node The-Doxygen-Documentation-System
|
|
@chapter The Doxygen Documentation System
|
|
|
|
@node How-To-Change-Things
|
|
@chapter How to Change Things
|
|
|
|
@node How-To-Set-Default-Values
|
|
@chapter How to Set Default Values
|
|
|
|
@node How-To-Write-A-New-Application
|
|
@chapter How to Write a New Application
|
|
|