7.3 A Real Example
7.3.4 A fifth.cc Walkthrough
7.3.4.4 The Main Program
The following code should be very familiar to you by now: int
Chapter 7: The Tracing System 91
{
NodeContainer nodes; nodes.Create (2);
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps")); pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms")); NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
This creates two nodes with a point-to-point channel between them, just as shown in the illustration at the start of the file.
The next few lines of code show something new. If we trace a connection that behaves perfectly, we will end up with a monotonically increasing congestion window. To see any interesting behavior, we really want to introduce link errors which will drop packets, cause duplicate ACKs and trigger the more interesting behaviors of the congestion window.
ns-3 provides ErrorModel objects which can be attached to Channels. We are using the RateErrorModel which allows us to introduce errors into a Channel at a given rate.
Ptr<RateErrorModel> em = CreateObjectWithAttributes<RateErrorModel> ( "RanVar", RandomVariableValue (UniformVariable (0., 1.)),
"ErrorRate", DoubleValue (0.00001));
devices.Get (1)->SetAttribute ("ReceiveErrorModel", PointerValue (em)); The above code instantiates a RateErrorModel Object. Rather than using the two-step process of instantiating it and then setting Attributes, we use the convenience function CreateObjectWithAttributes which allows us to do both at the same time. We set the “RanVar” Attribute to a random variable that generates a uniform distribution from 0 to 1. We also set the “ErrorRate” Attribute. We then set the resulting instantiated RateErrorModel as the error model used by the point-to-point NetDevice. This will give us some retransmissions and make our plot a little more interesting.
InternetStackHelper stack; stack.Install (nodes); Ipv4AddressHelper address;
address.SetBase (‘‘10.1.1.0’’, ‘‘255.255.255.252’’);
Ipv4InterfaceContainer interfaces = address.Assign (devices);
The above code should be familiar. It installs internet stacks on our two nodes and creates interfaces and assigns IP addresses for the point-to-point devices.
Since we are using TCP, we need something on the destination node to receive TCP connections and data. The PacketSink Application is commonly used in ns-3 for that purpose.
uint16_t sinkPort = 8080;
Address sinkAddress (InetSocketAddress(interfaces.GetAddress (1), sinkPort)); PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory",
InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
Chapter 7: The Tracing System 92
sinkApps.Start (Seconds (0.)); sinkApps.Stop (Seconds (20.));
This should all be familiar, with the exception of,
PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
This code instantiates a PacketSinkHelper and tells it to create sockets using the class ns3::TcpSocketFactory. This class implements a design pattern called “object factory” which is a commonly used mechanism for specifying a class used to create objects in an abstract way. Here, instead of having to create the objects themselves, you provide the PacketSinkHelper a string that specifies a TypeId string used to create an object which can then be used, in turn, to create instances of the Objects created by the factory.
The remaining parameter tells the Application which address and port it should Bind to.
The next two lines of code will create the socket and connect the trace source. Ptr<Socket> ns3TcpSocket = Socket::CreateSocket (nodes.Get (0),
TcpSocketFactory::GetTypeId ());
ns3TcpSocket->TraceConnectWithoutContext (‘‘CongestionWindow’’, MakeCallback (&CwndChange));
The first statement calls the static member function Socket::CreateSocket and pro- vides a Node and an explicit TypeId for the object factory used to create the socket. This is a slightly lower level call than the PacketSinkHelper call above, and uses an explicit C++ type instead of one referred to by a string. Otherwise, it is conceptually the same thing.
Once the TcpSocket is created and attached to the Node, we can use TraceConnectWithoutContext to connect the CongestionWindow trace source to our trace sink.
Recall that we coded an Application so we could take that Socket we just made (during configuration time) and use it in simulation time. We now have to instantiate that Application. We didn’t go to any trouble to create a helper to manage the Application so we are going to have to create and install it “manually”. This is actually quite easy:
Ptr<MyApp> app = CreateObject<MyApp> ();
app->Setup (ns3TcpSocket, sinkAddress, 1040, 1000, DataRate ("1Mbps")); nodes.Get (0)->AddApplication (app);
app->Start (Seconds (1.)); app->Stop (Seconds (20.));
The first line creates an Object of type MyApp – our Application. The second line tells the Application what Socket to use, what address to connect to, how much data to send at each send event, how many send events to generate and the rate at which to produce data from those events.
Next, we manually add the MyApp Application to the source node and explicitly call the Start and Stop methods on the Application to tell it when to start and stop doing its thing.
We need to actually do the connect from the receiver point-to-point NetDevice to our callback now.
Chapter 7: The Tracing System 93
devices.Get (1)->TraceConnectWithoutContext("PhyRxDrop", MakeCallback (&RxDrop)); It should now be obvious that we are getting a reference to the receiving Node NetDevice
from its container and connecting the trace source defined by the attribute “PhyRxDrop” on that device to the trace sink RxDrop.
Finally, we tell the simulator to override any Applications and just stop processing events at 20 seconds into the simulation.
Simulator::Stop (Seconds(20)); Simulator::Run ();
Simulator::Destroy (); return 0;
}
Recall that as soon as Simulator::Run is called, configuration time ends, and simulation time begins. All of the work we orchestrated by creating the Application and teaching it how to connect and send data actually happens during this function call.
As soon as Simulator::Run returns, the simulation is complete and we enter the tear- down phase. In this case, Simulator::Destroy takes care of the gory details and we just return a success code after it completes.