Output from the simulation will look something like the following:
void swat::Client::Send(): Sent 64 bytes to 10.1.1.4 [0a:01:01:04:50:00]---'mswm fsntyuryxpguffuyhjusuwojsfirknvbimkyaoudfqvjjlyfcndkakncmw' void swat::Client::Sent(ns3::Ptr, uint32_t): Transmit finished noti fication for 64 bytes void swat::Client::Send(): Sent 64 bytes to 255.255.255.255 [ff:ff:ff:ff:50:00]- --'schmjhvwoeawhoatraqgqfftrueuxlpvckirrjjsrwhnpcwcyldhfoevhigguj' void swat::Server::Receive(ns3::Ptr, const ns3::Packet&, const ns3: :Address&): Received 64 bytes from 10.1.1.1 [0a:01:01:01:02:04]---'mswmfsntyuryx pguffuyhjusuwojsfirknvbimkyaoudfqvjjlyfcndkakncmw' void swat::Server::Receive(ns3::Ptr, const ns3::Packet&, const ns3: :Address&): Received 64 bytes from 10.1.1.2 [0a:01:01:02:02:04]---'schmjhvwoeawh oatraqgqfftrueuxlpvckirrjjsrwhnpcwcyldhfoevhigguj' void swat::Server::Receive(ns3::Ptr, const ns3::Packet&, const ns3: :Address&): Received 64 bytes from 10.1.1.2 [0a:01:01:02:02:04]---'schmjhvwoeawh oatraqgqfftrueuxlpvckirrjjsrwhnpcwcyldhfoevhigguj'
General Notes
Applications use Socket objects to generate traffic. It's very similar to BSD-style sockets, though with a few changes. In particular, none of the functions block. Further,sockets are created by fetching a socket factory from the host node for a given protocol type, i.e. UDP.
This guide skips many error checks and sane programming practices (i.e. not hardcoding numbers).
As of this writing (2007/08/24), these applications require two patches be applied to the main branch for them to operate correctly. The server will receive an incorrect source IP otherwise. There is also an additional problem w/ source IPs for broadcasts which should be patched shortly.
Basics
Applications must derive from the Application class defined in ns3/application.h.
The class should override the StartApplication and StopApplication classes. These will be called at the time scheduled by the scenario program using the Start and Stop methods.
virtual void StartApplication (void); virtual void StopApplication (void);
In addition, as Application subclasses the Object class, applications should override the DoDispose method as well. This is called when an object and all of its components (e.g. aggregated interfaces) are to be destroyed, such as when the simulation is torn down. Objects are not usable after Dispose and the consequent DoDispose method calls.
virtual void DoDispose (void);
Note that the custom application should forward on the dispose call after conducting its own cleanup, i.e.:
void Client::DoDispose (void) {
// Clean up Client here...
// Forward up the request
Application::DoDispose();
// end Client::DoDispose
}
The custom application class also needs to provide a constructor and chain up a call to the Application constructor, e.g.:
Server::Server(Ptrn) : Application(n) { // end Server::Server }
That's all the functionality required of an application. Everything else is up to its individual logic.
The Client
Usage
Usage of the client is very simple, consisting of attaching it to a node and optionally specifying a destination address. A scenario program uses it as follows.
Ptrc0 = Create (n0, InetSocketAddress("10.1.1.4", 80)); c0->Start(Seconds(1.0)); c0->Stop (Seconds(10.0));
Initialization
The client provides two constructors, one which takes a destination and another that defaults to a broadcast destination. Both call an initialization function which sets default values for packet size and message rate.
Client::Client(Ptrn, const Address &t) : Application(n) { Initialize(n, t); // end Client:Client } void Client::Initialize(Ptr n, const Address &t) { socket = NULL; target = t; interval = 1; packetSize = 64; // end Initialize }
Note that the client internally uses a generic Address for the target. The display output assumes this is an InetSocketAddress and defaults to an Internet address local broadcast, but otherwise it does not matter and a different addressing scheme could be passed in, i.e. to run over a non-Internet network.
The destructor and disposal function of the client are very simple, the latter merely releasing the socket if it has been allocated.
Client::~Client() {
// end Client::~Client
}
void Client::DoDispose (void) {
if (socket != NULL) {
socket->Close();
}
// Forward up the request
Application::DoDispose();
// end Client::DoDispose
}
Startup
The scenario program defines the runtime of the application using the Start and Stop methods. These respectively schedule the StartApplication and StopApplication functions.
Here the client's StartApplication function creates the socket, sets a callback to be executed whenever a message finishes transmission, and then schedules the first message.
void Client::StartApplication (void) {
if (socket == NULL) {
InterfaceId protoIID = InterfaceId::LookupByName("Udp");
Ptr socketFactory = GetNode()->
QueryInterface (protoIID);
socket = socketFactory->CreateSocket();
socket->Bind(); // NS-3 UDP sockets call Bind to create the
// endpoint on Send()/SendTo() if it has not
// been created already.
// Connect is not necessary for using UDP sockets. May by used
// to provide defaults for Send(), which are overridden using
// SendTo().
// socket->Connect(target);
}
socket->SetSendCallback((Callback , uint32_t>)
MakeCallback(&Client::Sent, this));
ScheduleNextTx();
// end Client::StartApplication
}
Note that a socket object is not created directly. Rather it is fetched from a socket factory provided by the host node after specifying the protocol, UDP in this case.
The Connect method is not used here, although as the client only ever sends messages to one destination it be reasonable to do so. As with traditional BSD sockets, UDP Connect specifies a default destination which is used for Send calls. Packets may be sent to other destinations using SendTo. In NS-3 this incurs a lookup for the correct source IP for that destination. Using Send skips this step, a minor optimization for repeated messages. Note that this requires the patches mentioned above, or SendTo will not work after a Connect.
The optional send callback provides notification that the device has finished transmitting. It does not indicate that a packet has been received. Further, it does not currently provide for identifying the finished packet. The client's callback simply displays how many bytes have been sent:
void Client::Sent(Ptrsock, uint32_t length) { cout << __PRETTY_FUNCTION__ << ": Transmit finished notification for " << length << " bytes" << endl << endl; // end Sent }
Transmission
The first packet is scheduled by simply posting the Send function to the scheduler for a given number of seconds away.
void Client::ScheduleNextTx() {
sendEvent = Simulator::Schedule(Seconds(interval),
&Client::Send,
this);
// end Client::ScheduleNextTx
}
The Send function generates a random string of letters, sends it to the destination, and schedules the next packet using the same ScheduleNextTx function used to schedule the initial transmission.
void Client::Send() {
char buff[packetSize];
for (uint32_t i = 0; i Send(p); // Can use if Connect() call was made;
// saves a lookup on the proper source IP.
int res = socket->SendTo(target, p);
if (res < 0) {
cout << __PRETTY_FUNCTION__ << ": Error on send; returned " << res<
Note that even without a server listening, traffic will be sent to the destination host if possible, and will show up on any trace logs there (as it should). For many simulations it is not necessary to have anything listening to generated traffic, i.e. when it is the routing or other behavior below the application layer that is of interest.
Shutdown
Shutting down the client is simply a matter of canceling the pending transmission.
void Client::StopApplication (void) {
Simulator::Cancel(sendEvent);
// end Client:StopApplication
}
The Server
Usage
The server is configured by the scenario program in nearly identical fashion to the client. Its only parameter is the port to listen on, which is optional.
Ptr s0 = Create (n3, 80);
s0->Start(Seconds(0.5));
s0->Stop(Seconds(10.5));
Initialization
All the server must do to initialize is store the port number to use.
Server::Server(Ptr n, uint16_t p) : Application(n) {
Initialize(n, p);
// end Server::Server
}
void Server::Initialize(Ptr n, uint16_t p) {
socket = NULL;
port = p;
// end Server::Initialize
}
The destructor and dispose functions are identical to the client's.
Server::~Server() {
// end Server::~Server
}
void Server::DoDispose (void) {
if (socket != NULL) {
socket->Close();
}
Application::DoDispose();
// end Server::DoDispose
}
Startup
Just as with the client, on startup the server creates its socket. It also registers the callback to be used when traffic arrives on its endpoint (address and port binding).
void Server::StartApplication (void) {
if (socket == NULL) {
InterfaceId protoIID = InterfaceId::LookupByName("Udp");
Ptr socketFactory = GetNode()->
QueryInterface (protoIID);
socket = socketFactory->CreateSocket();
socket->Bind(InetSocketAddress(port));
}
socket->SetRecvCallback((Callback , const Packet &,
const Address &>)
MakeCallback(&Server::Receive, this));
// end Server::StartApplication
}
Receiving
Unlike the synchronous BSD recv and recvfrom functions, NS-3 applications receive incoming packets via a callback given to the socket. In this server application the callback is set when the application starts, as shown above. Here the application simply prints out the received message source, size, and contents.
void Server::Receive(Ptr socket, const Packet &packet,
const Address &from) {
InetSocketAddress address = InetSocketAddress::ConvertFrom (from);
cout << __PRETTY_FUNCTION__ << ": Received " << packet.GetSize() <<
" bytes from " << address.GetIpv4() << " [" << address << "]---'" <<
packet.PeekData() << "'" << endl;
// end Server::Receive
}
Shutdown
Stopping the server is simply a matter of removing the receive callback from the socket.
void Server::StopApplication (void) {
if (socket != NULL) {
socket->SetRecvCallback((Callback , const Packet &,
const Address &>) NULL);
}
// end Server::StopApplication
}
Wrap-Up
That's all there is to this simple client/server pair. Full listings and a scenario driver are available here.