doc: remove space before paren in code samples
This commit is contained in:
committed by
Peter Barnes
parent
820c029e8b
commit
700543b01a
@@ -326,8 +326,8 @@ Indent constructor's initialization list with 4 spaces.
|
||||
.. sourcecode:: cpp
|
||||
|
||||
MyClass::MyClass(int x, int y)
|
||||
: m_x (x),
|
||||
m_y (y)
|
||||
: m_x(x),
|
||||
m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -837,7 +837,7 @@ Casts
|
||||
=====
|
||||
|
||||
Where casts are necessary, use the Google C++ guidance: "Use C++-style casts
|
||||
like ``static_cast<float> (double_value)``, or brace initialization for
|
||||
like ``static_cast<float>(double_value)``, or brace initialization for
|
||||
conversion of arithmetic types like ``int64 y = int64{1} << 42``."
|
||||
Do not use C-style casts, since they can be unsafe.
|
||||
|
||||
@@ -1047,13 +1047,13 @@ the |ns3| smart pointer class ``Ptr`` should be used in boolean comparisons as f
|
||||
if (p == NULL) {...}
|
||||
if (p == 0) {...}
|
||||
|
||||
NS_ASSERT... (p != nullptr, ...) NS_ASSERT... (p, ...)
|
||||
NS_ABORT... (p != nullptr, ...) NS_ABORT... (p, ...)
|
||||
NS_ASSERT...(p != nullptr, ...) NS_ASSERT...(p, ...)
|
||||
NS_ABORT... (p != nullptr, ...) NS_ABORT... (p, ...)
|
||||
|
||||
NS_ASSERT... (p == nullptr, ...) NS_ASSERT... (!p, ...)
|
||||
NS_ABORT... (p == nullptr, ...) NS_ABORT... (!p, ...)
|
||||
NS_ASSERT...(p == nullptr, ...) NS_ASSERT...(!p, ...)
|
||||
NS_ABORT... (p == nullptr, ...) NS_ABORT... (!p, ...)
|
||||
|
||||
NS_TEST... (p, nullptr, ...) NS_TEST... (p, nullptr, ...)
|
||||
NS_TEST... (p, nullptr, ...) NS_TEST... (p, nullptr, ...)
|
||||
|
||||
C++ standard
|
||||
============
|
||||
@@ -1069,9 +1069,9 @@ Miscellaneous items
|
||||
===================
|
||||
|
||||
- ``NS_LOG_COMPONENT_DEFINE("log-component-name");`` statements should be
|
||||
placed within namespace ns3 (for module code) and after the
|
||||
placed within ``namespace ns3`` (for module code) and after the
|
||||
``using namespace ns3;``. In examples,
|
||||
``NS_OBJECT_ENSURE_REGISTERED()`` should also be placed within namespace ``ns3``.
|
||||
``NS_OBJECT_ENSURE_REGISTERED()`` should also be placed within ``namespace ns3``.
|
||||
|
||||
- Pointers and references are left-aligned:
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ specificity, these are:
|
||||
+=====================================+====================================+
|
||||
| Default Attribute values set when | Affect all instances of the class. |
|
||||
| Attributes are defined in | |
|
||||
| :cpp:func:`GetTypeId ()`. | |
|
||||
| :cpp:func:`GetTypeId()`. | |
|
||||
+-------------------------------------+------------------------------------+
|
||||
| :cpp:class:`CommandLine` | Affect all future instances. |
|
||||
| :cpp:func:`Config::SetDefault()` | |
|
||||
@@ -44,8 +44,8 @@ specificity, these are:
|
||||
| Helper methods with (string/ | Affects all instances created by |
|
||||
| AttributeValue) parameter pairs | the helper. |
|
||||
+-------------------------------------+------------------------------------+
|
||||
| :cpp:func:`MyClass::SetX ()` | Alters this particular instance. |
|
||||
| :cpp:func:`Object::SetAttribute ()` | Generally this is the only form |
|
||||
| :cpp:func:`MyClass::SetX()` | Alters this particular instance. |
|
||||
| :cpp:func:`Object::SetAttribute()` | Generally this is the only form |
|
||||
| :cpp:func:`Config::Set()` | which can be scheduled to alter |
|
||||
| | an instance once the simulation |
|
||||
| | is running. |
|
||||
@@ -99,7 +99,7 @@ references to heap-allocated objects that may cause memory leaks.
|
||||
For most basic usage (syntax), treat a smart pointer like a regular pointer::
|
||||
|
||||
Ptr<WifiNetDevice> nd = ...;
|
||||
nd->CallSomeFunction ();
|
||||
nd->CallSomeFunction();
|
||||
// etc.
|
||||
|
||||
So how do you get a smart pointer to an object, as in the first line
|
||||
@@ -111,24 +111,24 @@ CreateObject
|
||||
As we discussed above in :ref:`Memory-management-and-class-Ptr`, at the
|
||||
lowest-level API, objects of type :cpp:class:`Object` are not instantiated
|
||||
using ``operator new`` as usual but instead by a templated function called
|
||||
:cpp:func:`CreateObject ()`.
|
||||
:cpp:func:`CreateObject()`.
|
||||
|
||||
A typical way to create such an object is as follows::
|
||||
|
||||
Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice> ();
|
||||
Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice>();
|
||||
|
||||
You can think of this as being functionally equivalent to::
|
||||
|
||||
WifiNetDevice* nd = new WifiNetDevice ();
|
||||
WifiNetDevice* nd = new WifiNetDevice();
|
||||
|
||||
Objects that derive from :cpp:class:`Object` must be allocated on the heap
|
||||
using :cpp:func:`CreateObject ()`. Those deriving from :cpp:class:`ObjectBase`,
|
||||
using :cpp:func:`CreateObject()`. Those deriving from :cpp:class:`ObjectBase`,
|
||||
such as |ns3| helper functions and packet headers and trailers,
|
||||
can be allocated on the stack.
|
||||
|
||||
In some scripts, you may not see a lot of :cpp:func:`CreateObject ()` calls
|
||||
In some scripts, you may not see a lot of :cpp:func:`CreateObject()` calls
|
||||
in the code; this is because there are some helper objects in effect
|
||||
that are doing the :cpp:func:`CreateObject ()` calls for you.
|
||||
that are doing the :cpp:func:`CreateObject()` calls for you.
|
||||
|
||||
TypeId
|
||||
++++++
|
||||
@@ -150,39 +150,39 @@ Putting all of these concepts together, let's look at a specific
|
||||
example: class :cpp:class:`Node`.
|
||||
|
||||
The public header file ``node.h`` has a declaration that includes
|
||||
a static :cpp:func:`GetTypeId ()` function call::
|
||||
a static :cpp:func:`GetTypeId()` function call::
|
||||
|
||||
class Node : public Object
|
||||
{
|
||||
public:
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
...
|
||||
|
||||
This is defined in the ``node.cc`` file as follows::
|
||||
|
||||
TypeId
|
||||
Node::GetTypeId ()
|
||||
Node::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::Node")
|
||||
.SetParent<Object> ()
|
||||
.SetGroupName ("Network")
|
||||
.AddConstructor<Node> ()
|
||||
.AddAttribute ("DeviceList",
|
||||
"The list of devices associated to this Node.",
|
||||
ObjectVectorValue (),
|
||||
MakeObjectVectorAccessor (&Node::m_devices),
|
||||
MakeObjectVectorChecker<NetDevice> ())
|
||||
.AddAttribute ("ApplicationList",
|
||||
"The list of applications associated to this Node.",
|
||||
ObjectVectorValue (),
|
||||
MakeObjectVectorAccessor (&Node::m_applications),
|
||||
MakeObjectVectorChecker<Application> ())
|
||||
.AddAttribute ("Id",
|
||||
"The id (unique integer) of this Node.",
|
||||
TypeId::ATTR_GET, // allow only getting it.
|
||||
UintegerValue (0),
|
||||
MakeUintegerAccessor (&Node::m_id),
|
||||
MakeUintegerChecker<uint32_t> ())
|
||||
static TypeId tid = TypeId("ns3::Node")
|
||||
.SetParent<Object>()
|
||||
.SetGroupName("Network")
|
||||
.AddConstructor<Node>()
|
||||
.AddAttribute("DeviceList",
|
||||
"The list of devices associated to this Node.",
|
||||
ObjectVectorValue(),
|
||||
MakeObjectVectorAccessor(&Node::m_devices),
|
||||
MakeObjectVectorChecker<NetDevice>())
|
||||
.AddAttribute("ApplicationList",
|
||||
"The list of applications associated to this Node.",
|
||||
ObjectVectorValue(),
|
||||
MakeObjectVectorAccessor(&Node::m_applications),
|
||||
MakeObjectVectorChecker<Application>())
|
||||
.AddAttribute("Id",
|
||||
"The id(unique integer) of this Node.",
|
||||
TypeId::ATTR_GET, // allow only getting it.
|
||||
UintegerValue(0),
|
||||
MakeUintegerAccessor(&Node::m_id),
|
||||
MakeUintegerChecker<uint32_t>())
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
@@ -192,38 +192,38 @@ as an extended form of run time type information (RTTI). The C++ language
|
||||
includes a simple kind of RTTI in order to support ``dynamic_cast`` and
|
||||
``typeid`` operators.
|
||||
|
||||
The :cpp:func:`SetParent<Object> ()` call in the definition above is used in
|
||||
The :cpp:func:`SetParent<Object>()` call in the definition above is used in
|
||||
conjunction with our object aggregation mechanisms to allow safe up- and
|
||||
down-casting in inheritance trees during :cpp:func:`GetObject ()`.
|
||||
down-casting in inheritance trees during :cpp:func:`GetObject()`.
|
||||
It also enables subclasses to inherit the Attributes of their parent class.
|
||||
|
||||
The :cpp:func:`AddConstructor<Node> ()` call is used in conjunction
|
||||
The :cpp:func:`AddConstructor<Node>()` call is used in conjunction
|
||||
with our abstract object factory mechanisms to allow us to construct
|
||||
C++ objects without forcing a user to know the concrete class of
|
||||
the object she is building.
|
||||
|
||||
The three calls to :cpp:func:`AddAttribute ()` associate a given string
|
||||
The three calls to :cpp:func:`AddAttribute()` associate a given string
|
||||
with a strongly typed value in the class. Notice that you must provide
|
||||
a help string which may be displayed, for example, *via* command line
|
||||
processors. Each :cpp:class:`Attribute` is associated with mechanisms
|
||||
for accessing the underlying member variable in the object (for example,
|
||||
:cpp:func:`MakeUintegerAccessor ()` tells the generic :cpp:class:`Attribute`
|
||||
:cpp:func:`MakeUintegerAccessor()` tells the generic :cpp:class:`Attribute`
|
||||
code how to get to the node ID above). There are also "Checker" methods which
|
||||
are used to validate values against range limitations, such as maximum
|
||||
and minimum allowed values.
|
||||
|
||||
When users want to create Nodes, they will usually call some form of
|
||||
:cpp:func:`CreateObject ()`,::
|
||||
:cpp:func:`CreateObject()`,::
|
||||
|
||||
Ptr<Node> n = CreateObject<Node> ();
|
||||
Ptr<Node> n = CreateObject<Node>();
|
||||
|
||||
or more abstractly, using an object factory, you can create a
|
||||
:cpp:class:`Node` object without even knowing the concrete C++ type::
|
||||
|
||||
ObjectFactory factory;
|
||||
const std::string typeId = "ns3::Node'';
|
||||
factory.SetTypeId (typeId);
|
||||
Ptr<Object> node = factory.Create <Object> ();
|
||||
factory.SetTypeId(typeId);
|
||||
Ptr<Object> node = factory.Create <Object>();
|
||||
|
||||
Both of these methods result in fully initialized attributes being available
|
||||
in the resulting :cpp:class:`Object` instances.
|
||||
@@ -359,7 +359,7 @@ the following::
|
||||
|
||||
class QueueBase : public Object {
|
||||
public:
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
...
|
||||
|
||||
private:
|
||||
@@ -408,34 +408,34 @@ Let's consider things that a user may want to do with the value of
|
||||
to that default.
|
||||
* Set or get the value on an already instantiated queue.
|
||||
|
||||
The above things typically require providing ``Set ()`` and ``Get ()``
|
||||
The above things typically require providing ``Set()`` and ``Get()``
|
||||
functions, and some type of global default value.
|
||||
|
||||
In the |ns3| attribute system, these value definitions and accessor function
|
||||
registrations are moved into the :cpp:class:`TypeId` class; *e.g*.::
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (QueueBase);
|
||||
NS_OBJECT_ENSURE_REGISTERED(QueueBase);
|
||||
|
||||
TypeId
|
||||
QueueBase::GetTypeId ()
|
||||
QueueBase::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::DropTailQueue")
|
||||
.SetParent<Queue> ()
|
||||
.SetGroupName ("Network")
|
||||
static TypeId tid = TypeId("ns3::DropTailQueue")
|
||||
.SetParent<Queue>()
|
||||
.SetGroupName("Network")
|
||||
...
|
||||
.AddAttribute ("MaxSize",
|
||||
"The max queue size",
|
||||
QueueSizeValue (QueueSize ("100p")),
|
||||
MakeQueueSizeAccessor (&QueueBase::SetMaxSize,
|
||||
&QueueBase::GetMaxSize),
|
||||
MakeQueueSizeChecker ())
|
||||
.AddAttribute("MaxSize",
|
||||
"The max queue size",
|
||||
QueueSizeValue(QueueSize("100p")),
|
||||
MakeQueueSizeAccessor(&QueueBase::SetMaxSize,
|
||||
&QueueBase::GetMaxSize),
|
||||
MakeQueueSizeChecker())
|
||||
...
|
||||
;
|
||||
|
||||
return tid;
|
||||
}
|
||||
|
||||
The :cpp:func:`AddAttribute ()` method is performing a number of things for the
|
||||
The :cpp:func:`AddAttribute()` method is performing a number of things for the
|
||||
:cpp:member:`m_maxSize` value:
|
||||
|
||||
* Binding the (usually private) member variable :cpp:member:`m_maxSize`
|
||||
@@ -452,7 +452,7 @@ we will provide an example script that shows how users may manipulate
|
||||
these values.
|
||||
|
||||
Note that initialization of the attribute relies on the macro
|
||||
``NS_OBJECT_ENSURE_REGISTERED (QueueBase)`` being called; if you leave this
|
||||
``NS_OBJECT_ENSURE_REGISTERED(QueueBase)`` being called; if you leave this
|
||||
out of your new class implementation, your attributes will not be initialized
|
||||
correctly.
|
||||
|
||||
@@ -486,13 +486,13 @@ function begins::
|
||||
//
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
// Queues in ns-3 are objects that hold items (other objects) in
|
||||
// a queue structure. The C++ implementation uses templates to
|
||||
// allow queues to hold various types of items, but the most
|
||||
// common is a pointer to a packet (Ptr<Packet>).
|
||||
// common is a pointer to a packet(Ptr<Packet>).
|
||||
//
|
||||
// The maximum queue size can either be enforced in bytes ('b') or
|
||||
// packets ('p'). A special type called the ns3::QueueSize can
|
||||
@@ -505,12 +505,12 @@ function begins::
|
||||
//
|
||||
// Here, we set it to 80 packets. We could use one of two value types:
|
||||
// a string-based value or a QueueSizeValue value
|
||||
Config::SetDefault ("ns3::QueueBase::MaxSize", StringValue ("80p"));
|
||||
Config::SetDefault("ns3::QueueBase::MaxSize", StringValue("80p"));
|
||||
// The below function call is redundant
|
||||
Config::SetDefault ("ns3::QueueBase::MaxSize", QueueSizeValue (QueueSize (QueueSizeUnit::PACKETS, 80)));
|
||||
Config::SetDefault("ns3::QueueBase::MaxSize", QueueSizeValue(QueueSize(QueueSizeUnit::PACKETS, 80)));
|
||||
|
||||
The main thing to notice in the above are the two equivalent calls to
|
||||
:cpp:func:`Config::SetDefault ()`. This is how we set the default value
|
||||
:cpp:func:`Config::SetDefault()`. This is how we set the default value
|
||||
for all subsequently instantiated :cpp:class:`DropTailQueue`\s. We illustrate
|
||||
that two types of ``Value`` classes, a :cpp:class:`StringValue` and
|
||||
a :cpp:class:`QueueSizeValue` class, can be used to assign the value
|
||||
@@ -532,21 +532,21 @@ the :cpp:class:`CommandLine` API documentation.
|
||||
// For example, via "--ns3::QueueBase::MaxSize=80p"
|
||||
CommandLine cmd;
|
||||
// This provides yet another way to set the value from the command line:
|
||||
cmd.AddValue ("maxSize", "ns3::QueueBase::MaxSize");
|
||||
cmd.Parse (argc, argv);
|
||||
cmd.AddValue("maxSize", "ns3::QueueBase::MaxSize");
|
||||
cmd.Parse(argc, argv);
|
||||
|
||||
Now, we will create a few objects using the low-level API. Our
|
||||
newly created queues will not have :cpp:member:`m_maxSize` initialized to
|
||||
0 packets, as defined in the :cpp:func:`QueueBase::GetTypeId ()`
|
||||
0 packets, as defined in the :cpp:func:`QueueBase::GetTypeId()`
|
||||
function, but to 80 packets, because of what we did above with
|
||||
default values.::
|
||||
|
||||
Ptr<Node> n0 = CreateObject<Node> ();
|
||||
Ptr<Node> n0 = CreateObject<Node>();
|
||||
|
||||
Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice> ();
|
||||
n0->AddDevice (net0);
|
||||
Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice>();
|
||||
n0->AddDevice(net0);
|
||||
|
||||
Ptr<Queue<Packet> > q = CreateObject<DropTailQueue<Packet> > ();
|
||||
Ptr<Queue<Packet> > q = CreateObject<DropTailQueue<Packet> >();
|
||||
net0->AddQueue(q);
|
||||
|
||||
At this point, we have created a single :cpp:class:`Node` (``n0``)
|
||||
@@ -569,23 +569,23 @@ the helper and low-level APIs; either from the constructors themselves::
|
||||
|
||||
Ptr<GridPositionAllocator> p =
|
||||
CreateObjectWithAttributes<GridPositionAllocator>
|
||||
("MinX", DoubleValue (-100.0),
|
||||
"MinY", DoubleValue (-100.0),
|
||||
"DeltaX", DoubleValue (5.0),
|
||||
"DeltaY", DoubleValue (20.0),
|
||||
"GridWidth", UintegerValue (20),
|
||||
"LayoutType", StringValue ("RowFirst"));
|
||||
("MinX", DoubleValue(-100.0),
|
||||
"MinY", DoubleValue(-100.0),
|
||||
"DeltaX", DoubleValue(5.0),
|
||||
"DeltaY", DoubleValue(20.0),
|
||||
"GridWidth", UintegerValue(20),
|
||||
"LayoutType", StringValue("RowFirst"));
|
||||
|
||||
or from the higher-level helper APIs, such as::
|
||||
|
||||
mobility.SetPositionAllocator
|
||||
("ns3::GridPositionAllocator",
|
||||
"MinX", DoubleValue (-100.0),
|
||||
"MinY", DoubleValue (-100.0),
|
||||
"DeltaX", DoubleValue (5.0),
|
||||
"DeltaY", DoubleValue (20.0),
|
||||
"GridWidth", UintegerValue (20),
|
||||
"LayoutType", StringValue ("RowFirst"));
|
||||
("ns3::GridPositionAllocator",
|
||||
"MinX", DoubleValue(-100.0),
|
||||
"MinY", DoubleValue(-100.0),
|
||||
"DeltaX", DoubleValue(5.0),
|
||||
"DeltaY", DoubleValue(20.0),
|
||||
"GridWidth", UintegerValue(20),
|
||||
"LayoutType", StringValue("RowFirst"));
|
||||
|
||||
We don't illustrate it here, but you can also configure an
|
||||
:cpp:class:`ObjectFactory` with new values for specific attributes.
|
||||
@@ -596,9 +596,9 @@ one of the helper APIs for the class.
|
||||
To review, there are several ways to set values for attributes for
|
||||
class instances *to be created in the future:*
|
||||
|
||||
* :cpp:func:`Config::SetDefault ()`
|
||||
* :cpp:func:`CommandLine::AddValue ()`
|
||||
* :cpp:func:`CreateObjectWithAttributes<> ()`
|
||||
* :cpp:func:`Config::SetDefault()`
|
||||
* :cpp:func:`CommandLine::AddValue()`
|
||||
* :cpp:func:`CreateObjectWithAttributes<>()`
|
||||
* Various helper APIs
|
||||
|
||||
But what if you've already created an instance, and you want
|
||||
@@ -624,35 +624,35 @@ First, we observe that we can get a pointer to the (base class)
|
||||
``"TxQueue"``::
|
||||
|
||||
PointerValue ptr;
|
||||
net0->GetAttribute ("TxQueue", ptr);
|
||||
Ptr<Queue<Packet> > txQueue = ptr.Get<Queue<Packet> > ();
|
||||
net0->GetAttribute("TxQueue", ptr);
|
||||
Ptr<Queue<Packet> > txQueue = ptr.Get<Queue<Packet> >();
|
||||
|
||||
Using the :cpp:func:`GetObject ()` function, we can perform a safe downcast
|
||||
Using the :cpp:func:`GetObject()` function, we can perform a safe downcast
|
||||
to a :cpp:class:`DropTailQueue`. The `NS_ASSERT` checks that the pointer is
|
||||
valid.
|
||||
|
||||
::
|
||||
|
||||
Ptr<DropTailQueue<Packet> > dtq = txQueue->GetObject <DropTailQueue<Packet> > ();
|
||||
NS_ASSERT (dtq != 0);
|
||||
Ptr<DropTailQueue<Packet> > dtq = txQueue->GetObject <DropTailQueue<Packet> >();
|
||||
NS_ASSERT(dtq);
|
||||
|
||||
Next, we can get the value of an attribute on this queue. We have introduced
|
||||
wrapper ``Value`` classes for the underlying data types, similar
|
||||
to Java wrappers around these types, since the attribute system stores values
|
||||
serialized to strings, and not disparate types. Here, the attribute value
|
||||
is assigned to a :cpp:class:`QueueSizeValue`, and the :cpp:func:`Get ()`
|
||||
method on this value produces the (unwrapped) ``QueueSize``. That is,
|
||||
is assigned to a :cpp:class:`QueueSizeValue`, and the :cpp:func:`Get()`
|
||||
the variable `limit` is written into by the GetAttribute method.::
|
||||
|
||||
QueueSizeValue limit;
|
||||
dtq->GetAttribute ("MaxSize", limit);
|
||||
NS_LOG_INFO ("1. dtq limit: " << limit.Get ());
|
||||
dtq->GetAttribute("MaxSize", limit);
|
||||
NS_LOG_INFO("1. dtq limit: " << limit.Get());
|
||||
|
||||
Note that the above downcast is not really needed; we could have gotten
|
||||
the attribute value directly from ``txQueue``::
|
||||
|
||||
txQueue->GetAttribute ("MaxSize", limit);
|
||||
NS_LOG_INFO ("2. txQueue limit: " << limit.Get ());
|
||||
txQueue->GetAttribute("MaxSize", limit);
|
||||
NS_LOG_INFO("2. txQueue limit: " << limit.Get());
|
||||
|
||||
Now, let's set it to another value (60 packets). Let's also make
|
||||
use of the StringValue shorthand notation to set the size by
|
||||
@@ -661,9 +661,9 @@ by either the `p` or `b` character).
|
||||
|
||||
::
|
||||
|
||||
txQueue->SetAttribute ("MaxSize", StringValue ("60p"));
|
||||
txQueue->GetAttribute ("MaxSize", limit);
|
||||
NS_LOG_INFO ("3. txQueue limit changed: " << limit.Get ());
|
||||
txQueue->SetAttribute("MaxSize", StringValue("60p"));
|
||||
txQueue->GetAttribute("MaxSize", limit);
|
||||
NS_LOG_INFO("3. txQueue limit changed: " << limit.Get());
|
||||
|
||||
Config Namespace Path
|
||||
=====================
|
||||
@@ -675,11 +675,11 @@ would like to configure a specific attribute with a single statement.
|
||||
|
||||
::
|
||||
|
||||
Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxSize",
|
||||
StringValue ("25p"));
|
||||
txQueue->GetAttribute ("MaxSize", limit);
|
||||
NS_LOG_INFO ("4. txQueue limit changed through namespace: "
|
||||
<< limit.Get ());
|
||||
Config::Set("/NodeList/0/DeviceList/0/TxQueue/MaxSize",
|
||||
StringValue("25p"));
|
||||
txQueue->GetAttribute("MaxSize", limit);
|
||||
NS_LOG_INFO("4. txQueue limit changed through namespace: "
|
||||
<< limit.Get());
|
||||
|
||||
The configuration path often has the form of
|
||||
``".../<container name>/<index>/.../<attribute>/<attribute>"``
|
||||
@@ -691,14 +691,14 @@ ends with a succession of member attributes, in this case the ``"MaxSize"``
|
||||
attribute of the ``"TxQueue"`` of the chosen :cpp:class:`NetDevice`.
|
||||
|
||||
We could have also used wildcards to set this value for all nodes and all net
|
||||
devices (which in this simple example has the same effect as the previous
|
||||
:cpp:func:`Config::Set ()`)::
|
||||
devices(which in this simple example has the same effect as the previous
|
||||
:cpp:func:`Config::Set()`)::
|
||||
|
||||
Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxSize",
|
||||
StringValue ("15p"));
|
||||
txQueue->GetAttribute ("MaxSize", limit);
|
||||
NS_LOG_INFO ("5. txQueue limit changed through wildcarded namespace: "
|
||||
<< limit.Get ());
|
||||
Config::Set("/NodeList/*/DeviceList/*/TxQueue/MaxSize",
|
||||
StringValue("15p"));
|
||||
txQueue->GetAttribute("MaxSize", limit);
|
||||
NS_LOG_INFO("5. txQueue limit changed through wildcarded namespace: "
|
||||
<< limit.Get());
|
||||
|
||||
If you run this program from the command line, you should see the following
|
||||
output corresponding to the steps we took above:
|
||||
@@ -724,12 +724,12 @@ namespace path.
|
||||
|
||||
::
|
||||
|
||||
Names::Add ("server", n0);
|
||||
Names::Add ("server/eth0", net0);
|
||||
Names::Add("server", n0);
|
||||
Names::Add("server/eth0", net0);
|
||||
|
||||
...
|
||||
|
||||
Config::Set ("/Names/server/eth0/TxQueue/MaxPackets", UintegerValue (25));
|
||||
Config::Set("/Names/server/eth0/TxQueue/MaxPackets", UintegerValue(25));
|
||||
|
||||
Here we've added the path elements ``"server"`` and ``"eth0"`` under
|
||||
the ``"/Names/"`` namespace, then used the resulting configuration path
|
||||
@@ -755,8 +755,8 @@ or *via* strings. Direct implicit conversion of types to
|
||||
:cpp:class:`AttributeValue` is not really practical.
|
||||
So in the above, users have a choice of using strings or values::
|
||||
|
||||
p->Set ("cwnd", StringValue ("100")); // string-based setter
|
||||
p->Set ("cwnd", IntegerValue (100)); // integer-based setter
|
||||
p->Set("cwnd", StringValue("100")); // string-based setter
|
||||
p->Set("cwnd", IntegerValue(100)); // integer-based setter
|
||||
|
||||
The system provides some macros that help users declare and define
|
||||
new AttributeValue subclasses for new types that they want to introduce into
|
||||
@@ -792,18 +792,18 @@ In general, the attribute code to assign values to the underlying class member
|
||||
variables is executed after an object is constructed. But what if you need the
|
||||
values assigned before the constructor body executes, because you need them in
|
||||
the logic of the constructor? There is a way to do this, used for example in the
|
||||
class :cpp:class:`ConfigStore`: call :cpp:func:`ObjectBase::ConstructSelf ()` as
|
||||
class :cpp:class:`ConfigStore`: call :cpp:func:`ObjectBase::ConstructSelf()` as
|
||||
follows::
|
||||
|
||||
ConfigStore::ConfigStore ()
|
||||
ConfigStore::ConfigStore()
|
||||
{
|
||||
ObjectBase::ConstructSelf (AttributeConstructionList ());
|
||||
ObjectBase::ConstructSelf(AttributeConstructionList());
|
||||
// continue on with constructor.
|
||||
}
|
||||
|
||||
Beware that the object and all its derived classes must also implement
|
||||
a :cpp:func:`GetInstanceTypeId ()` method. Otherwise
|
||||
the :cpp:func:`ObjectBase::ConstructSelf ()` will not be able to read
|
||||
a :cpp:func:`GetInstanceTypeId()` method. Otherwise
|
||||
the :cpp:func:`ObjectBase::ConstructSelf()` will not be able to read
|
||||
the attributes.
|
||||
|
||||
Adding Attributes
|
||||
@@ -834,11 +834,11 @@ variable using the metadata system. If it were not already provided by |ns3|,
|
||||
the user could declare the following addition in the runtime metadata system (to
|
||||
the :cpp:func:`GetTypeId` definition for :cpp:class:`TcpSocket`)::
|
||||
|
||||
.AddAttribute ("Congestion window",
|
||||
"Tcp congestion window (bytes)",
|
||||
UintegerValue (1),
|
||||
MakeUintegerAccessor (&TcpSocket::m_cWnd),
|
||||
MakeUintegerChecker<uint16_t> ())
|
||||
.AddAttribute("Congestion window",
|
||||
"Tcp congestion window(bytes)",
|
||||
UintegerValue(1),
|
||||
MakeUintegerAccessor(&TcpSocket::m_cWnd),
|
||||
MakeUintegerChecker<uint16_t>())
|
||||
|
||||
Now, the user with a pointer to a :cpp:class:`TcpSocket` instance
|
||||
can perform operations such as
|
||||
@@ -863,7 +863,7 @@ In the ``my-mobility.h`` header file::
|
||||
class MyMobility : public MobilityModel
|
||||
{
|
||||
|
||||
This requires we declare the :cpp:func:`GetTypeId ()` function.
|
||||
This requires we declare the :cpp:func:`GetTypeId()` function.
|
||||
This is a one-line public function declaration::
|
||||
|
||||
public:
|
||||
@@ -871,31 +871,31 @@ This is a one-line public function declaration::
|
||||
* Register this type.
|
||||
* \return The object TypeId.
|
||||
*/
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
|
||||
We've already introduced what a :cpp:class:`TypeId` definition will look like
|
||||
in the ``my-mobility.cc`` implementation file::
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (MyMobility);
|
||||
NS_OBJECT_ENSURE_REGISTERED(MyMobility);
|
||||
|
||||
TypeId
|
||||
MyMobility::GetTypeId ()
|
||||
MyMobility::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::MyMobility")
|
||||
.SetParent<MobilityModel> ()
|
||||
.SetGroupName ("Mobility")
|
||||
.AddConstructor<MyMobility> ()
|
||||
.AddAttribute ("Bounds",
|
||||
"Bounds of the area to cruise.",
|
||||
RectangleValue (Rectangle (0.0, 0.0, 100.0, 100.0)),
|
||||
MakeRectangleAccessor (&MyMobility::m_bounds),
|
||||
MakeRectangleChecker ())
|
||||
.AddAttribute ("Time",
|
||||
"Change current direction and speed after moving for this delay.",
|
||||
TimeValue (Seconds (1.0)),
|
||||
MakeTimeAccessor (&MyMobility::m_modeTime),
|
||||
MakeTimeChecker ())
|
||||
// etc (more parameters).
|
||||
static TypeId tid = TypeId("ns3::MyMobility")
|
||||
.SetParent<MobilityModel>()
|
||||
.SetGroupName("Mobility")
|
||||
.AddConstructor<MyMobility>()
|
||||
.AddAttribute("Bounds",
|
||||
"Bounds of the area to cruise.",
|
||||
RectangleValue(Rectangle(0.0, 0.0, 100.0, 100.0)),
|
||||
MakeRectangleAccessor(&MyMobility::m_bounds),
|
||||
MakeRectangleChecker())
|
||||
.AddAttribute("Time",
|
||||
"Change current direction and speed after moving for this delay.",
|
||||
// etc (more parameters).
|
||||
TimeValue(Seconds(1.0)),
|
||||
MakeTimeAccessor(&MyMobility::m_modeTime),
|
||||
MakeTimeChecker())
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
@@ -903,14 +903,14 @@ in the ``my-mobility.cc`` implementation file::
|
||||
If we don't want to subclass from an existing class, in the header file
|
||||
we just inherit from :cpp:class:`ns3::Object`, and in the object file
|
||||
we set the parent class to :cpp:class:`ns3::Object` with
|
||||
``.SetParent<Object> ()``.
|
||||
``.SetParent<Object>()``.
|
||||
|
||||
Typical mistakes here involve:
|
||||
|
||||
* Not calling ``NS_OBJECT_ENSURE_REGISTERED ()``
|
||||
* Not calling the :cpp:func:`SetParent ()` method,
|
||||
* Not calling ``NS_OBJECT_ENSURE_REGISTERED()``
|
||||
* Not calling the :cpp:func:`SetParent()` method,
|
||||
or calling it with the wrong type.
|
||||
* Not calling the :cpp:func:`AddConstructor ()` method,
|
||||
* Not calling the :cpp:func:`AddConstructor()` method,
|
||||
or calling it with the wrong type.
|
||||
* Introducing a typographical error in the name of the :cpp:class:`TypeId`
|
||||
in its constructor.
|
||||
@@ -950,27 +950,27 @@ Header File
|
||||
One macro call and two operators, must be added below the class declaration in
|
||||
order to turn a Rectangle into a value usable by the ``Attribute`` system::
|
||||
|
||||
std::ostream &operator << (std::ostream &os, const Rectangle &rectangle);
|
||||
std::istream &operator >> (std::istream &is, Rectangle &rectangle);
|
||||
std::ostream &operator <<(std::ostream &os, const Rectangle &rectangle);
|
||||
std::istream &operator >>(std::istream &is, Rectangle &rectangle);
|
||||
|
||||
ATTRIBUTE_HELPER_HEADER (Rectangle);
|
||||
ATTRIBUTE_HELPER_HEADER(Rectangle);
|
||||
|
||||
Implementation File
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the class definition (``.cc`` file), the code looks like this::
|
||||
|
||||
ATTRIBUTE_HELPER_CPP (Rectangle);
|
||||
ATTRIBUTE_HELPER_CPP(Rectangle);
|
||||
|
||||
std::ostream &
|
||||
operator << (std::ostream &os, const Rectangle &rectangle)
|
||||
operator <<(std::ostream &os, const Rectangle &rectangle)
|
||||
{
|
||||
os << rectangle.xMin << "|" << rectangle.xMax << "|" << rectangle.yMin << "|"
|
||||
<< rectangle.yMax;
|
||||
return os;
|
||||
}
|
||||
std::istream &
|
||||
operator >> (std::istream &is, Rectangle &rectangle)
|
||||
operator >>(std::istream &is, Rectangle &rectangle)
|
||||
{
|
||||
char c1, c2, c3;
|
||||
is >> rectangle.xMin >> c1 >> rectangle.xMax >> c2 >> rectangle.yMin >> c3
|
||||
@@ -979,13 +979,13 @@ In the class definition (``.cc`` file), the code looks like this::
|
||||
c2 != '|' ||
|
||||
c3 != '|')
|
||||
{
|
||||
is.setstate (std::ios_base::failbit);
|
||||
is.setstate(std::ios_base::failbit);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
These stream operators simply convert from a string representation of the
|
||||
Rectangle (``"xMin|xMax|yMin|yMax"``) to the underlying Rectangle. The modeler
|
||||
Rectangle(``"xMin|xMax|yMin|yMax"``) to the underlying Rectangle. The modeler
|
||||
must specify these operators and the string syntactical representation of an
|
||||
instance of the new class.
|
||||
|
||||
@@ -1014,36 +1014,36 @@ to show how the system is extended::
|
||||
class ConfigExample : public Object
|
||||
{
|
||||
public:
|
||||
static TypeId GetTypeId () {
|
||||
static TypeId tid = TypeId ("ns3::A")
|
||||
.SetParent<Object> ()
|
||||
.AddAttribute ("TestInt16", "help text",
|
||||
IntegerValue (-2),
|
||||
MakeIntegerAccessor (&A::m_int16),
|
||||
MakeIntegerChecker<int16_t> ())
|
||||
static TypeId GetTypeId() {
|
||||
static TypeId tid = TypeId("ns3::A")
|
||||
.SetParent<Object>()
|
||||
.AddAttribute("TestInt16", "help text",
|
||||
IntegerValue(-2),
|
||||
MakeIntegerAccessor(&A::m_int16),
|
||||
MakeIntegerChecker<int16_t>())
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
int16_t m_int16;
|
||||
};
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (ConfigExample);
|
||||
NS_OBJECT_ENSURE_REGISTERED(ConfigExample);
|
||||
|
||||
Next, we use the Config subsystem to override the defaults in a couple of
|
||||
ways::
|
||||
|
||||
Config::SetDefault ("ns3::ConfigExample::TestInt16", IntegerValue (-5));
|
||||
Config::SetDefault("ns3::ConfigExample::TestInt16", IntegerValue(-5));
|
||||
|
||||
Ptr<ConfigExample> a_obj = CreateObject<ConfigExample> ();
|
||||
NS_ABORT_MSG_UNLESS (a_obj->m_int16 == -5,
|
||||
"Cannot set ConfigExample's integer attribute via Config::SetDefault");
|
||||
Ptr<ConfigExample> a_obj = CreateObject<ConfigExample>();
|
||||
NS_ABORT_MSG_UNLESS(a_obj->m_int16 == -5,
|
||||
"Cannot set ConfigExample's integer attribute via Config::SetDefault");
|
||||
|
||||
Ptr<ConfigExample> a2_obj = CreateObject<ConfigExample> ();
|
||||
a2_obj->SetAttribute ("TestInt16", IntegerValue (-3));
|
||||
Ptr<ConfigExample> a2_obj = CreateObject<ConfigExample>();
|
||||
a2_obj->SetAttribute("TestInt16", IntegerValue(-3));
|
||||
IntegerValue iv;
|
||||
a2_obj->GetAttribute ("TestInt16", iv);
|
||||
NS_ABORT_MSG_UNLESS (iv.Get () == -3,
|
||||
"Cannot set ConfigExample's integer attribute via SetAttribute");
|
||||
a2_obj->GetAttribute("TestInt16", iv);
|
||||
NS_ABORT_MSG_UNLESS(iv.Get() == -3,
|
||||
"Cannot set ConfigExample's integer attribute via SetAttribute");
|
||||
|
||||
The next statement is necessary to make sure that (one of) the objects
|
||||
created is rooted in the configuration namespace as an object instance.
|
||||
@@ -1052,14 +1052,14 @@ or :cpp:class:`ns3::Channel` instance,
|
||||
but here, since we are working at the core level, we need to create a
|
||||
new root namespace object::
|
||||
|
||||
Config::RegisterRootNamespaceObject (a2_obj);
|
||||
Config::RegisterRootNamespaceObject(a2_obj);
|
||||
|
||||
Writing
|
||||
+++++++
|
||||
|
||||
Next, we want to output the configuration store. The examples show how
|
||||
to do it in two formats, XML and raw text. In practice, one should perform
|
||||
this step just before calling :cpp:func:`Simulator::Run ()` to save the
|
||||
this step just before calling :cpp:func:`Simulator::Run()` to save the
|
||||
final configuration just before running the simulation.
|
||||
|
||||
There are three Attributes that govern the behavior of the ConfigStore:
|
||||
@@ -1072,27 +1072,26 @@ the ConfigStore format is plain text or Xml (``"FileFormat=Xml"``)
|
||||
|
||||
The example shows::
|
||||
|
||||
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.xml"));
|
||||
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
|
||||
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
|
||||
Config::SetDefault("ns3::ConfigStore::Filename", StringValue("output-attributes.xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("Xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Save"));
|
||||
ConfigStore outputConfig;
|
||||
outputConfig.ConfigureDefaults ();
|
||||
outputConfig.ConfigureAttributes ();
|
||||
outputConfig.ConfigureDefaults();
|
||||
outputConfig.ConfigureAttributes();
|
||||
|
||||
// Output config store to txt format
|
||||
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.txt"));
|
||||
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("RawText"));
|
||||
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
|
||||
Config::SetDefault("ns3::ConfigStore::Filename", StringValue("output-attributes.txt"));
|
||||
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("RawText"));
|
||||
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Save"));
|
||||
ConfigStore outputConfig2;
|
||||
outputConfig2.ConfigureDefaults ();
|
||||
outputConfig2.ConfigureAttributes ();
|
||||
outputConfig2.ConfigureDefaults();
|
||||
outputConfig2.ConfigureAttributes();
|
||||
|
||||
Simulator::Run ();
|
||||
Simulator::Run();
|
||||
|
||||
Simulator::Destroy ();
|
||||
Simulator::Destroy();
|
||||
|
||||
Note the placement of these statements just prior to the
|
||||
:cpp:func:`Simulator::Run ()` statement. This output logs all of the
|
||||
values in place just prior to starting the simulation (*i.e*. after
|
||||
all of the configuration has taken place).
|
||||
|
||||
@@ -1184,11 +1183,11 @@ are registered before being used in object construction).
|
||||
|
||||
::
|
||||
|
||||
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("input-defaults.xml"));
|
||||
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Load"));
|
||||
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::Filename", StringValue("input-defaults.xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Load"));
|
||||
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("Xml"));
|
||||
ConfigStore inputConfig;
|
||||
inputConfig.ConfigureDefaults ();
|
||||
inputConfig.ConfigureDefaults();
|
||||
|
||||
Next, note that loading of input configuration data is limited to Attribute
|
||||
default (*i.e*. not instance) values, and global values. Attribute instance
|
||||
@@ -1219,31 +1218,31 @@ write out the resulting attributes to a separate file called
|
||||
|
||||
#include "ns3/config-store-module.h"
|
||||
...
|
||||
int main (...)
|
||||
int main(...)
|
||||
{
|
||||
|
||||
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("input-defaults.xml"));
|
||||
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Load"));
|
||||
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::Filename", StringValue("input-defaults.xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Load"));
|
||||
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("Xml"));
|
||||
ConfigStore inputConfig;
|
||||
inputConfig.ConfigureDefaults ();
|
||||
inputConfig.ConfigureDefaults();
|
||||
|
||||
//
|
||||
// Allow the user to override any of the defaults and the above Bind () at
|
||||
// Allow the user to override any of the defaults and the above Bind() at
|
||||
// run-time, viacommand-line arguments
|
||||
//
|
||||
CommandLine cmd;
|
||||
cmd.Parse (argc, argv);
|
||||
cmd.Parse(argc, argv);
|
||||
|
||||
// setup topology
|
||||
...
|
||||
|
||||
// Invoke just before entering Simulator::Run ()
|
||||
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.xml"));
|
||||
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
|
||||
// Invoke just before entering Simulator::Run()
|
||||
Config::SetDefault("ns3::ConfigStore::Filename", StringValue("output-attributes.xml"));
|
||||
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Save"));
|
||||
ConfigStore outputConfig;
|
||||
outputConfig.ConfigureAttributes ();
|
||||
Simulator::Run ();
|
||||
outputConfig.ConfigureAttributes();
|
||||
Simulator::Run();
|
||||
}
|
||||
|
||||
ConfigStore use cases (pre- and post-simulation)
|
||||
@@ -1262,13 +1261,13 @@ As a matter of fact, some Objects might be created when the simulation starts.
|
||||
Hence, ConfigStore will not "report" their attributes if invoked earlier in the code.
|
||||
|
||||
A typical workflow might involve running the simulation, calling ConfigStore
|
||||
at the end of the simulation (after ``Simulator::Run ()`` and before ``Simulator::Destroy ()``)
|
||||
at the end of the simulation (after ``Simulator::Run()`` and before ``Simulator::Destroy()``)
|
||||
This will show all the attributes in the Objects, both those with default values, and those
|
||||
with values changed during the simulation execution.
|
||||
|
||||
To change these values, you'll need to either change the default (class-wide) attribute values
|
||||
(in this case call ConfigStore before the Object creation), or specific object attribute
|
||||
(in this case call ConfigStore after the Object creation, typically just before ``Simulator::Run ()``.
|
||||
(in this case call ConfigStore after the Object creation, typically just before ``Simulator::Run()``.
|
||||
|
||||
|
||||
ConfigStore GUI
|
||||
@@ -1278,7 +1277,7 @@ There is a GTK-based front end for the ConfigStore. This allows users to use a
|
||||
GUI to access and change variables.
|
||||
|
||||
Some screenshots are presented here. They are the result of using GtkConfig on
|
||||
``src/lte/examples/lena-dual-stripe.cc`` after ``Simulator::Run ()``.
|
||||
``src/lte/examples/lena-dual-stripe.cc`` after ``Simulator::Run()``.
|
||||
|
||||
.. _GtkConfig:
|
||||
|
||||
@@ -1327,15 +1326,15 @@ is rerun.
|
||||
Usage is almost the same as the non-GTK-based version, but there
|
||||
are no :cpp:class:`ConfigStore` attributes involved::
|
||||
|
||||
// Invoke just before entering Simulator::Run ()
|
||||
// Invoke just before entering Simulator::Run()
|
||||
GtkConfigStore config;
|
||||
config.ConfigureDefaults ();
|
||||
config.ConfigureAttributes ();
|
||||
config.ConfigureDefaults();
|
||||
config.ConfigureAttributes();
|
||||
|
||||
Now, when you run the script, a GUI should pop up, allowing you to open menus of
|
||||
attributes on different nodes/objects, and then launch the simulation execution
|
||||
when you are done.
|
||||
|
||||
Note that "launch the simulation" means to proceed with the simulation script.
|
||||
If GtkConfigStore has been called after ``Simulator::Run ()`` the simulation will
|
||||
If GtkConfigStore has been called after ``Simulator::Run()`` the simulation will
|
||||
not be started again - it will just end.
|
||||
|
||||
@@ -19,15 +19,15 @@ so that they can invoke methods on each other::
|
||||
|
||||
class A {
|
||||
public:
|
||||
void ReceiveInput ( // parameters );
|
||||
void ReceiveInput( /* parameters */ );
|
||||
...
|
||||
}
|
||||
|
||||
(in another source file:)
|
||||
and in another source file::
|
||||
|
||||
class B {
|
||||
public:
|
||||
void DoSomething ();
|
||||
void DoSomething();
|
||||
...
|
||||
|
||||
private:
|
||||
@@ -38,7 +38,7 @@ so that they can invoke methods on each other::
|
||||
B::DoSomething()
|
||||
{
|
||||
// Tell a_instance that something happened
|
||||
a_instance->ReceiveInput ( // parameters);
|
||||
a_instance->ReceiveInput( /* parameters */ );
|
||||
...
|
||||
}
|
||||
|
||||
@@ -58,7 +58,9 @@ This is not an abstract problem for network simulation research, but rather it
|
||||
has been a source of problems in previous simulators, when researchers want to
|
||||
extend or modify the system to do different things (as they are apt to do in
|
||||
research). Consider, for example, a user who wants to add an IPsec security
|
||||
protocol sublayer between TCP and IP::
|
||||
protocol sublayer between TCP and IP:
|
||||
|
||||
.. code-block::text
|
||||
|
||||
------------ -----------
|
||||
| TCP | | TCP |
|
||||
@@ -104,7 +106,7 @@ What you get from this is a variable named simply ``pfi`` that is initialized to
|
||||
the value 0. If you want to initialize this pointer to something meaningful, you
|
||||
have to have a function with a matching signature. In this case::
|
||||
|
||||
int MyFunction (int arg) {}
|
||||
int MyFunction(int arg) {}
|
||||
|
||||
If you have this target, you can initialize the variable to point to your
|
||||
function like::
|
||||
@@ -114,14 +116,14 @@ function like::
|
||||
You can then call MyFunction indirectly using the more suggestive form of the
|
||||
call::
|
||||
|
||||
int result = (*pfi) (1234);
|
||||
int result = (*pfi)(1234);
|
||||
|
||||
This is suggestive since it looks like you are dereferencing the function
|
||||
pointer just like you would dereference any pointer. Typically, however, people
|
||||
take advantage of the fact that the compiler knows what is going on and will
|
||||
just use a shorter form::
|
||||
|
||||
int result = pfi (1234);
|
||||
int result = pfi(1234);
|
||||
|
||||
Notice that the function pointer obeys value semantics, so you can pass it
|
||||
around like any other value. Typically, when you use an asynchronous interface
|
||||
@@ -136,7 +138,7 @@ the pointer to function returning an int (PFI).
|
||||
The declaration of the variable providing the indirection looks only slightly
|
||||
different::
|
||||
|
||||
int (MyClass::*pmi) (int arg) = 0;
|
||||
int (MyClass::*pmi)(int arg) = 0;
|
||||
|
||||
This declares a variable named ``pmi`` just as the previous example declared a
|
||||
variable named ``pfi``. Since the will be to call a method of an instance of a
|
||||
@@ -144,7 +146,7 @@ particular class, one must declare that method in a class::
|
||||
|
||||
class MyClass {
|
||||
public:
|
||||
int MyMethod (int arg);
|
||||
int MyMethod(int arg);
|
||||
};
|
||||
|
||||
Given this class declaration, one would then initialize that variable like
|
||||
@@ -158,18 +160,18 @@ pointer. This, in turn, means there must be an object of MyClass to refer to. A
|
||||
simplistic example of this is just calling a method indirectly (think virtual
|
||||
function)::
|
||||
|
||||
int (MyClass::*pmi) (int arg) = 0; // Declare a PMI
|
||||
int (MyClass::*pmi)(int arg) = 0; // Declare a PMI
|
||||
pmi = &MyClass::MyMethod; // Point at the implementation code
|
||||
|
||||
MyClass myClass; // Need an instance of the class
|
||||
(myClass.*pmi) (1234); // Call the method with an object ptr
|
||||
(myClass.*pmi)(1234); // Call the method with an object ptr
|
||||
|
||||
Just like in the C example, you can use this in an asynchronous call to another
|
||||
module which will *call back* using a method and an object pointer. The
|
||||
straightforward extension one might consider is to pass a pointer to the object
|
||||
and the PMI variable. The module would just do::
|
||||
|
||||
(*objectPtr.*pmi) (1234);
|
||||
(*objectPtr.*pmi)(1234);
|
||||
|
||||
to execute the callback on the desired object.
|
||||
|
||||
@@ -186,12 +188,12 @@ It is basically just a packaged-up function call, possibly with some state.
|
||||
|
||||
A functor has two parts, a specific part and a generic part, related through
|
||||
inheritance. The calling code (the code that executes the callback) will execute
|
||||
a generic overloaded ``operator ()`` of a generic functor to cause the callback
|
||||
a generic overloaded ``operator()`` of a generic functor to cause the callback
|
||||
to be called. The called code (the code that wants to be called back) will have
|
||||
to provide a specialized implementation of the ``operator ()`` that performs the
|
||||
to provide a specialized implementation of the ``operator()`` that performs the
|
||||
class-specific work that caused the close-coupling problem above.
|
||||
|
||||
With the specific functor and its overloaded ``operator ()`` created, the called
|
||||
With the specific functor and its overloaded ``operator()`` created, the called
|
||||
code then gives the specialized code to the module that will execute the
|
||||
callback (the calling code).
|
||||
|
||||
@@ -210,7 +212,7 @@ of the functor::
|
||||
class Functor
|
||||
{
|
||||
public:
|
||||
virtual int operator() (T arg) = 0;
|
||||
virtual int operator()(T arg) = 0;
|
||||
};
|
||||
|
||||
The caller defines a specific part of the functor that really is just there to
|
||||
@@ -226,7 +228,7 @@ implement the specific ``operator()`` method::
|
||||
m_pmi = _pmi;
|
||||
}
|
||||
|
||||
virtual int operator() (ARG arg)
|
||||
virtual int operator()(ARG arg)
|
||||
{
|
||||
(*m_p.*m_pmi)(arg);
|
||||
}
|
||||
@@ -240,8 +242,8 @@ Here is an example of the usage::
|
||||
class A
|
||||
{
|
||||
public:
|
||||
A (int a0) : a (a0) {}
|
||||
int Hello (int b0)
|
||||
A(int a0) : a(a0) {}
|
||||
int Hello(int b0)
|
||||
{
|
||||
std::cout << "Hello from A, a = " << a << " b0 = " << b0 << std::endl;
|
||||
}
|
||||
@@ -269,19 +271,19 @@ with the object pointer using the C++ PMI syntax.
|
||||
To use this, one could then declare some model code that takes a generic functor
|
||||
as a parameter::
|
||||
|
||||
void LibraryFunction (Functor functor);
|
||||
void LibraryFunction(Functor functor);
|
||||
|
||||
The code that will talk to the model would build a specific functor and pass it to ``LibraryFunction``::
|
||||
|
||||
MyClass myClass;
|
||||
SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
|
||||
SpecificFunctor<MyClass, int> functor(&myclass, MyClass::MyMethod);
|
||||
|
||||
When ``LibraryFunction`` is done, it executes the callback using the
|
||||
``operator()`` on the generic functor it was passed, and in this particular
|
||||
case, provides the integer argument::
|
||||
|
||||
void
|
||||
LibraryFunction (Functor functor)
|
||||
LibraryFunction(Functor functor)
|
||||
{
|
||||
// Execute the library function
|
||||
functor(1234);
|
||||
@@ -319,7 +321,7 @@ Using the Callback API with static functions
|
||||
Consider a function::
|
||||
|
||||
static double
|
||||
CbOne (double a, double b)
|
||||
CbOne(double a, double b)
|
||||
{
|
||||
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::endl;
|
||||
return a;
|
||||
@@ -327,7 +329,7 @@ Consider a function::
|
||||
|
||||
Consider also the following main program snippet::
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// return type: double
|
||||
// first arg type: double
|
||||
@@ -365,7 +367,7 @@ Now, we need to tie together this callback instance and the actual target functi
|
||||
callback-- this is important. We can pass in any such properly-typed function
|
||||
to this callback. Let's look at this more closely::
|
||||
|
||||
static double CbOne (double a, double b) {}
|
||||
static double CbOne(double a, double b) {}
|
||||
^ ^ ^
|
||||
| | |
|
||||
| | |
|
||||
@@ -378,21 +380,21 @@ arguments are the types of the arguments of the function signature.
|
||||
Now, let's bind our callback "one" to the function that matches its signature::
|
||||
|
||||
// build callback instance which points to cbOne function
|
||||
one = MakeCallback (&CbOne);
|
||||
one = MakeCallback(&CbOne);
|
||||
|
||||
This call to ``MakeCallback`` is, in essence, creating one of the specialized
|
||||
functors mentioned above. The variable declared using the ``Callback``
|
||||
template function is going to be playing the part of the generic functor. The
|
||||
assignment ``one = MakeCallback (&CbOne)`` is the cast that converts the
|
||||
assignment ``one = MakeCallback(&CbOne)`` is the cast that converts the
|
||||
specialized functor known to the callee to a generic functor known to the caller.
|
||||
|
||||
Then, later in the program, if the callback is needed, it can be used as follows::
|
||||
|
||||
NS_ASSERT (!one.IsNull ());
|
||||
NS_ASSERT(!one.IsNull());
|
||||
|
||||
// invoke cbOne function through callback instance
|
||||
double retOne;
|
||||
retOne = one (10.0, 20.0);
|
||||
retOne = one(10.0, 20.0);
|
||||
|
||||
The check for ``IsNull()`` ensures that the callback is not null -- that there
|
||||
is a function to call behind this callback. Then, ``one()`` executes the
|
||||
@@ -410,13 +412,13 @@ invoked. Consider this example, also from main-callback.cc::
|
||||
|
||||
class MyCb {
|
||||
public:
|
||||
int CbTwo (double a) {
|
||||
int CbTwo(double a) {
|
||||
std::cout << "invoke cbTwo a=" << a << std::endl;
|
||||
return -5;
|
||||
}
|
||||
};
|
||||
|
||||
int main ()
|
||||
int main()
|
||||
{
|
||||
...
|
||||
// return type: int
|
||||
@@ -424,7 +426,7 @@ invoked. Consider this example, also from main-callback.cc::
|
||||
Callback<int, double> two;
|
||||
MyCb cb;
|
||||
// build callback instance which points to MyCb::cbTwo
|
||||
two = MakeCallback (&MyCb::CbTwo, &cb);
|
||||
two = MakeCallback(&MyCb::CbTwo, &cb);
|
||||
...
|
||||
}
|
||||
|
||||
@@ -432,7 +434,7 @@ Here, we pass an additional object pointer to the ``MakeCallback<>`` function.
|
||||
Recall from the background section above that ``Operator()`` will use the pointer to
|
||||
member syntax when it executes on an object::
|
||||
|
||||
virtual int operator() (ARG arg)
|
||||
virtual int operator()(ARG arg)
|
||||
{
|
||||
(*m_p.*m_pmi)(arg);
|
||||
}
|
||||
@@ -440,11 +442,11 @@ member syntax when it executes on an object::
|
||||
And so we needed to provide the two variables (``m_p`` and ``m_pmi``) when
|
||||
we made the specific functor. The line::
|
||||
|
||||
two = MakeCallback (&MyCb::CbTwo, &cb);
|
||||
two = MakeCallback(&MyCb::CbTwo, &cb);
|
||||
|
||||
does precisely that. In this case, when ``two ()`` is invoked::
|
||||
does precisely that. In this case, when ``two()`` is invoked::
|
||||
|
||||
int result = two (1.0);
|
||||
int result = two(1.0);
|
||||
|
||||
will result in a call to the ``CbTwo`` member function (method) on the object
|
||||
pointed to by ``&cb``.
|
||||
@@ -457,8 +459,8 @@ check before using them. There is a special construct for a null
|
||||
callback, which is preferable to simply passing "0" as an argument;
|
||||
it is the ``MakeNullCallback<>`` construct::
|
||||
|
||||
two = MakeNullCallback<int, double> ();
|
||||
NS_ASSERT (two.IsNull ());
|
||||
two = MakeNullCallback<int, double>();
|
||||
NS_ASSERT(two.IsNull());
|
||||
|
||||
Invoking a null callback is just like invoking a null function pointer: it will
|
||||
crash at runtime.
|
||||
@@ -484,14 +486,14 @@ function that needs to be called whenever a packet is received. This function
|
||||
calls an object that actually writes the packet to disk in the pcap file
|
||||
format. The signature of one of these functions will be::
|
||||
|
||||
static void DefaultSink (Ptr<PcapFileWrapper> file, Ptr<const Packet> p);
|
||||
static void DefaultSink(Ptr<PcapFileWrapper> file, Ptr<const Packet> p);
|
||||
|
||||
The static keyword means this is a static function which does not need a
|
||||
``this`` pointer, so it will be using C-style callbacks. We don't want the
|
||||
calling code to have to know about anything but the Packet. What we want in
|
||||
the calling code is just a call that looks like::
|
||||
|
||||
m_promiscSnifferTrace (m_currentPkt);
|
||||
m_promiscSnifferTrace(m_currentPkt);
|
||||
|
||||
What we want to do is to *bind* the ``Ptr<PcapFileWriter> file`` to the
|
||||
specific callback implementation when it is created and arrange for the
|
||||
@@ -501,7 +503,7 @@ We provide the ``MakeBoundCallback`` template function for that purpose. It
|
||||
takes the same parameters as the ``MakeCallback`` template function but also
|
||||
takes the parameters to be bound. In the case of the example above::
|
||||
|
||||
MakeBoundCallback (&DefaultSink, file);
|
||||
MakeBoundCallback(&DefaultSink, file);
|
||||
|
||||
will create a specific callback implementation that knows to add in the extra
|
||||
bound arguments. Conceptually, it extends the specific functor described above
|
||||
@@ -518,7 +520,7 @@ with one or more bound arguments::
|
||||
m_boundArg = boundArg;
|
||||
}
|
||||
|
||||
virtual int operator() (ARG arg)
|
||||
virtual int operator()(ARG arg)
|
||||
{
|
||||
(*m_p.*m_pmi)(m_boundArg, arg);
|
||||
}
|
||||
@@ -532,7 +534,7 @@ You can see that when the specific functor is created, the bound argument is sav
|
||||
in the functor / callback object itself. When the ``operator()`` is invoked with
|
||||
the single parameter, as in::
|
||||
|
||||
m_promiscSnifferTrace (m_currentPkt);
|
||||
m_promiscSnifferTrace(m_currentPkt);
|
||||
|
||||
the implementation of ``operator()`` adds the bound parameter into the actual
|
||||
function call::
|
||||
@@ -542,20 +544,20 @@ function call::
|
||||
It's possible to bind two or three arguments as well. Say we have a function with
|
||||
signature::
|
||||
|
||||
static void NotifyEvent (Ptr<A> a, Ptr<B> b, MyEventType e);
|
||||
static void NotifyEvent(Ptr<A> a, Ptr<B> b, MyEventType e);
|
||||
|
||||
One can create bound callback binding first two arguments like::
|
||||
|
||||
MakeBoundCallback (&NotifyEvent, a1, b1);
|
||||
MakeBoundCallback(&NotifyEvent, a1, b1);
|
||||
|
||||
assuming `a1` and `b1` are objects of type `A` and `B` respectively. Similarly for
|
||||
three arguments one would have function with a signature::
|
||||
|
||||
static void NotifyEvent (Ptr<A> a, Ptr<B> b, MyEventType e);
|
||||
static void NotifyEvent(Ptr<A> a, Ptr<B> b, MyEventType e);
|
||||
|
||||
Binding three arguments in done with::
|
||||
|
||||
MakeBoundCallback (&NotifyEvent, a1, b1, c1);
|
||||
MakeBoundCallback(&NotifyEvent, a1, b1, c1);
|
||||
|
||||
again assuming `a1`, `b1` and `c1` are objects of type `A`, `B` and `C` respectively.
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ the basics here, instead focusing on preferred usage for |ns3|.
|
||||
| The ``Frobnitz`` is accessed by:: | The ``Frobnitz`` is accessed by:: |
|
||||
| | |
|
||||
| Foo::Frobnitz frob; | Foo::Frobnitz frob; |
|
||||
| frob.Set (...); | frob.Set (...); |
|
||||
| frob.Set(...); | frob.Set(...); |
|
||||
+--------------------------------------+------------------------------------+
|
||||
|
||||
To use a specific syntax highlighter, for example, ``bash`` shell commands:
|
||||
@@ -315,7 +315,7 @@ The preferred style for Doxygen comments is the JavaDoc style::
|
||||
* Understanding this material shouldn't be necessary to using
|
||||
* the class or method.
|
||||
*/
|
||||
void ExampleFunction (const int foo, double & bar, const bool baz);
|
||||
void ExampleFunction(const int foo, double & bar, const bool baz);
|
||||
|
||||
In this style the Doxygen comment block begins with two \`*' characters:
|
||||
``/**``, and precedes the item being documented.
|
||||
@@ -324,7 +324,7 @@ For items needing only a brief description, either of these short forms
|
||||
is appropriate::
|
||||
|
||||
/** Destructor implementation. */
|
||||
void DoDispose ();
|
||||
void DoDispose();
|
||||
|
||||
int m_count; //!< Count of ...
|
||||
|
||||
@@ -355,8 +355,8 @@ Useful Features
|
||||
#. In the sub class mark inherited functions with an ordinary comment::
|
||||
|
||||
// Inherited methods
|
||||
virtual void FooBar ();
|
||||
virtual int BarFoo (double baz);
|
||||
virtual void FooBar();
|
||||
virtual int BarFoo(double baz);
|
||||
|
||||
This doesn't work for static functions; see ``GetTypeId``, below, for an
|
||||
example.
|
||||
@@ -596,7 +596,7 @@ usage for |ns3|.
|
||||
* \tparam U \deduced The argument type.
|
||||
* \param [in] a The argument.
|
||||
*/
|
||||
template <typename T, typename U> T Function (U a);
|
||||
template <typename T, typename U> T Function(U a);
|
||||
|
||||
* Use ``\tparam U \deduced`` because the type ``U`` can be deduced at
|
||||
the site where the template is invoked. Basically deduction can only
|
||||
@@ -604,7 +604,7 @@ usage for |ns3|.
|
||||
|
||||
* Use ``\tparam T \explicit`` because the type ``T`` can't be deduced;
|
||||
it must be given explicitly at the invocation site, as in
|
||||
``Create<MyObject> (...)``
|
||||
``Create<MyObject>(...)``
|
||||
|
||||
* ``\internal`` should be used only to set off a discussion of implementation
|
||||
details, not to mark ``private`` functions (they are already marked,
|
||||
@@ -621,16 +621,16 @@ cases is:
|
||||
|
||||
* Default constructor/destructor::
|
||||
|
||||
MyClass (); //!< Default constructor
|
||||
~MyClass (); //!< Destructor
|
||||
MyClass(); //!< Default constructor
|
||||
~MyClass(); //!< Destructor
|
||||
|
||||
* Dummy destructor and DoDispose::
|
||||
|
||||
/** Dummy destructor, see DoDispose. */
|
||||
~MyClass ();
|
||||
~MyClass();
|
||||
|
||||
/** Destructor implementation */
|
||||
virtual void DoDispose ();
|
||||
virtual void DoDispose();
|
||||
|
||||
* GetTypeId::
|
||||
|
||||
@@ -638,4 +638,4 @@ cases is:
|
||||
* Register this type.
|
||||
* \return The object TypeId.
|
||||
*/
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
|
||||
@@ -62,7 +62,7 @@ might write this:
|
||||
|
||||
::
|
||||
|
||||
void handler (int arg0, int arg1)
|
||||
void handler(int arg0, int arg1)
|
||||
{
|
||||
std::cout << "handler called with argument arg0=" << arg0 << " and
|
||||
arg1=" << arg1 << std::endl;
|
||||
@@ -115,13 +115,13 @@ What does this mean?
|
||||
|
||||
::
|
||||
|
||||
Simulator::Schedule (Time const &time, MEM mem_ptr, OBJ obj);
|
||||
Simulator::Schedule(Time const &time, MEM mem_ptr, OBJ obj);
|
||||
|
||||
vs.
|
||||
|
||||
::
|
||||
|
||||
Simulator::ScheduleWithContext (uint32_t context, Time const &time, MEM mem_ptr, OBJ obj);
|
||||
Simulator::ScheduleWithContext(uint32_t context, Time const &time, MEM mem_ptr, OBJ obj);
|
||||
|
||||
Readers who invest time and effort in developing or using a non-trivial
|
||||
simulation model will know the value of the |ns3| logging framework to
|
||||
@@ -212,8 +212,8 @@ event execution. These are derived from the abstract base class `SimulatorImpl`
|
||||
You can choose which simulator engine to use by setting a global variable,
|
||||
for example::
|
||||
|
||||
GlobalValue::Bind ("SimulatorImplementationType",
|
||||
StringValue ("ns3::DistributedSimulatorImpl"));
|
||||
GlobalValue::Bind("SimulatorImplementationType",
|
||||
StringValue("ns3::DistributedSimulatorImpl"));
|
||||
|
||||
or by using a command line argument::
|
||||
|
||||
@@ -306,8 +306,8 @@ The main job of the `Scheduler` classes is to maintain the priority queue of
|
||||
future events. The scheduler can be set with a global variable,
|
||||
similar to choosing the `SimulatorImpl`::
|
||||
|
||||
GlobalValue::Bind ("SchedulerType",
|
||||
StringValue ("ns3::DistributedSimulatorImpl"));
|
||||
GlobalValue::Bind("SchedulerType",
|
||||
StringValue("ns3::DistributedSimulatorImpl"));
|
||||
|
||||
The scheduler can be changed at any time via `Simulator::SetScheduler()`.
|
||||
The default scheduler is `MapScheduler` which uses a `std::map<>` to
|
||||
@@ -343,6 +343,3 @@ complexity of the other API calls.
|
||||
+-----------------------+-------------------------------------+-------------+--------------+----------+--------------+
|
||||
| PriorityQueueSchduler | `std::priority_queue<,std::vector>` | Logarithimc | Logarithims | 24 bytes | 0 |
|
||||
+-----------------------+-------------------------------------+-------------+--------------+----------+--------------+
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -88,24 +88,24 @@ was created using the following code from gnuplot-example.cc: ::
|
||||
std::string dataTitle = "2-D Data";
|
||||
|
||||
// Instantiate the plot and set its title.
|
||||
Gnuplot plot (graphicsFileName);
|
||||
plot.SetTitle (plotTitle);
|
||||
Gnuplot plot(graphicsFileName);
|
||||
plot.SetTitle(plotTitle);
|
||||
|
||||
// Make the graphics file, which the plot file will create when it
|
||||
// is used with Gnuplot, be a PNG file.
|
||||
plot.SetTerminal ("png");
|
||||
plot.SetTerminal("png");
|
||||
|
||||
// Set the labels for each axis.
|
||||
plot.SetLegend ("X Values", "Y Values");
|
||||
plot.SetLegend("X Values", "Y Values");
|
||||
|
||||
// Set the range for the x axis.
|
||||
plot.AppendExtra ("set xrange [-6:+6]");
|
||||
plot.AppendExtra("set xrange [-6:+6]");
|
||||
|
||||
// Instantiate the dataset, set its title, and make the points be
|
||||
// plotted along with connecting lines.
|
||||
Gnuplot2dDataset dataset;
|
||||
dataset.SetTitle (dataTitle);
|
||||
dataset.SetStyle (Gnuplot2dDataset::LINES_POINTS);
|
||||
dataset.SetTitle(dataTitle);
|
||||
dataset.SetStyle(Gnuplot2dDataset::LINES_POINTS);
|
||||
|
||||
double x;
|
||||
double y;
|
||||
@@ -121,20 +121,20 @@ was created using the following code from gnuplot-example.cc: ::
|
||||
y = x * x;
|
||||
|
||||
// Add this point.
|
||||
dataset.Add (x, y);
|
||||
dataset.Add(x, y);
|
||||
}
|
||||
|
||||
// Add the dataset to the plot.
|
||||
plot.AddDataset (dataset);
|
||||
plot.AddDataset(dataset);
|
||||
|
||||
// Open the plot file.
|
||||
std::ofstream plotFile (plotFileName.c_str());
|
||||
std::ofstream plotFile(plotFileName.c_str());
|
||||
|
||||
// Write the plot file.
|
||||
plot.GenerateOutput (plotFile);
|
||||
plot.GenerateOutput(plotFile);
|
||||
|
||||
// Close the plot file.
|
||||
plotFile.close ();
|
||||
plotFile.close();
|
||||
|
||||
An Example 2-Dimensional Plot with Error Bars
|
||||
*********************************************
|
||||
@@ -154,27 +154,27 @@ was created using the following code from gnuplot-example.cc: ::
|
||||
std::string dataTitle = "2-D Data With Error Bars";
|
||||
|
||||
// Instantiate the plot and set its title.
|
||||
Gnuplot plot (graphicsFileName);
|
||||
plot.SetTitle (plotTitle);
|
||||
Gnuplot plot(graphicsFileName);
|
||||
plot.SetTitle(plotTitle);
|
||||
|
||||
// Make the graphics file, which the plot file will create when it
|
||||
// is used with Gnuplot, be a PNG file.
|
||||
plot.SetTerminal ("png");
|
||||
plot.SetTerminal("png");
|
||||
|
||||
// Set the labels for each axis.
|
||||
plot.SetLegend ("X Values", "Y Values");
|
||||
plot.SetLegend("X Values", "Y Values");
|
||||
|
||||
// Set the range for the x axis.
|
||||
plot.AppendExtra ("set xrange [-6:+6]");
|
||||
plot.AppendExtra("set xrange [-6:+6]");
|
||||
|
||||
// Instantiate the dataset, set its title, and make the points be
|
||||
// plotted with no connecting lines.
|
||||
Gnuplot2dDataset dataset;
|
||||
dataset.SetTitle (dataTitle);
|
||||
dataset.SetStyle (Gnuplot2dDataset::POINTS);
|
||||
dataset.SetTitle(dataTitle);
|
||||
dataset.SetStyle(Gnuplot2dDataset::POINTS);
|
||||
|
||||
// Make the dataset have error bars in both the x and y directions.
|
||||
dataset.SetErrorBars (Gnuplot2dDataset::XY);
|
||||
dataset.SetErrorBars(Gnuplot2dDataset::XY);
|
||||
|
||||
double x;
|
||||
double xErrorDelta;
|
||||
@@ -199,20 +199,20 @@ was created using the following code from gnuplot-example.cc: ::
|
||||
|
||||
// Add this point with uncertainties in both the x and y
|
||||
// direction.
|
||||
dataset.Add (x, y, xErrorDelta, yErrorDelta);
|
||||
dataset.Add(x, y, xErrorDelta, yErrorDelta);
|
||||
}
|
||||
|
||||
// Add the dataset to the plot.
|
||||
plot.AddDataset (dataset);
|
||||
plot.AddDataset(dataset);
|
||||
|
||||
// Open the plot file.
|
||||
std::ofstream plotFile (plotFileName.c_str());
|
||||
std::ofstream plotFile(plotFileName.c_str());
|
||||
|
||||
// Write the plot file.
|
||||
plot.GenerateOutput (plotFile);
|
||||
plot.GenerateOutput(plotFile);
|
||||
|
||||
// Close the plot file.
|
||||
plotFile.close ();
|
||||
plotFile.close();
|
||||
|
||||
An Example 3-Dimensional Plot
|
||||
*****************************
|
||||
@@ -232,34 +232,34 @@ was created using the following code from gnuplot-example.cc: ::
|
||||
std::string dataTitle = "3-D Data";
|
||||
|
||||
// Instantiate the plot and set its title.
|
||||
Gnuplot plot (graphicsFileName);
|
||||
plot.SetTitle (plotTitle);
|
||||
Gnuplot plot(graphicsFileName);
|
||||
plot.SetTitle(plotTitle);
|
||||
|
||||
// Make the graphics file, which the plot file will create when it
|
||||
// is used with Gnuplot, be a PNG file.
|
||||
plot.SetTerminal ("png");
|
||||
plot.SetTerminal("png");
|
||||
|
||||
// Rotate the plot 30 degrees around the x axis and then rotate the
|
||||
// plot 120 degrees around the new z axis.
|
||||
plot.AppendExtra ("set view 30, 120, 1.0, 1.0");
|
||||
plot.AppendExtra("set view 30, 120, 1.0, 1.0");
|
||||
|
||||
// Make the zero for the z-axis be in the x-axis and y-axis plane.
|
||||
plot.AppendExtra ("set ticslevel 0");
|
||||
plot.AppendExtra("set ticslevel 0");
|
||||
|
||||
// Set the labels for each axis.
|
||||
plot.AppendExtra ("set xlabel 'X Values'");
|
||||
plot.AppendExtra ("set ylabel 'Y Values'");
|
||||
plot.AppendExtra ("set zlabel 'Z Values'");
|
||||
plot.AppendExtra("set xlabel 'X Values'");
|
||||
plot.AppendExtra("set ylabel 'Y Values'");
|
||||
plot.AppendExtra("set zlabel 'Z Values'");
|
||||
|
||||
// Set the ranges for the x and y axis.
|
||||
plot.AppendExtra ("set xrange [-5:+5]");
|
||||
plot.AppendExtra ("set yrange [-5:+5]");
|
||||
plot.AppendExtra("set xrange [-5:+5]");
|
||||
plot.AppendExtra("set yrange [-5:+5]");
|
||||
|
||||
// Instantiate the dataset, set its title, and make the points be
|
||||
// connected by lines.
|
||||
Gnuplot3dDataset dataset;
|
||||
dataset.SetTitle (dataTitle);
|
||||
dataset.SetStyle ("with lines");
|
||||
dataset.SetTitle(dataTitle);
|
||||
dataset.SetStyle("with lines");
|
||||
|
||||
double x;
|
||||
double y;
|
||||
@@ -278,22 +278,22 @@ was created using the following code from gnuplot-example.cc: ::
|
||||
z = x * x * y * y;
|
||||
|
||||
// Add this point.
|
||||
dataset.Add (x, y, z);
|
||||
dataset.Add(x, y, z);
|
||||
}
|
||||
|
||||
// The blank line is necessary at the end of each x value's data
|
||||
// points for the 3-D surface grid to work.
|
||||
dataset.AddEmptyLine ();
|
||||
dataset.AddEmptyLine();
|
||||
}
|
||||
|
||||
// Add the dataset to the plot.
|
||||
plot.AddDataset (dataset);
|
||||
plot.AddDataset(dataset);
|
||||
|
||||
// Open the plot file.
|
||||
std::ofstream plotFile (plotFileName.c_str());
|
||||
std::ofstream plotFile(plotFileName.c_str());
|
||||
|
||||
// Write the plot file.
|
||||
plot.GenerateOutput (plotFile);
|
||||
plot.GenerateOutput(plotFile);
|
||||
|
||||
// Close the plot file.
|
||||
plotFile.close ();
|
||||
plotFile.close();
|
||||
|
||||
@@ -99,7 +99,7 @@ To add the hash function ``foo``, follow the ``hash-murmur3.h``/``.cc`` pattern:
|
||||
* ``include`` the declaration in ``hash.h`` (at the point where
|
||||
``hash-murmur3.h`` is included.
|
||||
* In your own code, instantiate a ``Hasher`` object via the constructor
|
||||
``Hasher (Ptr<Hash::Function::Foo> ())``
|
||||
``Hasher(Ptr<Hash::Function::Foo>())``
|
||||
|
||||
|
||||
If your hash function is a single function, e.g. ``hashf``, you don't
|
||||
|
||||
@@ -60,8 +60,8 @@ is called "router" such as here:
|
||||
|
||||
::
|
||||
|
||||
RouterTestSuite::RouterTestSuite ()
|
||||
: TestSuite ("router", UNIT)
|
||||
RouterTestSuite::RouterTestSuite()
|
||||
: TestSuite("router", UNIT)
|
||||
|
||||
Try this command:
|
||||
|
||||
@@ -154,8 +154,8 @@ which looks like this:
|
||||
::
|
||||
|
||||
#include "ns3/example-as-test.h"
|
||||
static ns3::ExampleAsTestSuite g_modExampleOne ("mymodule-example-mod-example-one", "mod-example", NS_TEST_SOURCEDIR, "--arg-one");
|
||||
static ns3::ExampleAsTestSuite g_modExampleTwo ("mymodule-example-mod-example-two", "mod-example", NS_TEST_SOURCEDIR, "--arg-two");
|
||||
static ns3::ExampleAsTestSuite g_modExampleOne("mymodule-example-mod-example-one", "mod-example", NS_TEST_SOURCEDIR, "--arg-one");
|
||||
static ns3::ExampleAsTestSuite g_modExampleTwo("mymodule-example-mod-example-two", "mod-example", NS_TEST_SOURCEDIR, "--arg-two");
|
||||
|
||||
The arguments to the constructor are the name of the test suite, the
|
||||
example to run, the directory that contains the "good" reference file
|
||||
@@ -240,11 +240,11 @@ the wifi Information Elements.
|
||||
...
|
||||
};
|
||||
void
|
||||
BasicMultiLinkElementTest::DoRun ()
|
||||
BasicMultiLinkElementTest::DoRun()
|
||||
{
|
||||
MultiLinkElement mle (WIFI_MAC_MGT_BEACON);
|
||||
MultiLinkElement mle(WIFI_MAC_MGT_BEACON);
|
||||
// Fill in the Multi-Link Element
|
||||
TestHeaderSerialization (mle, WIFI_MAC_MGT_BEACON);
|
||||
TestHeaderSerialization(mle, WIFI_MAC_MGT_BEACON);
|
||||
}
|
||||
|
||||
Examples of this approach are found, e.g., in ``src/wifi/test/wifi-eht-info-elems-test.cc``
|
||||
|
||||
@@ -31,9 +31,9 @@ use of a particular function.
|
||||
|
||||
For example, this code snippet is from ``Ipv4L3Protocol::IsDestinationAddress()``::
|
||||
|
||||
if (address == iaddr.GetBroadcast ())
|
||||
if (address == iaddr.GetBroadcast())
|
||||
{
|
||||
NS_LOG_LOGIC ("For me (interface broadcast address)");
|
||||
NS_LOG_LOGIC("For me (interface broadcast address)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,10 +66,10 @@ The second way to enable logging is to use explicit statements in your
|
||||
program, such as in the ``first`` tutorial program::
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
|
||||
LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
|
||||
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
|
||||
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
|
||||
...
|
||||
|
||||
(The meaning of ``LOG_LEVEL_INFO``, and other possible values,
|
||||
@@ -107,7 +107,7 @@ in a module, spanning different compilation units, but logically grouped
|
||||
together, such as the |ns3| wifi code::
|
||||
|
||||
WifiHelper wifiHelper;
|
||||
wifiHelper.EnableLogComponents ();
|
||||
wifiHelper.EnableLogComponents();
|
||||
|
||||
The ``NS_LOG`` log component wildcard \`*' will enable all components.
|
||||
|
||||
@@ -319,7 +319,7 @@ How to add logging to your code
|
||||
|
||||
Adding logging to your code is very simple:
|
||||
|
||||
1. Invoke the ``NS_LOG_COMPONENT_DEFINE (...);`` macro
|
||||
1. Invoke the ``NS_LOG_COMPONENT_DEFINE(...);`` macro
|
||||
inside of ``namespace ns3``.
|
||||
|
||||
Create a unique string identifier (usually based on the name of the file
|
||||
@@ -330,7 +330,7 @@ Adding logging to your code is very simple:
|
||||
|
||||
namespace ns3 {
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("Ipv4L3Protocol");
|
||||
NS_LOG_COMPONENT_DEFINE("Ipv4L3Protocol");
|
||||
...
|
||||
|
||||
This registers ``Ipv4L3Protocol`` as a log component.
|
||||
@@ -361,15 +361,15 @@ In case you want to add logging statements to the methods of your template class
|
||||
|
||||
This requires you to perform these steps for all the subclasses of your class.
|
||||
|
||||
2. Invoke the ``NS_LOG_TEMPLATE_DEFINE (...);`` macro in the constructor of
|
||||
2. Invoke the ``NS_LOG_TEMPLATE_DEFINE(...);`` macro in the constructor of
|
||||
your class by providing the name of a log component registered by calling
|
||||
the ``NS_LOG_COMPONENT_DEFINE (...);`` macro in some module. For instance:
|
||||
the ``NS_LOG_COMPONENT_DEFINE(...);`` macro in some module. For instance:
|
||||
|
||||
::
|
||||
|
||||
template <typename Item>
|
||||
Queue<Item>::Queue ()
|
||||
: NS_LOG_TEMPLATE_DEFINE ("Queue")
|
||||
Queue<Item>::Queue()
|
||||
: NS_LOG_TEMPLATE_DEFINE("Queue")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -380,18 +380,18 @@ In case you want to add logging statements to a static member template
|
||||
|
||||
1. Invoke the ``NS_LOG_STATIC_TEMPLATE_DEFINE (...);`` macro in your static
|
||||
method by providing the name of a log component registered by calling
|
||||
the ``NS_LOG_COMPONENT_DEFINE (...);`` macro in some module. For instance:
|
||||
the ``NS_LOG_COMPONENT_DEFINE(...);`` macro in some module. For instance:
|
||||
|
||||
::
|
||||
|
||||
template <typename Item>
|
||||
void
|
||||
NetDeviceQueue::PacketEnqueued (Ptr<Queue<Item> > queue,
|
||||
Ptr<NetDeviceQueueInterface> ndqi,
|
||||
uint8_t txq, Ptr<const Item> item)
|
||||
NetDeviceQueue::PacketEnqueued(Ptr<Queue<Item> > queue,
|
||||
Ptr<NetDeviceQueueInterface> ndqi,
|
||||
uint8_t txq, Ptr<const Item> item)
|
||||
{
|
||||
|
||||
NS_LOG_STATIC_TEMPLATE_DEFINE ("NetDeviceQueueInterface");
|
||||
NS_LOG_STATIC_TEMPLATE_DEFINE("NetDeviceQueueInterface");
|
||||
...
|
||||
|
||||
2. Add logging statements (macro calls) to your static method.
|
||||
@@ -433,24 +433,24 @@ Logging Macros
|
||||
Severity Class Macro
|
||||
================ ==========================
|
||||
``LOG_NONE`` (none needed)
|
||||
``LOG_ERROR`` ``NS_LOG_ERROR (...);``
|
||||
``LOG_WARN`` ``NS_LOG_WARN (...);``
|
||||
``LOG_DEBUG`` ``NS_LOG_DEBUG (...);``
|
||||
``LOG_INFO`` ``NS_LOG_INFO (...);``
|
||||
``LOG_FUNCTION`` ``NS_LOG_FUNCTION (...);``
|
||||
``LOG_LOGIC`` ``NS_LOG_LOGIC (...);``
|
||||
``LOG_ERROR`` ``NS_LOG_ERROR(...);``
|
||||
``LOG_WARN`` ``NS_LOG_WARN(...);``
|
||||
``LOG_DEBUG`` ``NS_LOG_DEBUG(...);``
|
||||
``LOG_INFO`` ``NS_LOG_INFO(...);``
|
||||
``LOG_FUNCTION`` ``NS_LOG_FUNCTION(...);``
|
||||
``LOG_LOGIC`` ``NS_LOG_LOGIC(...);``
|
||||
================ ==========================
|
||||
|
||||
The macros function as output streamers, so anything you can send to
|
||||
``std::cout``, joined by ``<<`` operators, is allowed::
|
||||
|
||||
void MyClass::Check (int value, char * item)
|
||||
void MyClass::Check(int value, char * item)
|
||||
{
|
||||
NS_LOG_FUNCTION (this << arg << item);
|
||||
NS_LOG_FUNCTION(this << arg << item);
|
||||
if (arg > 10)
|
||||
{
|
||||
NS_LOG_ERROR ("encountered bad value " << value <<
|
||||
" while checking " << name << "!");
|
||||
NS_LOG_ERROR("encountered bad value " << value <<
|
||||
" while checking " << name << "!");
|
||||
}
|
||||
...
|
||||
}
|
||||
@@ -463,7 +463,7 @@ Logging Macros
|
||||
Unconditional Logging
|
||||
=====================
|
||||
|
||||
As a convenience, the ``NS_LOG_UNCOND (...);`` macro will always log its
|
||||
As a convenience, the ``NS_LOG_UNCOND(...);`` macro will always log its
|
||||
arguments, even if the associated log-component is not enabled at any
|
||||
severity. This macro does not use any of the prefix options. Note that
|
||||
logging is only enabled in debug builds; this macro won't produce
|
||||
@@ -473,19 +473,19 @@ output in optimized builds.
|
||||
Guidelines
|
||||
==========
|
||||
|
||||
* Start every class method with ``NS_LOG_FUNCTION (this << args...);``
|
||||
* Start every class method with ``NS_LOG_FUNCTION(this << args...);``
|
||||
This enables easy function call tracing.
|
||||
|
||||
* Except: don't log operators or explicit copy constructors,
|
||||
since these will cause infinite recursion and stack overflow.
|
||||
|
||||
* For methods without arguments use the same form:
|
||||
``NS_LOG_FUNCTION (this);``
|
||||
``NS_LOG_FUNCTION(this);``
|
||||
|
||||
* For static functions:
|
||||
|
||||
* With arguments use ``NS_LOG_FUNCTION (...);`` as normal.
|
||||
* Without arguments use ``NS_LOG_FUNCTION_NOARGS ();``
|
||||
* With arguments use ``NS_LOG_FUNCTION(...);`` as normal.
|
||||
* Without arguments use ``NS_LOG_FUNCTION_NOARGS();``
|
||||
|
||||
* Use ``NS_LOG_ERROR`` for serious error conditions that probably
|
||||
invalidate the simulation execution.
|
||||
@@ -507,10 +507,7 @@ Guidelines
|
||||
``NS_LOG="***"``).
|
||||
|
||||
* Use an explicit cast for any variable of type uint8_t or int8_t,
|
||||
e.g., ``NS_LOG_LOGIC ("Variable i is " << static_cast<int> (i));``.
|
||||
e.g., ``NS_LOG_LOGIC("Variable i is " << static_cast<int>(i));``.
|
||||
Without the cast, the integer is interpreted as a char, and the result
|
||||
will be most likely not in line with the expectations.
|
||||
This is a well documented C++ 'feature'.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ So far, in our design, we have::
|
||||
* \returns true if the Packet is to be considered as errored/corrupted
|
||||
* \param pkt Packet to apply error model to
|
||||
*/
|
||||
bool IsCorrupt (Ptr<Packet> pkt);
|
||||
bool IsCorrupt(Ptr<Packet> pkt);
|
||||
};
|
||||
|
||||
Note that we do not pass a const pointer, thereby allowing the function to
|
||||
@@ -109,16 +109,16 @@ a base class and first subclass that could be posted for initial review::
|
||||
class ErrorModel
|
||||
{
|
||||
public:
|
||||
ErrorModel ();
|
||||
virtual ~ErrorModel ();
|
||||
bool IsCorrupt (Ptr<Packet> pkt);
|
||||
void Reset ();
|
||||
void Enable ();
|
||||
void Disable ();
|
||||
bool IsEnabled () const;
|
||||
ErrorModel();
|
||||
virtual ~ErrorModel();
|
||||
bool IsCorrupt(Ptr<Packet> pkt);
|
||||
void Reset();
|
||||
void Enable();
|
||||
void Disable();
|
||||
bool IsEnabled() const;
|
||||
private:
|
||||
virtual bool DoCorrupt (Ptr<Packet> pkt) = 0;
|
||||
virtual void DoReset () = 0;
|
||||
virtual bool DoCorrupt(Ptr<Packet> pkt) = 0;
|
||||
virtual void DoReset() = 0;
|
||||
};
|
||||
|
||||
enum ErrorUnit
|
||||
@@ -133,16 +133,16 @@ a base class and first subclass that could be posted for initial review::
|
||||
class RateErrorModel : public ErrorModel
|
||||
{
|
||||
public:
|
||||
RateErrorModel ();
|
||||
virtual ~RateErrorModel ();
|
||||
enum ErrorUnit GetUnit () const;
|
||||
void SetUnit (enum ErrorUnit error_unit);
|
||||
double GetRate () const;
|
||||
void SetRate (double rate);
|
||||
void SetRandomVariable (const RandomVariable &ranvar);
|
||||
RateErrorModel();
|
||||
virtual ~RateErrorModel();
|
||||
enum ErrorUnit GetUnit() const;
|
||||
void SetUnit(enum ErrorUnit error_unit);
|
||||
double GetRate() const;
|
||||
void SetRate(double rate);
|
||||
void SetRandomVariable(const RandomVariable &ranvar);
|
||||
private:
|
||||
virtual bool DoCorrupt (Ptr<Packet> pkt);
|
||||
virtual void DoReset ();
|
||||
virtual bool DoCorrupt(Ptr<Packet> pkt);
|
||||
virtual void DoReset();
|
||||
};
|
||||
|
||||
|
||||
@@ -294,19 +294,19 @@ from class Object.::
|
||||
class ErrorModel : public Object
|
||||
{
|
||||
public:
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
|
||||
ErrorModel ();
|
||||
virtual ~ErrorModel ();
|
||||
ErrorModel();
|
||||
virtual ~ErrorModel();
|
||||
};
|
||||
|
||||
class RateErrorModel : public ErrorModel
|
||||
{
|
||||
public:
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
|
||||
RateErrorModel ();
|
||||
virtual ~RateErrorModel ();
|
||||
RateErrorModel();
|
||||
virtual ~RateErrorModel();
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -319,7 +319,7 @@ But we are in ``src/network/model``, so we must include it as "``#include
|
||||
"ns3/object.h"``". Note also that this goes outside the namespace declaration.
|
||||
|
||||
Second, each class must implement a static public member function called
|
||||
``GetTypeId ()``.
|
||||
``GetTypeId()``.
|
||||
|
||||
Third, it is a good idea to implement constructors and destructors rather than
|
||||
to let the compiler generate them, and to make the destructor virtual. In C++,
|
||||
@@ -335,52 +335,52 @@ file.::
|
||||
|
||||
namespace ns3 {
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (ErrorModel);
|
||||
NS_OBJECT_ENSURE_REGISTERED(ErrorModel);
|
||||
|
||||
TypeId ErrorModel::GetTypeId ()
|
||||
TypeId ErrorModel::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::ErrorModel")
|
||||
.SetParent<Object> ()
|
||||
.SetGroupName ("Network")
|
||||
static TypeId tid = TypeId("ns3::ErrorModel")
|
||||
.SetParent<Object>()
|
||||
.SetGroupName("Network")
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
|
||||
ErrorModel::ErrorModel ()
|
||||
ErrorModel::ErrorModel()
|
||||
{
|
||||
}
|
||||
|
||||
ErrorModel::~ErrorModel ()
|
||||
ErrorModel::~ErrorModel()
|
||||
{
|
||||
}
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (RateErrorModel);
|
||||
NS_OBJECT_ENSURE_REGISTERED(RateErrorModel);
|
||||
|
||||
TypeId RateErrorModel::GetTypeId ()
|
||||
TypeId RateErrorModel::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::RateErrorModel")
|
||||
.SetParent<ErrorModel> ()
|
||||
.SetGroupName ("Network")
|
||||
.AddConstructor<RateErrorModel> ()
|
||||
static TypeId tid = TypeId("ns3::RateErrorModel")
|
||||
.SetParent<ErrorModel>()
|
||||
.SetGroupName("Network")
|
||||
.AddConstructor<RateErrorModel>()
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
|
||||
RateErrorModel::RateErrorModel ()
|
||||
RateErrorModel::RateErrorModel()
|
||||
{
|
||||
}
|
||||
|
||||
RateErrorModel::~RateErrorModel ()
|
||||
RateErrorModel::~RateErrorModel()
|
||||
{
|
||||
}
|
||||
|
||||
What is the ``GetTypeId ()`` function? This function does a few things. It
|
||||
What is the ``GetTypeId()`` function? This function does a few things. It
|
||||
registers a unique string into the TypeId system. It establishes the hierarchy
|
||||
of objects in the attribute system (via ``SetParent``). It also declares that
|
||||
certain objects can be created via the object creation framework
|
||||
(``AddConstructor``).
|
||||
|
||||
The macro ``NS_OBJECT_ENSURE_REGISTERED (classname)`` is needed also once for
|
||||
The macro ``NS_OBJECT_ENSURE_REGISTERED(classname)`` is needed also once for
|
||||
every class that defines a new GetTypeId method, and it does the actual
|
||||
registration of the class into the system. The :ref:`Object-model` chapter
|
||||
discusses this in more detail.
|
||||
@@ -434,35 +434,35 @@ Add Accessor
|
||||
::
|
||||
|
||||
void
|
||||
PointToPointNetDevice::SetReceiveErrorModel (Ptr<ErrorModel> em)
|
||||
PointToPointNetDevice::SetReceiveErrorModel(Ptr<ErrorModel> em)
|
||||
{
|
||||
NS_LOG_FUNCTION (this << em);
|
||||
NS_LOG_FUNCTION(this << em);
|
||||
m_receiveErrorModel = em;
|
||||
}
|
||||
|
||||
.AddAttribute ("ReceiveErrorModel",
|
||||
.AddAttribute("ReceiveErrorModel",
|
||||
"The receiver error model used to simulate packet loss",
|
||||
PointerValue (),
|
||||
MakePointerAccessor (&PointToPointNetDevice::m_receiveErrorModel),
|
||||
MakePointerChecker<ErrorModel> ())
|
||||
PointerValue(),
|
||||
MakePointerAccessor(&PointToPointNetDevice::m_receiveErrorModel),
|
||||
MakePointerChecker<ErrorModel>())
|
||||
|
||||
Plumb Into the System
|
||||
+++++++++++++++++++++
|
||||
|
||||
::
|
||||
|
||||
void PointToPointNetDevice::Receive (Ptr<Packet> packet)
|
||||
void PointToPointNetDevice::Receive(Ptr<Packet> packet)
|
||||
{
|
||||
NS_LOG_FUNCTION (this << packet);
|
||||
NS_LOG_FUNCTION(this << packet);
|
||||
uint16_t protocol = 0;
|
||||
|
||||
if (m_receiveErrorModel && m_receiveErrorModel->IsCorrupt (packet) )
|
||||
if(m_receiveErrorModel && m_receiveErrorModel->IsCorrupt(packet) )
|
||||
{
|
||||
//
|
||||
// If we have an error model and it indicates that it is time to lose a
|
||||
// corrupted packet, don't forward this packet up, let it go.
|
||||
//
|
||||
m_dropTrace (packet);
|
||||
m_dropTrace(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -470,12 +470,12 @@ Plumb Into the System
|
||||
// Hit the receive trace hook, strip off the point-to-point protocol header
|
||||
// and forward this packet up the protocol stack.
|
||||
//
|
||||
m_rxTrace (packet);
|
||||
m_rxTrace(packet);
|
||||
ProcessHeader(packet, protocol);
|
||||
m_rxCallback (this, packet, protocol, GetRemote ());
|
||||
if (!m_promiscCallback.IsNull ())
|
||||
{ m_promiscCallback (this, packet, protocol, GetRemote (),
|
||||
GetAddress (), NetDevice::PACKET_HOST);
|
||||
m_rxCallback(this, packet, protocol, GetRemote());
|
||||
if(!m_promiscCallback.IsNull())
|
||||
{ m_promiscCallback(this, packet, protocol, GetRemote(),
|
||||
GetAddress(), NetDevice::PACKET_HOST);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,13 +492,13 @@ Create Null Functional Script
|
||||
// We can obtain a handle to the NetDevice via the channel and node
|
||||
// pointers
|
||||
Ptr<PointToPointNetDevice> nd3 = PointToPointTopology::GetNetDevice
|
||||
(n3, channel2);
|
||||
Ptr<ErrorModel> em = Create<ErrorModel> ();
|
||||
nd3->SetReceiveErrorModel (em);
|
||||
(n3, channel2);
|
||||
Ptr<ErrorModel> em = Create<ErrorModel>();
|
||||
nd3->SetReceiveErrorModel(em);
|
||||
|
||||
|
||||
bool
|
||||
ErrorModel::DoCorrupt (Packet& p)
|
||||
ErrorModel::DoCorrupt(Packet& p)
|
||||
{
|
||||
NS_LOG_FUNCTION;
|
||||
NS_LOG_UNCOND("Corrupt!");
|
||||
@@ -514,7 +514,7 @@ Add a Subclass
|
||||
**************
|
||||
|
||||
The trivial base class ErrorModel does not do anything interesting, but it
|
||||
provides a useful base class interface (Corrupt () and Reset ()), forwarded to
|
||||
provides a useful base class interface (``Corrupt()`` and ``Reset()``), forwarded to
|
||||
virtual functions that can be subclassed. Let's next consider what we call a
|
||||
BasicErrorModel which is based on the |ns2| ErrorModel class (in
|
||||
``ns-2/queue/errmodel.{cc,h}``).
|
||||
@@ -542,24 +542,24 @@ We declare BasicErrorModel to be a subclass of ErrorModel as follows,::
|
||||
class BasicErrorModel : public ErrorModel
|
||||
{
|
||||
public:
|
||||
static TypeId GetTypeId ();
|
||||
static TypeId GetTypeId();
|
||||
...
|
||||
private:
|
||||
// Implement base class pure virtual functions
|
||||
virtual bool DoCorrupt (Ptr<Packet> p);
|
||||
virtual bool DoReset ();
|
||||
virtual bool DoCorrupt(Ptr<Packet> p);
|
||||
virtual bool DoReset();
|
||||
...
|
||||
}
|
||||
|
||||
and configure the subclass GetTypeId function by setting a unique TypeId string
|
||||
and setting the Parent to ErrorModel::
|
||||
|
||||
TypeId RateErrorModel::GetTypeId ()
|
||||
TypeId RateErrorModel::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::RateErrorModel")
|
||||
.SetParent<ErrorModel> ()
|
||||
.SetGroupName ("Network")
|
||||
.AddConstructor<RateErrorModel> ()
|
||||
static TypeId tid = TypeId("ns3::RateErrorModel")
|
||||
.SetParent<ErrorModel>()
|
||||
.SetGroupName("Network")
|
||||
.AddConstructor<RateErrorModel>()
|
||||
...
|
||||
|
||||
Build Core Functions and Unit Tests
|
||||
|
||||
@@ -161,10 +161,10 @@ The skeleton test suite will contain the below constructor,
|
||||
which declares a new unit test named ``new-module``,
|
||||
with a single test case consisting of the class ``NewModuleTestCase1``::
|
||||
|
||||
NewModuleTestSuite::NewModuleTestSuite ()
|
||||
: TestSuite ("new-module", UNIT)
|
||||
NewModuleTestSuite::NewModuleTestSuite()
|
||||
: TestSuite("new-module", UNIT)
|
||||
{
|
||||
AddTestCase (new NewModuleTestCase1);
|
||||
AddTestCase(new NewModuleTestCase1);
|
||||
}
|
||||
|
||||
Step 3 - Declare Source Files
|
||||
|
||||
@@ -32,10 +32,10 @@ properties; for instance::
|
||||
class Address
|
||||
{
|
||||
public:
|
||||
Address ();
|
||||
Address (uint8_t type, const uint8_t *buffer, uint8_t len);
|
||||
Address (const Address & address);
|
||||
Address &operator = (const Address &address);
|
||||
Address();
|
||||
Address(uint8_t type, const uint8_t *buffer, uint8_t len);
|
||||
Address(const Address & address);
|
||||
Address &operator=(const Address &address);
|
||||
...
|
||||
private:
|
||||
uint8_t m_type;
|
||||
@@ -132,7 +132,7 @@ allocated using a templated Create or CreateObject method, as follows.
|
||||
|
||||
For objects deriving from class :cpp:class:`Object`::
|
||||
|
||||
Ptr<WifiNetDevice> device = CreateObject<WifiNetDevice> ();
|
||||
Ptr<WifiNetDevice> device = CreateObject<WifiNetDevice>();
|
||||
|
||||
Please do not create such objects using ``operator new``; create them using
|
||||
:cpp:func:`CreateObject()` instead.
|
||||
@@ -141,7 +141,7 @@ For objects deriving from class :cpp:class:`SimpleRefCount`, or other objects
|
||||
that support usage of the smart pointer class, a templated helper function is
|
||||
available and recommended to be used::
|
||||
|
||||
Ptr<B> b = Create<B> ();
|
||||
Ptr<B> b = Create<B>();
|
||||
|
||||
This is simply a wrapper around operator new that correctly handles the
|
||||
reference counting system.
|
||||
@@ -190,12 +190,12 @@ node. Let's look at how some Ipv4 protocols are added to a node.::
|
||||
static void
|
||||
AddIpv4Stack(Ptr<Node> node)
|
||||
{
|
||||
Ptr<Ipv4L3Protocol> ipv4 = CreateObject<Ipv4L3Protocol> ();
|
||||
ipv4->SetNode (node);
|
||||
node->AggregateObject (ipv4);
|
||||
Ptr<Ipv4Impl> ipv4Impl = CreateObject<Ipv4Impl> ();
|
||||
ipv4Impl->SetIpv4 (ipv4);
|
||||
node->AggregateObject (ipv4Impl);
|
||||
Ptr<Ipv4L3Protocol> ipv4 = CreateObject<Ipv4L3Protocol>();
|
||||
ipv4->SetNode(node);
|
||||
node->AggregateObject(ipv4);
|
||||
Ptr<Ipv4Impl> ipv4Impl = CreateObject<Ipv4Impl>();
|
||||
ipv4Impl->SetIpv4(ipv4);
|
||||
node->AggregateObject(ipv4Impl);
|
||||
}
|
||||
|
||||
Note that the Ipv4 protocols are created using :cpp:func:`CreateObject()`.
|
||||
@@ -220,7 +220,7 @@ configure a default route. To do so, it must access an object within the node
|
||||
that has an interface to the IP forwarding configuration. It performs the
|
||||
following::
|
||||
|
||||
Ptr<Ipv4> ipv4 = m_node->GetObject<Ipv4> ();
|
||||
Ptr<Ipv4> ipv4 = m_node->GetObject<Ipv4>();
|
||||
|
||||
If the node in fact does not have an Ipv4 object aggregated to it, then the
|
||||
method will return null. Therefore, it is good practice to check the return
|
||||
@@ -248,9 +248,9 @@ pattern in use in the |ns3| system. It is heavily used in the "helper" API.
|
||||
Class :cpp:class:`ObjectFactory` can be used to instantiate objects and to
|
||||
configure the attributes on those objects::
|
||||
|
||||
void SetTypeId (TypeId tid);
|
||||
void Set (std::string name, const AttributeValue &value);
|
||||
Ptr<T> Create () const;
|
||||
void SetTypeId(TypeId tid);
|
||||
void Set(std::string name, const AttributeValue &value);
|
||||
Ptr<T> Create() const;
|
||||
|
||||
The first method allows one to use the |ns3| TypeId system to specify the type
|
||||
of objects created. The second allows one to set attributes on the objects to be
|
||||
@@ -260,15 +260,15 @@ For example: ::
|
||||
|
||||
ObjectFactory factory;
|
||||
// Make this factory create objects of type FriisPropagationLossModel
|
||||
factory.SetTypeId ("ns3::FriisPropagationLossModel")
|
||||
factory.SetTypeId("ns3::FriisPropagationLossModel")
|
||||
// Make this factory object change a default value of an attribute, for
|
||||
// subsequently created objects
|
||||
factory.Set ("SystemLoss", DoubleValue (2.0));
|
||||
factory.Set("SystemLoss", DoubleValue(2.0));
|
||||
// Create one such object
|
||||
Ptr<Object> object = factory.Create ();
|
||||
factory.Set ("SystemLoss", DoubleValue (3.0));
|
||||
Ptr<Object> object = factory.Create();
|
||||
factory.Set("SystemLoss", DoubleValue(3.0));
|
||||
// Create another object with a different SystemLoss
|
||||
Ptr<Object> object = factory.Create ();
|
||||
Ptr<Object> object = factory.Create();
|
||||
|
||||
Downcasting
|
||||
***********
|
||||
@@ -285,9 +285,9 @@ dynamic casting much more user friendly::
|
||||
|
||||
template <typename T1, typename T2>
|
||||
Ptr<T1>
|
||||
DynamicCast (Ptr<T2> const&p)
|
||||
DynamicCast(Ptr<T2> const&p)
|
||||
{
|
||||
return Ptr<T1> (dynamic_cast<T1 *> (PeekPointer (p)));
|
||||
return Ptr<T1>(dynamic_cast<T1 *>(PeekPointer(p)));
|
||||
}
|
||||
|
||||
DynamicCast works when the programmer has a base type pointer and is testing
|
||||
|
||||
@@ -117,20 +117,20 @@ The correct way to create these objects is to use the templated
|
||||
|
||||
::
|
||||
|
||||
Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable> ();
|
||||
Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable>();
|
||||
|
||||
then you can access values by calling methods on the object such as:
|
||||
|
||||
::
|
||||
|
||||
myRandomNo = x->GetInteger ();
|
||||
myRandomNo = x->GetInteger();
|
||||
|
||||
|
||||
If you try to instead do something like this:
|
||||
|
||||
::
|
||||
|
||||
myRandomNo = UniformRandomVariable().GetInteger ();
|
||||
myRandomNo = UniformRandomVariable().GetInteger();
|
||||
|
||||
your program will encounter a segmentation fault, because the implementation
|
||||
relies on some attribute construction that occurs only when `CreateObject`
|
||||
@@ -160,11 +160,11 @@ A class :cpp:class:`ns3::RngSeedManager` provides an API to control the seeding
|
||||
run number behavior. This seeding and substream state setting must be called
|
||||
before any random variables are created; e.g::
|
||||
|
||||
RngSeedManager::SetSeed (3); // Changes seed from default of 1 to 3
|
||||
RngSeedManager::SetRun (7); // Changes run number from default of 1 to 7
|
||||
RngSeedManager::SetSeed(3); // Changes seed from default of 1 to 3
|
||||
RngSeedManager::SetRun(7); // Changes run number from default of 1 to 7
|
||||
// Now, create random variables
|
||||
Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable> ();
|
||||
Ptr<ExponentialRandomVariable> y = CreateObject<ExponentialRandomVarlable> ();
|
||||
Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable>();
|
||||
Ptr<ExponentialRandomVariable> y = CreateObject<ExponentialRandomVarlable>();
|
||||
...
|
||||
|
||||
Which is better, setting a new seed or advancing the substream state? There is
|
||||
@@ -227,13 +227,13 @@ that access the next value in the substream.
|
||||
* \brief Returns a random double from the underlying distribution
|
||||
* \return A floating point random value
|
||||
*/
|
||||
double GetValue () const;
|
||||
double GetValue() const;
|
||||
|
||||
/**
|
||||
* \brief Returns a random integer from the underlying distribution
|
||||
* \return Integer cast of ::GetValue()
|
||||
*/
|
||||
uint32_t GetInteger () const;
|
||||
uint32_t GetInteger() const;
|
||||
|
||||
We have already described the seeding configuration above. Different
|
||||
RandomVariable subclasses may have additional API.
|
||||
@@ -273,17 +273,17 @@ that values can be set for them through the |ns3| attribute system.
|
||||
An example is in the propagation models for WifiNetDevice::
|
||||
|
||||
TypeId
|
||||
RandomPropagationDelayModel::GetTypeId ()
|
||||
RandomPropagationDelayModel::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::RandomPropagationDelayModel")
|
||||
.SetParent<PropagationDelayModel> ()
|
||||
.SetGroupName ("Propagation")
|
||||
.AddConstructor<RandomPropagationDelayModel> ()
|
||||
.AddAttribute ("Variable",
|
||||
"The random variable which generates random delays (s).",
|
||||
StringValue ("ns3::UniformRandomVariable"),
|
||||
MakePointerAccessor (&RandomPropagationDelayModel::m_variable),
|
||||
MakePointerChecker<RandomVariableStream> ())
|
||||
static TypeId tid = TypeId("ns3::RandomPropagationDelayModel")
|
||||
.SetParent<PropagationDelayModel>()
|
||||
.SetGroupName("Propagation")
|
||||
.AddConstructor<RandomPropagationDelayModel>()
|
||||
.AddAttribute("Variable",
|
||||
"The random variable which generates random delays (s).",
|
||||
StringValue("ns3::UniformRandomVariable"),
|
||||
MakePointerAccessor(&RandomPropagationDelayModel::m_variable),
|
||||
MakePointerChecker<RandomVariableStream>())
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ The usage of the realtime simulator is straightforward, from a scripting
|
||||
perspective. Users just need to set the attribute
|
||||
``SimulatorImplementationType`` to the Realtime simulator, such as follows: ::
|
||||
|
||||
GlobalValue::Bind ("SimulatorImplementationType",
|
||||
StringValue ("ns3::RealtimeSimulatorImpl"));
|
||||
GlobalValue::Bind("SimulatorImplementationType",
|
||||
StringValue("ns3::RealtimeSimulatorImpl"));
|
||||
|
||||
There is a script in ``examples/realtime/realtime-udp-echo.cc`` that
|
||||
has an example of how to configure the realtime behavior. Try:
|
||||
|
||||
@@ -707,7 +707,7 @@ arguments as needed, but basedir is the minimum needed)::
|
||||
(gdb) r --suite=
|
||||
Starting program: <..>/build/utils/ns3-dev-test-runner-debug --suite=wifi-interference
|
||||
[Thread debugging using libthread_db enabled]
|
||||
assert failed. file=../src/core/model/type-id.cc, line=138, cond="uid <= m_information.size () && uid != 0"
|
||||
assert failed. file=../src/core/model/type-id.cc, line=138, cond="uid <= m_information.size() && uid != 0"
|
||||
...
|
||||
|
||||
Here is another example of how to use valgrind to debug a memory problem
|
||||
@@ -757,13 +757,13 @@ as a ''unit'' test with the display name, ``my-test-suite-name``.
|
||||
class MySuite : public TestSuite
|
||||
{
|
||||
public:
|
||||
MyTestSuite ();
|
||||
MyTestSuite();
|
||||
};
|
||||
|
||||
MyTestSuite::MyTestSuite ()
|
||||
: TestSuite ("my-test-suite-name", UNIT)
|
||||
MyTestSuite::MyTestSuite()
|
||||
: TestSuite("my-test-suite-name", UNIT)
|
||||
{
|
||||
AddTestCase (new MyTestCase, TestCase::QUICK);
|
||||
AddTestCase(new MyTestCase, TestCase::QUICK);
|
||||
}
|
||||
|
||||
static MyTestSuite myTestSuite;
|
||||
@@ -794,20 +794,20 @@ override also the ``DoSetup`` method.
|
||||
|
||||
class MyTestCase : public TestCase
|
||||
{
|
||||
MyTestCase ();
|
||||
virtual void DoSetup ();
|
||||
virtual void DoRun ();
|
||||
MyTestCase();
|
||||
virtual void DoSetup();
|
||||
virtual void DoRun();
|
||||
};
|
||||
|
||||
MyTestCase::MyTestCase ()
|
||||
: TestCase ("Check some bit of functionality")
|
||||
MyTestCase::MyTestCase()
|
||||
: TestCase("Check some bit of functionality")
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
MyTestCase::DoRun ()
|
||||
MyTestCase::DoRun()
|
||||
{
|
||||
NS_TEST_ASSERT_MSG_EQ (true, true, "Some failure message");
|
||||
NS_TEST_ASSERT_MSG_EQ(true, true, "Some failure message");
|
||||
}
|
||||
|
||||
Utilities
|
||||
|
||||
@@ -24,7 +24,7 @@ output, as in, ::
|
||||
|
||||
#include <iostream>
|
||||
...
|
||||
int main ()
|
||||
int main()
|
||||
{
|
||||
...
|
||||
std::cout << "The value of x is " << x << std::endl;
|
||||
@@ -139,19 +139,19 @@ made using those operators.::
|
||||
class MyObject : public Object
|
||||
{
|
||||
public:
|
||||
static TypeId GetTypeId ()
|
||||
static TypeId GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("MyObject")
|
||||
.SetParent (Object::GetTypeId ())
|
||||
.AddConstructor<MyObject> ()
|
||||
.AddTraceSource ("MyInteger",
|
||||
"An integer value to trace.",
|
||||
MakeTraceSourceAccessor (&MyObject::m_myInt))
|
||||
static TypeId tid = TypeId("MyObject")
|
||||
.SetParent(Object::GetTypeId())
|
||||
.AddConstructor<MyObject>()
|
||||
.AddTraceSource("MyInteger",
|
||||
"An integer value to trace.",
|
||||
MakeTraceSourceAccessor(&MyObject::m_myInt))
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
|
||||
MyObject () {}
|
||||
MyObject() {}
|
||||
TracedValue<uint32_t> m_myInt;
|
||||
};
|
||||
|
||||
@@ -166,7 +166,7 @@ infrastructure that overloads the operators mentioned above and drives the
|
||||
callback process.::
|
||||
|
||||
void
|
||||
IntTrace (Int oldValue, Int newValue)
|
||||
IntTrace(Int oldValue, Int newValue)
|
||||
{
|
||||
std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
|
||||
}
|
||||
@@ -176,11 +176,11 @@ function. This function will be called whenever one of the operators of the
|
||||
``TracedValue`` is executed.::
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
Ptr<MyObject> myObject = CreateObject<MyObject> ();
|
||||
Ptr<MyObject> myObject = CreateObject<MyObject>();
|
||||
|
||||
myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback(&IntTrace));
|
||||
myObject->TraceConnectWithoutContext("MyInteger", MakeCallback(&IntTrace));
|
||||
|
||||
myObject->m_myInt = 1234;
|
||||
}
|
||||
@@ -228,13 +228,13 @@ called a *config path*.
|
||||
For example, one might find something that looks like the following in the
|
||||
system (taken from ``examples/tcp-large-transfer.cc``)::
|
||||
|
||||
void CwndTracer (uint32_t oldval, uint32_t newval) {}
|
||||
void CwndTracer(uint32_t oldval, uint32_t newval) {}
|
||||
|
||||
...
|
||||
|
||||
Config::ConnectWithoutContext (
|
||||
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
|
||||
MakeCallback (&CwndTracer));
|
||||
Config::ConnectWithoutContext(
|
||||
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
|
||||
MakeCallback(&CwndTracer));
|
||||
|
||||
This should look very familiar. It is the same thing as the previous example,
|
||||
except that a static member function of class ``Config`` is being called instead
|
||||
@@ -246,11 +246,11 @@ must be an ``Attribute`` of an ``Object``. In fact, if you had a pointer to the
|
||||
``Object`` that has the "CongestionWindow" ``Attribute`` handy (call it
|
||||
``theObject``), you could write this just like the previous example::
|
||||
|
||||
void CwndTracer (uint32_t oldval, uint32_t newval) {}
|
||||
void CwndTracer(uint32_t oldval, uint32_t newval) {}
|
||||
|
||||
...
|
||||
|
||||
theObject->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndTracer));
|
||||
theObject->TraceConnectWithoutContext("CongestionWindow", MakeCallback(&CwndTracer));
|
||||
|
||||
It turns out that the code for ``Config::ConnectWithoutContext`` does exactly
|
||||
that. This function takes a path that represents a chain of ``Object`` pointers
|
||||
@@ -284,7 +284,7 @@ This socket, the type of which turns out to be an ``ns3::TcpSocketImpl`` defines
|
||||
an attribute called "CongestionWindow" which is a ``TracedValue<uint32_t>``.
|
||||
The ``Config::ConnectWithoutContext`` now does a,::
|
||||
|
||||
object->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndTracer));
|
||||
object->TraceConnectWithoutContext("CongestionWindow", MakeCallback(&CwndTracer));
|
||||
|
||||
using the object pointer from "SocketList/0" which makes the connection between
|
||||
the trace source defined in the socket to the callback -- ``CwndTracer``.
|
||||
@@ -323,10 +323,10 @@ helper methods designed for use inside other (device) helpers.
|
||||
|
||||
Perhaps you will recall seeing some of these variations::
|
||||
|
||||
pointToPoint.EnablePcapAll ("second");
|
||||
pointToPoint.EnablePcap ("second", p2pNodes.Get (0)->GetId (), 0);
|
||||
csma.EnablePcap ("third", csmaDevices.Get (0), true);
|
||||
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
|
||||
pointToPoint.EnablePcapAll("second");
|
||||
pointToPoint.EnablePcap("second", p2pNodes.Get(0)->GetId(), 0);
|
||||
csma.EnablePcap("third", csmaDevices.Get(0), true);
|
||||
pointToPoint.EnableAsciiAll(ascii.CreateFileStream("myfirst.tr"));
|
||||
|
||||
What may not be obvious, though, is that there is a consistent model for all of
|
||||
the trace-related methods found in the system. We will now take a little time
|
||||
@@ -381,14 +381,14 @@ The class ``PcapHelperForDevice`` is a ``mixin`` provides the high level
|
||||
functionality for using pcap tracing in an |ns3| device. Every device must
|
||||
implement a single virtual method inherited from this class.::
|
||||
|
||||
virtual void EnablePcapInternal (std::string prefix, Ptr<NetDevice> nd, bool promiscuous) = 0;
|
||||
virtual void EnablePcapInternal(std::string prefix, Ptr<NetDevice> nd, bool promiscuous) = 0;
|
||||
|
||||
The signature of this method reflects the device-centric view of the situation
|
||||
at this level. All of the public methods inherited from class
|
||||
``PcapUserHelperForDevice`` reduce to calling this single device-dependent
|
||||
implementation method. For example, the lowest level pcap method,::
|
||||
|
||||
void EnablePcap (std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap(std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false);
|
||||
|
||||
will call the device implementation of ``EnablePcapInternal`` directly. All
|
||||
other public pcap tracing methods build on this implementation to provide
|
||||
@@ -402,17 +402,17 @@ Pcap Tracing Device Helper Methods
|
||||
|
||||
::
|
||||
|
||||
void EnablePcap (std::string prefix, Ptr<NetDevice> nd,
|
||||
bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap (std::string prefix, std::string ndName,
|
||||
bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap (std::string prefix, NetDeviceContainer d,
|
||||
bool promiscuous = false);
|
||||
void EnablePcap (std::string prefix, NodeContainer n,
|
||||
bool promiscuous = false);
|
||||
void EnablePcap (std::string prefix, uint32_t nodeid, uint32_t deviceid,
|
||||
bool promiscuous = false);
|
||||
void EnablePcapAll (std::string prefix, bool promiscuous = false);
|
||||
void EnablePcap(std::string prefix, Ptr<NetDevice> nd,
|
||||
bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap(std::string prefix, std::string ndName,
|
||||
bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap(std::string prefix, NetDeviceContainer d,
|
||||
bool promiscuous = false);
|
||||
void EnablePcap(std::string prefix, NodeContainer n,
|
||||
bool promiscuous = false);
|
||||
void EnablePcap(std::string prefix, uint32_t nodeid, uint32_t deviceid,
|
||||
bool promiscuous = false);
|
||||
void EnablePcapAll(std::string prefix, bool promiscuous = false);
|
||||
|
||||
In each of the methods shown above, there is a default parameter called
|
||||
``promiscuous`` that defaults to false. This parameter indicates that the trace
|
||||
@@ -422,7 +422,7 @@ mode) simply add a true parameter to any of the calls above. For example,::
|
||||
|
||||
Ptr<NetDevice> nd;
|
||||
...
|
||||
helper.EnablePcap ("prefix", nd, true);
|
||||
helper.EnablePcap("prefix", nd, true);
|
||||
|
||||
will enable promiscuous mode captures on the ``NetDevice`` specified by ``nd``.
|
||||
|
||||
@@ -438,7 +438,7 @@ since the net device must belong to exactly one ``Node``. For example,::
|
||||
|
||||
Ptr<NetDevice> nd;
|
||||
...
|
||||
helper.EnablePcap ("prefix", nd);
|
||||
helper.EnablePcap("prefix", nd);
|
||||
|
||||
You can enable pcap tracing on a particular node/net-device pair by providing a
|
||||
``std::string`` representing an object name service string to an ``EnablePcap``
|
||||
@@ -446,10 +446,10 @@ method. The ``Ptr<NetDevice>`` is looked up from the name string. Again, the
|
||||
``<Node>`` is implicit since the named net device must belong to exactly one
|
||||
``Node``. For example,::
|
||||
|
||||
Names::Add ("server" ...);
|
||||
Names::Add ("server/eth0" ...);
|
||||
Names::Add("server" ...);
|
||||
Names::Add("server/eth0" ...);
|
||||
...
|
||||
helper.EnablePcap ("prefix", "server/ath0");
|
||||
helper.EnablePcap("prefix", "server/ath0");
|
||||
|
||||
You can enable pcap tracing on a collection of node/net-device pairs by
|
||||
providing a ``NetDeviceContainer``. For each ``NetDevice`` in the container the
|
||||
@@ -460,7 +460,7 @@ example,::
|
||||
|
||||
NetDeviceContainer d = ...;
|
||||
...
|
||||
helper.EnablePcap ("prefix", d);
|
||||
helper.EnablePcap("prefix", d);
|
||||
|
||||
You can enable pcap tracing on a collection of node/net-device pairs by
|
||||
providing a ``NodeContainer``. For each ``Node`` in the ``NodeContainer`` its
|
||||
@@ -471,18 +471,18 @@ enabled.::
|
||||
|
||||
NodeContainer n;
|
||||
...
|
||||
helper.EnablePcap ("prefix", n);
|
||||
helper.EnablePcap("prefix", n);
|
||||
|
||||
You can enable pcap tracing on the basis of node ID and device ID as well as
|
||||
with explicit ``Ptr``. Each ``Node`` in the system has an integer node ID and
|
||||
each device connected to a node has an integer device ID.::
|
||||
|
||||
helper.EnablePcap ("prefix", 21, 1);
|
||||
helper.EnablePcap("prefix", 21, 1);
|
||||
|
||||
Finally, you can enable pcap tracing for all devices in the system, with the
|
||||
same type as that managed by the device helper.::
|
||||
|
||||
helper.EnablePcapAll ("prefix");
|
||||
helper.EnablePcapAll("prefix");
|
||||
|
||||
Pcap Tracing Device Helper Filename Selection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -506,8 +506,8 @@ your pcap file name will automatically pick this up and be called
|
||||
|
||||
Finally, two of the methods shown above,::
|
||||
|
||||
void EnablePcap (std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap (std::string prefix, std::string ndName, bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap(std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false);
|
||||
void EnablePcap(std::string prefix, std::string ndName, bool promiscuous = false, bool explicitFilename = false);
|
||||
|
||||
have a default parameter called ``explicitFilename``. When set to true, this
|
||||
parameter disables the automatic filename completion mechanism and allows you to
|
||||
@@ -520,7 +520,7 @@ given device, one could::
|
||||
|
||||
Ptr<NetDevice> nd;
|
||||
...
|
||||
helper.EnablePcap ("my-pcap-file.pcap", nd, true, true);
|
||||
helper.EnablePcap("my-pcap-file.pcap", nd, true, true);
|
||||
|
||||
The first ``true`` parameter enables promiscuous mode traces and the second
|
||||
tells the helper to interpret the ``prefix`` parameter as a complete filename.
|
||||
@@ -537,7 +537,7 @@ using ASCII tracing to a device helper class. As in the pcap case, every device
|
||||
must implement a single virtual method inherited from the ASCII trace
|
||||
``mixin``.::
|
||||
|
||||
virtual void EnableAsciiInternal (Ptr<OutputStreamWrapper> stream, std::string prefix, Ptr<NetDevice> nd) = 0;
|
||||
virtual void EnableAsciiInternal(Ptr<OutputStreamWrapper> stream, std::string prefix, Ptr<NetDevice> nd) = 0;
|
||||
|
||||
The signature of this method reflects the device-centric view of the situation
|
||||
at this level; and also the fact that the helper may be writing to a shared
|
||||
@@ -546,8 +546,8 @@ class ``AsciiTraceHelperForDevice`` reduce to calling this single device-
|
||||
dependent implementation method. For example, the lowest level ASCII trace
|
||||
methods,::
|
||||
|
||||
void EnableAscii (std::string prefix, Ptr<NetDevice> nd);
|
||||
void EnableAscii (Ptr<OutputStreamWrapper> stream, Ptr<NetDevice> nd);
|
||||
void EnableAscii(std::string prefix, Ptr<NetDevice> nd);
|
||||
void EnableAscii(Ptr<OutputStreamWrapper> stream, Ptr<NetDevice> nd);
|
||||
|
||||
will call the device implementation of ``EnableAsciiInternal`` directly,
|
||||
providing either a valid prefix or stream. All other public ASCII tracing
|
||||
@@ -562,23 +562,23 @@ Ascii Tracing Device Helper Methods
|
||||
|
||||
::
|
||||
|
||||
void EnableAscii (std::string prefix, Ptr<NetDevice> nd);
|
||||
void EnableAscii (Ptr<OutputStreamWrapper> stream, Ptr<NetDevice> nd);
|
||||
void EnableAscii(std::string prefix, Ptr<NetDevice> nd);
|
||||
void EnableAscii(Ptr<OutputStreamWrapper> stream, Ptr<NetDevice> nd);
|
||||
|
||||
void EnableAscii (std::string prefix, std::string ndName);
|
||||
void EnableAscii (Ptr<OutputStreamWrapper> stream, std::string ndName);
|
||||
void EnableAscii(std::string prefix, std::string ndName);
|
||||
void EnableAscii(Ptr<OutputStreamWrapper> stream, std::string ndName);
|
||||
|
||||
void EnableAscii (std::string prefix, NetDeviceContainer d);
|
||||
void EnableAscii (Ptr<OutputStreamWrapper> stream, NetDeviceContainer d);
|
||||
void EnableAscii(std::string prefix, NetDeviceContainer d);
|
||||
void EnableAscii(Ptr<OutputStreamWrapper> stream, NetDeviceContainer d);
|
||||
|
||||
void EnableAscii (std::string prefix, NodeContainer n);
|
||||
void EnableAscii (Ptr<OutputStreamWrapper> stream, NodeContainer n);
|
||||
void EnableAscii(std::string prefix, NodeContainer n);
|
||||
void EnableAscii(Ptr<OutputStreamWrapper> stream, NodeContainer n);
|
||||
|
||||
void EnableAscii (std::string prefix, uint32_t nodeid, uint32_t deviceid);
|
||||
void EnableAscii (Ptr<OutputStreamWrapper> stream, uint32_t nodeid, uint32_t deviceid);
|
||||
void EnableAscii(std::string prefix, uint32_t nodeid, uint32_t deviceid);
|
||||
void EnableAscii(Ptr<OutputStreamWrapper> stream, uint32_t nodeid, uint32_t deviceid);
|
||||
|
||||
void EnableAsciiAll (std::string prefix);
|
||||
void EnableAsciiAll (Ptr<OutputStreamWrapper> stream);
|
||||
void EnableAsciiAll(std::string prefix);
|
||||
void EnableAsciiAll(Ptr<OutputStreamWrapper> stream);
|
||||
|
||||
You are encouraged to peruse the Doxygen for class ``TraceHelperForDevice`` to
|
||||
find the details of these methods; but to summarize ...
|
||||
@@ -598,7 +598,7 @@ exactly one ``Node``. For example,::
|
||||
|
||||
Ptr<NetDevice> nd;
|
||||
...
|
||||
helper.EnableAscii ("prefix", nd);
|
||||
helper.EnableAscii("prefix", nd);
|
||||
|
||||
In this case, no trace contexts are written to the ASCII trace file since they
|
||||
would be redundant. The system will pick the file name to be created using the
|
||||
@@ -612,10 +612,10 @@ refer to a single file::
|
||||
Ptr<NetDevice> nd1;
|
||||
Ptr<NetDevice> nd2;
|
||||
...
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream("trace-file-name.tr");
|
||||
...
|
||||
helper.EnableAscii (stream, nd1);
|
||||
helper.EnableAscii (stream, nd2);
|
||||
helper.EnableAscii(stream, nd1);
|
||||
helper.EnableAscii(stream, nd2);
|
||||
|
||||
In this case, trace contexts are written to the ASCII trace file since they
|
||||
are required to disambiguate traces from the two devices. Note that since the
|
||||
@@ -628,28 +628,28 @@ You can enable ASCII tracing on a particular node/net-device pair by providing a
|
||||
string. Again, the ``<Node>`` is implicit since the named net device must
|
||||
belong to exactly one ``Node``. For example,::
|
||||
|
||||
Names::Add ("client" ...);
|
||||
Names::Add ("client/eth0" ...);
|
||||
Names::Add ("server" ...);
|
||||
Names::Add ("server/eth0" ...);
|
||||
Names::Add("client" ...);
|
||||
Names::Add("client/eth0" ...);
|
||||
Names::Add("server" ...);
|
||||
Names::Add("server/eth0" ...);
|
||||
...
|
||||
helper.EnableAscii ("prefix", "client/eth0");
|
||||
helper.EnableAscii ("prefix", "server/eth0");
|
||||
helper.EnableAscii("prefix", "client/eth0");
|
||||
helper.EnableAscii("prefix", "server/eth0");
|
||||
|
||||
This would result in two files named ``prefix-client-eth0.tr`` and
|
||||
``prefix-server-eth0.tr`` with traces for each device in the respective trace
|
||||
file. Since all of the EnableAscii functions are overloaded to take a stream
|
||||
wrapper, you can use that form as well::
|
||||
|
||||
Names::Add ("client" ...);
|
||||
Names::Add ("client/eth0" ...);
|
||||
Names::Add ("server" ...);
|
||||
Names::Add ("server/eth0" ...);
|
||||
Names::Add("client" ...);
|
||||
Names::Add("client/eth0" ...);
|
||||
Names::Add("server" ...);
|
||||
Names::Add("server/eth0" ...);
|
||||
...
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream("trace-file-name.tr");
|
||||
...
|
||||
helper.EnableAscii (stream, "client/eth0");
|
||||
helper.EnableAscii (stream, "server/eth0");
|
||||
helper.EnableAscii(stream, "client/eth0");
|
||||
helper.EnableAscii(stream, "server/eth0");
|
||||
|
||||
This would result in a single trace file called ``trace-file-name.tr`` that
|
||||
contains all of the trace events for both devices. The events would be
|
||||
@@ -663,7 +663,7 @@ since the found net device must belong to exactly one ``Node``. For example,::
|
||||
|
||||
NetDeviceContainer d = ...;
|
||||
...
|
||||
helper.EnableAscii ("prefix", d);
|
||||
helper.EnableAscii("prefix", d);
|
||||
|
||||
This would result in a number of ASCII trace files being created, each of which
|
||||
follows the <prefix>-<node id>-<device id>.tr convention. Combining all of the
|
||||
@@ -671,9 +671,9 @@ traces into a single file is accomplished similarly to the examples above::
|
||||
|
||||
NetDeviceContainer d = ...;
|
||||
...
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream("trace-file-name.tr");
|
||||
...
|
||||
helper.EnableAscii (stream, d);
|
||||
helper.EnableAscii(stream, d);
|
||||
|
||||
You can enable ascii tracing on a collection of node/net-device pairs by
|
||||
providing a ``NodeContainer``. For each ``Node`` in the ``NodeContainer`` its
|
||||
@@ -684,7 +684,7 @@ enabled.::
|
||||
|
||||
NodeContainer n;
|
||||
...
|
||||
helper.EnableAscii ("prefix", n);
|
||||
helper.EnableAscii("prefix", n);
|
||||
|
||||
This would result in a number of ASCII trace files being created, each of which
|
||||
follows the <prefix>-<node id>-<device id>.tr convention. Combining all of the
|
||||
@@ -694,14 +694,14 @@ You can enable pcap tracing on the basis of node ID and device ID as well as
|
||||
with explicit ``Ptr``. Each ``Node`` in the system has an integer node ID and
|
||||
each device connected to a node has an integer device ID.::
|
||||
|
||||
helper.EnableAscii ("prefix", 21, 1);
|
||||
helper.EnableAscii("prefix", 21, 1);
|
||||
|
||||
Of course, the traces can be combined into a single file as shown above.
|
||||
|
||||
Finally, you can enable pcap tracing for all devices in the system, with the
|
||||
same type as that managed by the device helper.::
|
||||
|
||||
helper.EnableAsciiAll ("prefix");
|
||||
helper.EnableAsciiAll("prefix");
|
||||
|
||||
This would result in a number of ASCII trace files being created, one for
|
||||
every device in the system of the type managed by the helper. All of these
|
||||
@@ -751,14 +751,14 @@ difference will be in the method names and signatures. Different method names
|
||||
are required to disambiguate class ``Ipv4`` from ``Ipv6`` which are both derived
|
||||
from class ``Object``, and methods that share the same signature.::
|
||||
|
||||
virtual void EnablePcapIpv4Internal (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface) = 0;
|
||||
virtual void EnablePcapIpv4Internal(std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface) = 0;
|
||||
|
||||
The signature of this method reflects the protocol and interface-centric view of
|
||||
the situation at this level. All of the public methods inherited from class
|
||||
``PcapHelperForIpv4`` reduce to calling this single device-dependent
|
||||
implementation method. For example, the lowest level pcap method,::
|
||||
|
||||
void EnablePcapIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnablePcapIpv4(std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
|
||||
will call the device implementation of ``EnablePcapIpv4Internal`` directly. All
|
||||
other public pcap tracing methods build on this implementation to provide
|
||||
@@ -777,12 +777,12 @@ constraints.
|
||||
|
||||
Note that just like in the device version, there are six methods::
|
||||
|
||||
void EnablePcapIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnablePcapIpv4 (std::string prefix, std::string ipv4Name, uint32_t interface);
|
||||
void EnablePcapIpv4 (std::string prefix, Ipv4InterfaceContainer c);
|
||||
void EnablePcapIpv4 (std::string prefix, NodeContainer n);
|
||||
void EnablePcapIpv4 (std::string prefix, uint32_t nodeid, uint32_t interface);
|
||||
void EnablePcapIpv4All (std::string prefix);
|
||||
void EnablePcapIpv4(std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnablePcapIpv4(std::string prefix, std::string ipv4Name, uint32_t interface);
|
||||
void EnablePcapIpv4(std::string prefix, Ipv4InterfaceContainer c);
|
||||
void EnablePcapIpv4(std::string prefix, NodeContainer n);
|
||||
void EnablePcapIpv4(std::string prefix, uint32_t nodeid, uint32_t interface);
|
||||
void EnablePcapIpv4All(std::string prefix);
|
||||
|
||||
You are encouraged to peruse the Doxygen for class ``PcapHelperForIpv4`` to find
|
||||
the details of these methods; but to summarize ...
|
||||
@@ -790,17 +790,17 @@ the details of these methods; but to summarize ...
|
||||
You can enable pcap tracing on a particular protocol/interface pair by providing
|
||||
a ``Ptr<Ipv4>`` and ``interface`` to an ``EnablePcap`` method. For example,::
|
||||
|
||||
Ptr<Ipv4> ipv4 = node->GetObject<Ipv4> ();
|
||||
Ptr<Ipv4> ipv4 = node->GetObject<Ipv4>();
|
||||
...
|
||||
helper.EnablePcapIpv4 ("prefix", ipv4, 0);
|
||||
helper.EnablePcapIpv4("prefix", ipv4, 0);
|
||||
|
||||
You can enable pcap tracing on a particular node/net-device pair by providing a
|
||||
``std::string`` representing an object name service string to an ``EnablePcap``
|
||||
method. The ``Ptr<Ipv4>`` is looked up from the name string. For example,::
|
||||
|
||||
Names::Add ("serverIPv4" ...);
|
||||
Names::Add("serverIPv4" ...);
|
||||
...
|
||||
helper.EnablePcapIpv4 ("prefix", "serverIpv4", 1);
|
||||
helper.EnablePcapIpv4("prefix", "serverIpv4", 1);
|
||||
|
||||
You can enable pcap tracing on a collection of protocol/interface pairs by
|
||||
providing an ``Ipv4InterfaceContainer``. For each ``Ipv4`` / interface pair in
|
||||
@@ -810,13 +810,13 @@ corresponding interface. For example,::
|
||||
|
||||
NodeContainer nodes;
|
||||
...
|
||||
NetDeviceContainer devices = deviceHelper.Install (nodes);
|
||||
NetDeviceContainer devices = deviceHelper.Install(nodes);
|
||||
...
|
||||
Ipv4AddressHelper ipv4;
|
||||
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer interfaces = ipv4.Assign (devices);
|
||||
ipv4.SetBase("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer interfaces = ipv4.Assign(devices);
|
||||
...
|
||||
helper.EnablePcapIpv4 ("prefix", interfaces);
|
||||
helper.EnablePcapIpv4("prefix", interfaces);
|
||||
|
||||
You can enable pcap tracing on a collection of protocol/interface pairs by
|
||||
providing a ``NodeContainer``. For each ``Node`` in the ``NodeContainer`` the
|
||||
@@ -825,19 +825,19 @@ and tracing is enabled on the resulting pairs. For example,::
|
||||
|
||||
NodeContainer n;
|
||||
...
|
||||
helper.EnablePcapIpv4 ("prefix", n);
|
||||
helper.EnablePcapIpv4("prefix", n);
|
||||
|
||||
You can enable pcap tracing on the basis of node ID and interface as well. In
|
||||
this case, the node-id is translated to a ``Ptr<Node>`` and the appropriate
|
||||
protocol is looked up in the node. The resulting protocol and interface are used
|
||||
to specify the resulting trace source.::
|
||||
|
||||
helper.EnablePcapIpv4 ("prefix", 21, 1);
|
||||
helper.EnablePcapIpv4("prefix", 21, 1);
|
||||
|
||||
Finally, you can enable pcap tracing for all interfaces in the system, with
|
||||
associated protocol being the same type as that managed by the device helper.::
|
||||
|
||||
helper.EnablePcapIpv4All ("prefix");
|
||||
helper.EnablePcapIpv4All("prefix");
|
||||
|
||||
Pcap Tracing Protocol Helper Filename Selection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -884,8 +884,8 @@ The class ``AsciiTraceHelperForIpv4`` adds the high level functionality for
|
||||
using ASCII tracing to a protocol helper. Each protocol that enables these
|
||||
methods must implement a single virtual method inherited from this class.::
|
||||
|
||||
virtual void EnableAsciiIpv4Internal (Ptr<OutputStreamWrapper> stream, std::string prefix,
|
||||
Ptr<Ipv4> ipv4, uint32_t interface) = 0;
|
||||
virtual void EnableAsciiIpv4Internal(Ptr<OutputStreamWrapper> stream, std::string prefix,
|
||||
Ptr<Ipv4> ipv4, uint32_t interface) = 0;
|
||||
|
||||
The signature of this method reflects the protocol- and interface-centric view
|
||||
of the situation at this level; and also the fact that the helper may be writing
|
||||
@@ -894,8 +894,8 @@ to a shared output stream. All of the public methods inherited from class
|
||||
dependent implementation method. For example, the lowest level ascii trace
|
||||
methods,::
|
||||
|
||||
void EnableAsciiIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnableAsciiIpv4(std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnableAsciiIpv4(Ptr<OutputStreamWrapper> stream, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
|
||||
will call the device implementation of ``EnableAsciiIpv4Internal`` directly,
|
||||
providing either the prefix or the stream. All other public ascii tracing
|
||||
@@ -910,23 +910,23 @@ Ascii Tracing Device Helper Methods
|
||||
|
||||
::
|
||||
|
||||
void EnableAsciiIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnableAsciiIpv4(std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
void EnableAsciiIpv4(Ptr<OutputStreamWrapper> stream, Ptr<Ipv4> ipv4, uint32_t interface);
|
||||
|
||||
void EnableAsciiIpv4 (std::string prefix, std::string ipv4Name, uint32_t interface);
|
||||
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, std::string ipv4Name, uint32_t interface);
|
||||
void EnableAsciiIpv4(std::string prefix, std::string ipv4Name, uint32_t interface);
|
||||
void EnableAsciiIpv4(Ptr<OutputStreamWrapper> stream, std::string ipv4Name, uint32_t interface);
|
||||
|
||||
void EnableAsciiIpv4 (std::string prefix, Ipv4InterfaceContainer c);
|
||||
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, Ipv4InterfaceContainer c);
|
||||
void EnableAsciiIpv4(std::string prefix, Ipv4InterfaceContainer c);
|
||||
void EnableAsciiIpv4(Ptr<OutputStreamWrapper> stream, Ipv4InterfaceContainer c);
|
||||
|
||||
void EnableAsciiIpv4 (std::string prefix, NodeContainer n);
|
||||
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, NodeContainer n);
|
||||
void EnableAsciiIpv4(std::string prefix, NodeContainer n);
|
||||
void EnableAsciiIpv4(Ptr<OutputStreamWrapper> stream, NodeContainer n);
|
||||
|
||||
void EnableAsciiIpv4 (std::string prefix, uint32_t nodeid, uint32_t deviceid);
|
||||
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, uint32_t nodeid, uint32_t interface);
|
||||
void EnableAsciiIpv4(std::string prefix, uint32_t nodeid, uint32_t deviceid);
|
||||
void EnableAsciiIpv4(Ptr<OutputStreamWrapper> stream, uint32_t nodeid, uint32_t interface);
|
||||
|
||||
void EnableAsciiIpv4All (std::string prefix);
|
||||
void EnableAsciiIpv4All (Ptr<OutputStreamWrapper> stream);
|
||||
void EnableAsciiIpv4All(std::string prefix);
|
||||
void EnableAsciiIpv4All(Ptr<OutputStreamWrapper> stream);
|
||||
|
||||
You are encouraged to peruse the Doxygen for class ``PcapAndAsciiHelperForIpv4``
|
||||
to find the details of these methods; but to summarize ...
|
||||
@@ -945,7 +945,7 @@ protocol/interface pair by providing a ``Ptr<Ipv4>`` and an ``interface`` to an
|
||||
|
||||
Ptr<Ipv4> ipv4;
|
||||
...
|
||||
helper.EnableAsciiIpv4 ("prefix", ipv4, 1);
|
||||
helper.EnableAsciiIpv4("prefix", ipv4, 1);
|
||||
|
||||
In this case, no trace contexts are written to the ASCII trace file since they
|
||||
would be redundant. The system will pick the file name to be created using the
|
||||
@@ -957,13 +957,13 @@ traces sent to a single file, you can do that as well by using an object to
|
||||
refer to a single file. We have already something similar to this in the "cwnd"
|
||||
example above::
|
||||
|
||||
Ptr<Ipv4> protocol1 = node1->GetObject<Ipv4> ();
|
||||
Ptr<Ipv4> protocol2 = node2->GetObject<Ipv4> ();
|
||||
Ptr<Ipv4> protocol1 = node1->GetObject<Ipv4>();
|
||||
Ptr<Ipv4> protocol2 = node2->GetObject<Ipv4>();
|
||||
...
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream("trace-file-name.tr");
|
||||
...
|
||||
helper.EnableAsciiIpv4 (stream, protocol1, 1);
|
||||
helper.EnableAsciiIpv4 (stream, protocol2, 1);
|
||||
helper.EnableAsciiIpv4(stream, protocol1, 1);
|
||||
helper.EnableAsciiIpv4(stream, protocol2, 1);
|
||||
|
||||
In this case, trace contexts are written to the ASCII trace file since they are
|
||||
required to disambiguate traces from the two interfaces. Note that since the
|
||||
@@ -976,24 +976,24 @@ method. The ``Ptr<Ipv4>`` is looked up from the name string. The ``<Node>`` in
|
||||
the resulting filenames is implicit since there is a one-to-one correspondence
|
||||
between protocol instances and nodes, For example,::
|
||||
|
||||
Names::Add ("node1Ipv4" ...);
|
||||
Names::Add ("node2Ipv4" ...);
|
||||
Names::Add("node1Ipv4" ...);
|
||||
Names::Add("node2Ipv4" ...);
|
||||
...
|
||||
helper.EnableAsciiIpv4 ("prefix", "node1Ipv4", 1);
|
||||
helper.EnableAsciiIpv4 ("prefix", "node2Ipv4", 1);
|
||||
helper.EnableAsciiIpv4("prefix", "node1Ipv4", 1);
|
||||
helper.EnableAsciiIpv4("prefix", "node2Ipv4", 1);
|
||||
|
||||
This would result in two files named "prefix-nnode1Ipv4-i1.tr" and
|
||||
"prefix-nnode2Ipv4-i1.tr" with traces for each interface in the respective
|
||||
trace file. Since all of the EnableAscii functions are overloaded to take a
|
||||
stream wrapper, you can use that form as well::
|
||||
|
||||
Names::Add ("node1Ipv4" ...);
|
||||
Names::Add ("node2Ipv4" ...);
|
||||
Names::Add("node1Ipv4" ...);
|
||||
Names::Add("node2Ipv4" ...);
|
||||
...
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream("trace-file-name.tr");
|
||||
...
|
||||
helper.EnableAsciiIpv4 (stream, "node1Ipv4", 1);
|
||||
helper.EnableAsciiIpv4 (stream, "node2Ipv4", 1);
|
||||
helper.EnableAsciiIpv4(stream, "node1Ipv4", 1);
|
||||
helper.EnableAsciiIpv4(stream, "node2Ipv4", 1);
|
||||
|
||||
This would result in a single trace file called "trace-file-name.tr" that
|
||||
contains all of the trace events for both interfaces. The events would be
|
||||
@@ -1007,14 +1007,14 @@ one-to-one correspondence between each protocol and its node. For example,::
|
||||
|
||||
NodeContainer nodes;
|
||||
...
|
||||
NetDeviceContainer devices = deviceHelper.Install (nodes);
|
||||
NetDeviceContainer devices = deviceHelper.Install(nodes);
|
||||
...
|
||||
Ipv4AddressHelper ipv4;
|
||||
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer interfaces = ipv4.Assign (devices);
|
||||
ipv4.SetBase("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer interfaces = ipv4.Assign(devices);
|
||||
...
|
||||
...
|
||||
helper.EnableAsciiIpv4 ("prefix", interfaces);
|
||||
helper.EnableAsciiIpv4("prefix", interfaces);
|
||||
|
||||
This would result in a number of ASCII trace files being created, each of which
|
||||
follows the <prefix>-n<node id>-i<interface>.tr convention. Combining all of the
|
||||
@@ -1022,15 +1022,15 @@ traces into a single file is accomplished similarly to the examples above::
|
||||
|
||||
NodeContainer nodes;
|
||||
...
|
||||
NetDeviceContainer devices = deviceHelper.Install (nodes);
|
||||
NetDeviceContainer devices = deviceHelper.Install(nodes);
|
||||
...
|
||||
Ipv4AddressHelper ipv4;
|
||||
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer interfaces = ipv4.Assign (devices);
|
||||
ipv4.SetBase("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer interfaces = ipv4.Assign(devices);
|
||||
...
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
|
||||
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream("trace-file-name.tr");
|
||||
...
|
||||
helper.EnableAsciiIpv4 (stream, interfaces);
|
||||
helper.EnableAsciiIpv4(stream, interfaces);
|
||||
|
||||
You can enable ASCII tracing on a collection of protocol/interface pairs by
|
||||
providing a ``NodeContainer``. For each ``Node`` in the ``NodeContainer`` the
|
||||
@@ -1039,7 +1039,7 @@ and tracing is enabled on the resulting pairs. For example,::
|
||||
|
||||
NodeContainer n;
|
||||
...
|
||||
helper.EnableAsciiIpv4 ("prefix", n);
|
||||
helper.EnableAsciiIpv4("prefix", n);
|
||||
|
||||
This would result in a number of ASCII trace files being created, each of which
|
||||
follows the <prefix>-<node id>-<device id>.tr convention. Combining all of the
|
||||
@@ -1050,14 +1050,14 @@ this case, the node-id is translated to a ``Ptr<Node>`` and the appropriate
|
||||
protocol is looked up in the node. The resulting protocol and interface are
|
||||
used to specify the resulting trace source.::
|
||||
|
||||
helper.EnableAsciiIpv4 ("prefix", 21, 1);
|
||||
helper.EnableAsciiIpv4("prefix", 21, 1);
|
||||
|
||||
Of course, the traces can be combined into a single file as shown above.
|
||||
|
||||
Finally, you can enable ASCII tracing for all interfaces in the system, with
|
||||
associated protocol being the same type as that managed by the device helper.::
|
||||
|
||||
helper.EnableAsciiIpv4All ("prefix");
|
||||
helper.EnableAsciiIpv4All("prefix");
|
||||
|
||||
This would result in a number of ASCII trace files being created, one for
|
||||
every interface in the system related to a protocol of the type managed by the
|
||||
|
||||
@@ -55,7 +55,7 @@ closely, try running it under the `gdb debugger
|
||||
Program received signal SIGSEGV, Segmentation fault.
|
||||
0x0804aa12 in main (argc=1, argv=0xbfdfefa4)
|
||||
at ../examples/tcp-point-to-point.cc:136
|
||||
136 Ptr<Socket> localSocket = socketFactory->CreateSocket ();
|
||||
136 Ptr<Socket> localSocket = socketFactory->CreateSocket();
|
||||
(gdb) p localSocket
|
||||
$1 = {m_ptr = 0x3c5d65}
|
||||
(gdb) p socketFactory
|
||||
@@ -73,9 +73,9 @@ Let's look around line 136 of tcp-point-to-point, as gdb suggests:
|
||||
|
||||
.. sourcecode:: cpp
|
||||
|
||||
Ptr<SocketFactory> socketFactory = n2->GetObject<SocketFactory> (Tcp::iid);
|
||||
Ptr<Socket> localSocket = socketFactory->CreateSocket ();
|
||||
localSocket->Bind ();
|
||||
Ptr<SocketFactory> socketFactory = n2->GetObject<SocketFactory>(Tcp::iid);
|
||||
Ptr<Socket> localSocket = socketFactory->CreateSocket();
|
||||
localSocket->Bind();
|
||||
|
||||
The culprit here is that the return value of GetObject is not being checked and
|
||||
may be null.
|
||||
|
||||
@@ -76,7 +76,7 @@ This is all just as it was in ``first.cc``, so there is nothing new yet.
|
||||
|
||||
using namespace ns3;
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("SecondScriptExample");
|
||||
NS_LOG_COMPONENT_DEFINE("SecondScriptExample");
|
||||
|
||||
The main program begins with a slightly different twist. We use a verbose
|
||||
flag to determine whether or not the ``UdpEchoClientApplication`` and
|
||||
@@ -99,10 +99,10 @@ entirely comfortable with the following code at this point in the tutorial.
|
||||
uint32_t nCsma = 3;
|
||||
|
||||
CommandLine cmd;
|
||||
cmd.AddValue ("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
|
||||
cmd.AddValue ("verbose", "Tell echo applications to log if true", verbose);
|
||||
cmd.AddValue("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
|
||||
cmd.AddValue("verbose", "Tell echo applications to log if true", verbose);
|
||||
|
||||
cmd.Parse (argc, argv);
|
||||
cmd.Parse(argc, argv);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
@@ -119,7 +119,7 @@ done in ``first.cc``.
|
||||
::
|
||||
|
||||
NodeContainer p2pNodes;
|
||||
p2pNodes.Create (2);
|
||||
p2pNodes.Create(2);
|
||||
|
||||
Next, we declare another ``NodeContainer`` to hold the nodes that will be
|
||||
part of the bus (CSMA) network. First, we just instantiate the container
|
||||
@@ -128,8 +128,8 @@ object itself.
|
||||
::
|
||||
|
||||
NodeContainer csmaNodes;
|
||||
csmaNodes.Add (p2pNodes.Get (1));
|
||||
csmaNodes.Create (nCsma);
|
||||
csmaNodes.Add(p2pNodes.Get(1));
|
||||
csmaNodes.Create(nCsma);
|
||||
|
||||
The next line of code ``Gets`` the first node (as in having an index of one)
|
||||
from the point-to-point node container and adds it to the container of nodes
|
||||
@@ -148,11 +148,11 @@ the helper and a two millisecond delay on channels created by the helper.
|
||||
::
|
||||
|
||||
PointToPointHelper pointToPoint;
|
||||
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
|
||||
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));
|
||||
|
||||
NetDeviceContainer p2pDevices;
|
||||
p2pDevices = pointToPoint.Install (p2pNodes);
|
||||
p2pDevices = pointToPoint.Install(p2pNodes);
|
||||
|
||||
We then instantiate a ``NetDeviceContainer`` to keep track of the
|
||||
point-to-point net devices and we ``Install`` devices on the
|
||||
@@ -173,11 +173,11 @@ its native data type.
|
||||
::
|
||||
|
||||
CsmaHelper csma;
|
||||
csma.SetChannelAttribute ("DataRate", StringValue ("100Mbps"));
|
||||
csma.SetChannelAttribute ("Delay", TimeValue (NanoSeconds (6560)));
|
||||
csma.SetChannelAttribute("DataRate", StringValue("100Mbps"));
|
||||
csma.SetChannelAttribute("Delay", TimeValue(NanoSeconds(6560)));
|
||||
|
||||
NetDeviceContainer csmaDevices;
|
||||
csmaDevices = csma.Install (csmaNodes);
|
||||
csmaDevices = csma.Install(csmaNodes);
|
||||
|
||||
Just as we created a ``NetDeviceContainer`` to hold the devices created by
|
||||
the ``PointToPointHelper`` we create a ``NetDeviceContainer`` to hold
|
||||
@@ -192,8 +192,8 @@ stacks present. Just as in the ``first.cc`` script, we will use the
|
||||
::
|
||||
|
||||
InternetStackHelper stack;
|
||||
stack.Install (p2pNodes.Get (0));
|
||||
stack.Install (csmaNodes);
|
||||
stack.Install(p2pNodes.Get(0));
|
||||
stack.Install(csmaNodes);
|
||||
|
||||
Recall that we took one of the nodes from the ``p2pNodes`` container and
|
||||
added it to the ``csmaNodes`` container. Thus we only need to install
|
||||
@@ -208,9 +208,9 @@ two point-to-point devices.
|
||||
::
|
||||
|
||||
Ipv4AddressHelper address;
|
||||
address.SetBase ("10.1.1.0", "255.255.255.0");
|
||||
address.SetBase("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer p2pInterfaces;
|
||||
p2pInterfaces = address.Assign (p2pDevices);
|
||||
p2pInterfaces = address.Assign(p2pDevices);
|
||||
|
||||
Recall that we save the created interfaces in a container to make it easy to
|
||||
pull out addressing information later for use in setting up the applications.
|
||||
@@ -224,9 +224,9 @@ from network number 10.1.2.0 in this case, as seen below.
|
||||
|
||||
::
|
||||
|
||||
address.SetBase ("10.1.2.0", "255.255.255.0");
|
||||
address.SetBase("10.1.2.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer csmaInterfaces;
|
||||
csmaInterfaces = address.Assign (csmaDevices);
|
||||
csmaInterfaces = address.Assign(csmaDevices);
|
||||
|
||||
Now we have a topology built, but we need applications. This section is
|
||||
going to be fundamentally similar to the applications section of
|
||||
@@ -242,11 +242,11 @@ the constructor.
|
||||
|
||||
::
|
||||
|
||||
UdpEchoServerHelper echoServer (9);
|
||||
UdpEchoServerHelper echoServer(9);
|
||||
|
||||
ApplicationContainer serverApps = echoServer.Install (csmaNodes.Get (nCsma));
|
||||
serverApps.Start (Seconds (1.0));
|
||||
serverApps.Stop (Seconds (10.0));
|
||||
ApplicationContainer serverApps = echoServer.Install(csmaNodes.Get(nCsma));
|
||||
serverApps.Start(Seconds(1.0));
|
||||
serverApps.Stop(Seconds(10.0));
|
||||
|
||||
Recall that the ``csmaNodes NodeContainer`` contains one of the
|
||||
nodes created for the point-to-point network and ``nCsma`` "extra" nodes.
|
||||
@@ -267,14 +267,14 @@ leftmost point-to-point node seen in the topology illustration.
|
||||
|
||||
::
|
||||
|
||||
UdpEchoClientHelper echoClient (csmaInterfaces.GetAddress (nCsma), 9);
|
||||
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
|
||||
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
|
||||
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
|
||||
UdpEchoClientHelper echoClient(csmaInterfaces.GetAddress(nCsma), 9);
|
||||
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
|
||||
echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0)));
|
||||
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
|
||||
|
||||
ApplicationContainer clientApps = echoClient.Install (p2pNodes.Get (0));
|
||||
clientApps.Start (Seconds (2.0));
|
||||
clientApps.Stop (Seconds (10.0));
|
||||
ApplicationContainer clientApps = echoClient.Install(p2pNodes.Get(0));
|
||||
clientApps.Start(Seconds(2.0));
|
||||
clientApps.Stop(Seconds(10.0));
|
||||
|
||||
Since we have actually built an internetwork here, we need some form of
|
||||
internetwork routing. |ns3| provides what we call global routing to
|
||||
@@ -292,7 +292,7 @@ is a one-liner:
|
||||
|
||||
::
|
||||
|
||||
Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
|
||||
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
|
||||
|
||||
Next we enable pcap tracing. The first line of code to enable pcap tracing
|
||||
in the point-to-point helper should be familiar to you by now. The second
|
||||
@@ -301,8 +301,8 @@ you haven't encountered yet.
|
||||
|
||||
::
|
||||
|
||||
pointToPoint.EnablePcapAll ("second");
|
||||
csma.EnablePcap ("second", csmaDevices.Get (1), true);
|
||||
pointToPoint.EnablePcapAll("second");
|
||||
csma.EnablePcap("second", csmaDevices.Get(1), true);
|
||||
|
||||
The CSMA network is a multi-point-to-point network. This means that there
|
||||
can (and are in this case) multiple endpoints on a shared medium. Each of
|
||||
@@ -329,8 +329,8 @@ the ``first.cc`` example.
|
||||
|
||||
::
|
||||
|
||||
Simulator::Run ();
|
||||
Simulator::Destroy ();
|
||||
Simulator::Run();
|
||||
Simulator::Destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -564,9 +564,9 @@ number and device number as parameters. Go ahead and replace the
|
||||
|
||||
::
|
||||
|
||||
pointToPoint.EnablePcap ("second", p2pNodes.Get (0)->GetId (), 0);
|
||||
csma.EnablePcap ("second", csmaNodes.Get (nCsma)->GetId (), 0, false);
|
||||
csma.EnablePcap ("second", csmaNodes.Get (nCsma-1)->GetId (), 0, false);
|
||||
pointToPoint.EnablePcap("second", p2pNodes.Get(0)->GetId(), 0);
|
||||
csma.EnablePcap("second", csmaNodes.Get(nCsma)->GetId(), 0, false);
|
||||
csma.EnablePcap("second", csmaNodes.Get(nCsma-1)->GetId(), 0, false);
|
||||
|
||||
We know that we want to create a pcap file with the base name "second" and
|
||||
we also know that the device of interest in both cases is going to be zero,
|
||||
@@ -610,14 +610,14 @@ On line 110, notice the following command to enable tracing on one node
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
csma.EnablePcap ("second", csmaDevices.Get (1), true);
|
||||
csma.EnablePcap("second", csmaDevices.Get(1), true);
|
||||
|
||||
Change the index to the quantity ``nCsma``, corresponding to the last
|
||||
node in the topology-- the node that contains the echo server:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
csma.EnablePcap ("second", csmaDevices.Get (nCsma), true);
|
||||
csma.EnablePcap("second", csmaDevices.Get(nCsma), true);
|
||||
|
||||
If you build the new script and run the simulation setting ``nCsma`` to 100,
|
||||
|
||||
@@ -657,7 +657,7 @@ argument of ``false`` indicates that you would like a non-promiscuous trace:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
csma.EnablePcap ("second", csmaDevices.Get (nCsma - 1), false);
|
||||
csma.EnablePcap("second", csmaDevices.Get(nCsma - 1), false);
|
||||
|
||||
Now build and run as before:
|
||||
|
||||
@@ -872,7 +872,7 @@ component is defined. This should all be quite familiar by now.
|
||||
|
||||
using namespace ns3;
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("ThirdScriptExample");
|
||||
NS_LOG_COMPONENT_DEFINE("ThirdScriptExample");
|
||||
|
||||
The main program begins just like ``second.cc`` by adding some command line
|
||||
parameters for enabling or disabling logging components and for changing the
|
||||
@@ -885,11 +885,11 @@ number of devices created.
|
||||
uint32_t nWifi = 3;
|
||||
|
||||
CommandLine cmd;
|
||||
cmd.AddValue ("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
|
||||
cmd.AddValue ("nWifi", "Number of wifi STA devices", nWifi);
|
||||
cmd.AddValue ("verbose", "Tell echo applications to log if true", verbose);
|
||||
cmd.AddValue("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
|
||||
cmd.AddValue("nWifi", "Number of wifi STA devices", nWifi);
|
||||
cmd.AddValue("verbose", "Tell echo applications to log if true", verbose);
|
||||
|
||||
cmd.Parse (argc,argv);
|
||||
cmd.Parse(argc,argv);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
@@ -903,7 +903,7 @@ that we will connect via the point-to-point link.
|
||||
::
|
||||
|
||||
NodeContainer p2pNodes;
|
||||
p2pNodes.Create (2);
|
||||
p2pNodes.Create(2);
|
||||
|
||||
Next, we see an old friend. We instantiate a ``PointToPointHelper`` and
|
||||
set the associated default ``Attributes`` so that we create a five megabit
|
||||
@@ -914,11 +914,11 @@ on the nodes and the channel between them.
|
||||
::
|
||||
|
||||
PointToPointHelper pointToPoint;
|
||||
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
|
||||
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));
|
||||
|
||||
NetDeviceContainer p2pDevices;
|
||||
p2pDevices = pointToPoint.Install (p2pNodes);
|
||||
p2pDevices = pointToPoint.Install(p2pNodes);
|
||||
|
||||
Next, we declare another ``NodeContainer`` to hold the nodes that will be
|
||||
part of the bus (CSMA) network.
|
||||
@@ -926,8 +926,8 @@ part of the bus (CSMA) network.
|
||||
::
|
||||
|
||||
NodeContainer csmaNodes;
|
||||
csmaNodes.Add (p2pNodes.Get (1));
|
||||
csmaNodes.Create (nCsma);
|
||||
csmaNodes.Add(p2pNodes.Get(1));
|
||||
csmaNodes.Create(nCsma);
|
||||
|
||||
The next line of code ``Gets`` the first node (as in having an index of one)
|
||||
from the point-to-point node container and adds it to the container of nodes
|
||||
@@ -943,11 +943,11 @@ selected nodes.
|
||||
::
|
||||
|
||||
CsmaHelper csma;
|
||||
csma.SetChannelAttribute ("DataRate", StringValue ("100Mbps"));
|
||||
csma.SetChannelAttribute ("Delay", TimeValue (NanoSeconds (6560)));
|
||||
csma.SetChannelAttribute("DataRate", StringValue("100Mbps"));
|
||||
csma.SetChannelAttribute("Delay", TimeValue(NanoSeconds(6560)));
|
||||
|
||||
NetDeviceContainer csmaDevices;
|
||||
csmaDevices = csma.Install (csmaNodes);
|
||||
csmaDevices = csma.Install(csmaNodes);
|
||||
|
||||
Next, we are going to create the nodes that will be part of the Wi-Fi network.
|
||||
We are going to create a number of "station" nodes as specified by the
|
||||
@@ -957,8 +957,8 @@ point-to-point link as the node for the access point.
|
||||
::
|
||||
|
||||
NodeContainer wifiStaNodes;
|
||||
wifiStaNodes.Create (nWifi);
|
||||
NodeContainer wifiApNode = p2pNodes.Get (0);
|
||||
wifiStaNodes.Create(nWifi);
|
||||
NodeContainer wifiApNode = p2pNodes.Get(0);
|
||||
|
||||
The next bit of code constructs the wifi devices and the interconnection
|
||||
channel between these wifi nodes. First, we configure the PHY and channel
|
||||
@@ -966,8 +966,8 @@ helpers:
|
||||
|
||||
::
|
||||
|
||||
YansWifiChannelHelper channel = YansWifiChannelHelper::Default ();
|
||||
YansWifiPhyHelper phy = YansWifiPhyHelper::Default ();
|
||||
YansWifiChannelHelper channel = YansWifiChannelHelper::Default();
|
||||
YansWifiPhyHelper phy = YansWifiPhyHelper::Default();
|
||||
|
||||
For simplicity, this code uses the default PHY layer configuration and
|
||||
channel models which are documented in the API doxygen documentation for
|
||||
@@ -980,7 +980,7 @@ wireless medium and can communicate and interfere:
|
||||
|
||||
::
|
||||
|
||||
phy.SetChannel (channel.Create ());
|
||||
phy.SetChannel(channel.Create());
|
||||
|
||||
Once the PHY helper is configured, we can focus on the MAC layer. The
|
||||
WifiMacHelper object is used to set MAC parameters.
|
||||
@@ -992,7 +992,7 @@ the MAC layer implementation.
|
||||
::
|
||||
|
||||
WifiMacHelper mac;
|
||||
Ssid ssid = Ssid ("ns-3-ssid");
|
||||
Ssid ssid = Ssid("ns-3-ssid");
|
||||
|
||||
WifiHelper will, by default, configure
|
||||
the standard in use to be 802.11ax (known commercially as Wi-Fi 6) and configure
|
||||
@@ -1013,9 +1013,9 @@ the WifiNetDevice objects that the helper create.
|
||||
::
|
||||
|
||||
NetDeviceContainer staDevices
|
||||
mac.SetType ("ns3::StaWifiMac",
|
||||
"Ssid", SsidValue (ssid),
|
||||
"ActiveProbing", BooleanValue (false));
|
||||
mac.SetType("ns3::StaWifiMac",
|
||||
"Ssid", SsidValue(ssid),
|
||||
"ActiveProbing", BooleanValue(false));
|
||||
|
||||
In the above code, the specific kind of MAC layer that
|
||||
will be created by the helper is specified by the TypeId value
|
||||
@@ -1035,7 +1035,7 @@ create the Wi-Fi devices of these stations:
|
||||
::
|
||||
|
||||
NetDeviceContainer staDevices;
|
||||
staDevices = wifi.Install (phy, mac, wifiStaNodes);
|
||||
staDevices = wifi.Install(phy, mac, wifiStaNodes);
|
||||
|
||||
We have configured Wi-Fi for all of our STA nodes, and now we need to
|
||||
configure the AP (access point) node. We begin this process by changing
|
||||
@@ -1044,8 +1044,8 @@ requirements of the AP.
|
||||
|
||||
::
|
||||
|
||||
mac.SetType ("ns3::ApWifiMac",
|
||||
"Ssid", SsidValue (ssid));
|
||||
mac.SetType("ns3::ApWifiMac",
|
||||
"Ssid", SsidValue(ssid));
|
||||
|
||||
In this case, the ``WifiMacHelper`` is going to create MAC
|
||||
layers of the "ns3::ApWifiMac", the latter specifying that a MAC
|
||||
@@ -1057,7 +1057,7 @@ The next lines create the single AP which shares the same set of PHY-level
|
||||
::
|
||||
|
||||
NetDeviceContainer apDevices;
|
||||
apDevices = wifi.Install (phy, mac, wifiApNode);
|
||||
apDevices = wifi.Install(phy, mac, wifiApNode);
|
||||
|
||||
Now, we are going to add mobility models. We want the STA nodes to be mobile,
|
||||
wandering around inside a bounding box, and we want to make the AP node
|
||||
@@ -1069,13 +1069,13 @@ First, we instantiate a ``MobilityHelper`` object and set some
|
||||
|
||||
MobilityHelper mobility;
|
||||
|
||||
mobility.SetPositionAllocator ("ns3::GridPositionAllocator",
|
||||
"MinX", DoubleValue (0.0),
|
||||
"MinY", DoubleValue (0.0),
|
||||
"DeltaX", DoubleValue (5.0),
|
||||
"DeltaY", DoubleValue (10.0),
|
||||
"GridWidth", UintegerValue (3),
|
||||
"LayoutType", StringValue ("RowFirst"));
|
||||
mobility.SetPositionAllocator("ns3::GridPositionAllocator",
|
||||
"MinX", DoubleValue(0.0),
|
||||
"MinY", DoubleValue(0.0),
|
||||
"DeltaX", DoubleValue(5.0),
|
||||
"DeltaY", DoubleValue(10.0),
|
||||
"GridWidth", UintegerValue(3),
|
||||
"LayoutType", StringValue("RowFirst"));
|
||||
|
||||
This code tells the mobility helper to use a two-dimensional grid to initially
|
||||
place the STA nodes. Feel free to explore the Doxygen for class
|
||||
@@ -1088,15 +1088,15 @@ box.
|
||||
|
||||
::
|
||||
|
||||
mobility.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
|
||||
"Bounds", RectangleValue (Rectangle (-50, 50, -50, 50)));
|
||||
mobility.SetMobilityModel("ns3::RandomWalk2dMobilityModel",
|
||||
"Bounds", RectangleValue(Rectangle(-50, 50, -50, 50)));
|
||||
|
||||
We now tell the ``MobilityHelper`` to install the mobility models on the
|
||||
STA nodes.
|
||||
|
||||
::
|
||||
|
||||
mobility.Install (wifiStaNodes);
|
||||
mobility.Install(wifiStaNodes);
|
||||
|
||||
We want the access point to remain in a fixed position during the simulation.
|
||||
We accomplish this by setting the mobility model for this node to be the
|
||||
@@ -1104,8 +1104,8 @@ We accomplish this by setting the mobility model for this node to be the
|
||||
|
||||
::
|
||||
|
||||
mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
|
||||
mobility.Install (wifiApNode);
|
||||
mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
|
||||
mobility.Install(wifiApNode);
|
||||
|
||||
We now have our nodes, devices and channels created, and mobility models
|
||||
chosen for the Wi-Fi nodes, but we have no protocol stacks present. Just as
|
||||
@@ -1115,9 +1115,9 @@ to install these stacks.
|
||||
::
|
||||
|
||||
InternetStackHelper stack;
|
||||
stack.Install (csmaNodes);
|
||||
stack.Install (wifiApNode);
|
||||
stack.Install (wifiStaNodes);
|
||||
stack.Install(csmaNodes);
|
||||
stack.Install(wifiApNode);
|
||||
stack.Install(wifiStaNodes);
|
||||
|
||||
Just as in the ``second.cc`` example script, we are going to use the
|
||||
``Ipv4AddressHelper`` to assign IP addresses to our device interfaces.
|
||||
@@ -1130,50 +1130,50 @@ both the STA devices and the AP on the wireless network.
|
||||
|
||||
Ipv4AddressHelper address;
|
||||
|
||||
address.SetBase ("10.1.1.0", "255.255.255.0");
|
||||
address.SetBase("10.1.1.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer p2pInterfaces;
|
||||
p2pInterfaces = address.Assign (p2pDevices);
|
||||
p2pInterfaces = address.Assign(p2pDevices);
|
||||
|
||||
address.SetBase ("10.1.2.0", "255.255.255.0");
|
||||
address.SetBase("10.1.2.0", "255.255.255.0");
|
||||
Ipv4InterfaceContainer csmaInterfaces;
|
||||
csmaInterfaces = address.Assign (csmaDevices);
|
||||
csmaInterfaces = address.Assign(csmaDevices);
|
||||
|
||||
address.SetBase ("10.1.3.0", "255.255.255.0");
|
||||
address.Assign (staDevices);
|
||||
address.Assign (apDevices);
|
||||
address.SetBase("10.1.3.0", "255.255.255.0");
|
||||
address.Assign(staDevices);
|
||||
address.Assign(apDevices);
|
||||
|
||||
We put the echo server on the "rightmost" node in the illustration at the
|
||||
start of the file. We have done this before.
|
||||
|
||||
::
|
||||
|
||||
UdpEchoServerHelper echoServer (9);
|
||||
UdpEchoServerHelper echoServer(9);
|
||||
|
||||
ApplicationContainer serverApps = echoServer.Install (csmaNodes.Get (nCsma));
|
||||
serverApps.Start (Seconds (1.0));
|
||||
serverApps.Stop (Seconds (10.0));
|
||||
ApplicationContainer serverApps = echoServer.Install(csmaNodes.Get(nCsma));
|
||||
serverApps.Start(Seconds(1.0));
|
||||
serverApps.Stop(Seconds(10.0));
|
||||
|
||||
And we put the echo client on the last STA node we created, pointing it to
|
||||
the server on the CSMA network. We have also seen similar operations before.
|
||||
|
||||
::
|
||||
|
||||
UdpEchoClientHelper echoClient (csmaInterfaces.GetAddress (nCsma), 9);
|
||||
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
|
||||
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
|
||||
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
|
||||
UdpEchoClientHelper echoClient(csmaInterfaces.GetAddress(nCsma), 9);
|
||||
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
|
||||
echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0)));
|
||||
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
|
||||
|
||||
ApplicationContainer clientApps =
|
||||
echoClient.Install (wifiStaNodes.Get (nWifi - 1));
|
||||
clientApps.Start (Seconds (2.0));
|
||||
clientApps.Stop (Seconds (10.0));
|
||||
echoClient.Install(wifiStaNodes.Get(nWifi - 1));
|
||||
clientApps.Start(Seconds(2.0));
|
||||
clientApps.Stop(Seconds(10.0));
|
||||
|
||||
Since we have built an internetwork here, we need to enable internetwork routing
|
||||
just as we did in the ``second.cc`` example script.
|
||||
|
||||
::
|
||||
|
||||
Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
|
||||
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
|
||||
|
||||
One thing that can surprise some users is the fact that the simulation we just
|
||||
created will never "naturally" stop. This is because we asked the wireless
|
||||
@@ -1186,15 +1186,15 @@ loop.
|
||||
|
||||
::
|
||||
|
||||
Simulator::Stop (Seconds (10.0));
|
||||
Simulator::Stop(Seconds(10.0));
|
||||
|
||||
We create just enough tracing to cover all three networks:
|
||||
|
||||
::
|
||||
|
||||
pointToPoint.EnablePcapAll ("third");
|
||||
phy.EnablePcap ("third", apDevices.Get (0));
|
||||
csma.EnablePcap ("third", csmaDevices.Get (0), true);
|
||||
pointToPoint.EnablePcapAll("third");
|
||||
phy.EnablePcap("third", apDevices.Get(0));
|
||||
csma.EnablePcap("third", csmaDevices.Get(0), true);
|
||||
|
||||
These three lines of code will start pcap tracing on both of the point-to-point
|
||||
nodes that serves as our backbone, will start a promiscuous (monitor) mode
|
||||
@@ -1206,8 +1206,8 @@ Finally, we actually run the simulation, clean up and then exit the program.
|
||||
|
||||
::
|
||||
|
||||
Simulator::Run ();
|
||||
Simulator::Destroy ();
|
||||
Simulator::Run();
|
||||
Simulator::Destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1357,11 +1357,11 @@ following function:
|
||||
::
|
||||
|
||||
void
|
||||
CourseChange (std::string context, Ptr<const MobilityModel> model)
|
||||
CourseChange(std::string context, Ptr<const MobilityModel> model)
|
||||
{
|
||||
Vector position = model->GetPosition ();
|
||||
NS_LOG_UNCOND (context <<
|
||||
" x = " << position.x << ", y = " << position.y);
|
||||
Vector position = model->GetPosition();
|
||||
NS_LOG_UNCOND(context <<
|
||||
" x = " << position.x << ", y = " << position.y);
|
||||
}
|
||||
|
||||
This code just pulls the position information from the mobility model and
|
||||
@@ -1374,11 +1374,10 @@ script just before the ``Simulator::Run`` call.
|
||||
::
|
||||
|
||||
std::ostringstream oss;
|
||||
oss <<
|
||||
"/NodeList/" << wifiStaNodes.Get (nWifi - 1)->GetId () <<
|
||||
"/$ns3::MobilityModel/CourseChange";
|
||||
oss << "/NodeList/" << wifiStaNodes.Get(nWifi - 1)->GetId()
|
||||
<< "/$ns3::MobilityModel/CourseChange";
|
||||
|
||||
Config::Connect (oss.str (), MakeCallback (&CourseChange));
|
||||
Config::Connect(oss.str(), MakeCallback(&CourseChange));
|
||||
|
||||
What we do here is to create a string containing the tracing namespace path
|
||||
of the event to which we want to connect. First, we have to figure out which
|
||||
@@ -1535,29 +1534,29 @@ Changing from the defaults
|
||||
* The type of queue used by a NetDevice can be usually modified through the device helper::
|
||||
|
||||
NodeContainer nodes;
|
||||
nodes.Create (2);
|
||||
nodes.Create(2);
|
||||
|
||||
PointToPointHelper p2p;
|
||||
p2p.SetQueue ("ns3::DropTailQueue", "MaxSize", StringValue ("50p"));
|
||||
p2p.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("50p"));
|
||||
|
||||
NetDeviceContainer devices = p2p.Install (nodes);
|
||||
NetDeviceContainer devices = p2p.Install(nodes);
|
||||
|
||||
* The type of queue disc installed on a NetDevice can be modified through the
|
||||
traffic control helper::
|
||||
|
||||
InternetStackHelper stack;
|
||||
stack.Install (nodes);
|
||||
stack.Install(nodes);
|
||||
|
||||
TrafficControlHelper tch;
|
||||
tch.SetRootQueueDisc ("ns3::CoDelQueueDisc", "MaxSize", StringValue ("1000p"));
|
||||
tch.Install (devices);
|
||||
tch.SetRootQueueDisc("ns3::CoDelQueueDisc", "MaxSize", StringValue("1000p"));
|
||||
tch.Install(devices);
|
||||
|
||||
* BQL can be enabled on a device that supports it through the traffic control helper::
|
||||
|
||||
InternetStackHelper stack;
|
||||
stack.Install (nodes);
|
||||
stack.Install(nodes);
|
||||
|
||||
TrafficControlHelper tch;
|
||||
tch.SetRootQueueDisc ("ns3::CoDelQueueDisc", "MaxSize", StringValue ("1000p"));
|
||||
tch.SetQueueLimits ("ns3::DynamicQueueLimits", "HoldTime", StringValue ("4ms"));
|
||||
tch.Install (devices);
|
||||
tch.SetRootQueueDisc("ns3::CoDelQueueDisc", "MaxSize", StringValue("1000p"));
|
||||
tch.SetQueueLimits("ns3::DynamicQueueLimits", "HoldTime", StringValue("4ms"));
|
||||
tch.Install(devices);
|
||||
|
||||
@@ -292,7 +292,7 @@ The next line of the script is the following,
|
||||
|
||||
::
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
|
||||
NS_LOG_COMPONENT_DEFINE("FirstScriptExample");
|
||||
|
||||
We will use this statement as a convenient place to talk about our Doxygen
|
||||
documentation system. If you look at the project web site,
|
||||
@@ -334,7 +334,7 @@ The next lines of the script you will find are,
|
||||
::
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
This is just the declaration of the main function of your program (script).
|
||||
@@ -347,7 +347,7 @@ to be the default value:
|
||||
|
||||
::
|
||||
|
||||
Time::SetResolution (Time::NS);
|
||||
Time::SetResolution(Time::NS);
|
||||
|
||||
The resolution is the smallest time value that can be represented (as well as
|
||||
the smallest representable difference between two time values).
|
||||
@@ -386,7 +386,7 @@ simulation.
|
||||
::
|
||||
|
||||
NodeContainer nodes;
|
||||
nodes.Create (2);
|
||||
nodes.Create(2);
|
||||
|
||||
Let's find the documentation for the ``NodeContainer`` class before we
|
||||
continue. Another way to get into the documentation for a given class is via
|
||||
@@ -433,8 +433,8 @@ The next three lines in the script are,
|
||||
::
|
||||
|
||||
PointToPointHelper pointToPoint;
|
||||
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
|
||||
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));
|
||||
|
||||
The first line,
|
||||
|
||||
@@ -447,7 +447,7 @@ high-level perspective the next line,
|
||||
|
||||
::
|
||||
|
||||
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
|
||||
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
|
||||
|
||||
tells the ``PointToPointHelper`` object to use the value "5Mbps"
|
||||
(five megabits per second) as the "DataRate" when it creates a
|
||||
@@ -468,7 +468,7 @@ final line,
|
||||
|
||||
::
|
||||
|
||||
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
|
||||
pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));
|
||||
|
||||
tells the ``PointToPointHelper`` to use the value "2ms" (two milliseconds)
|
||||
as the value of the propagation delay of every point to point channel it
|
||||
@@ -490,7 +490,7 @@ following two lines of code,
|
||||
::
|
||||
|
||||
NetDeviceContainer devices;
|
||||
devices = pointToPoint.Install (nodes);
|
||||
devices = pointToPoint.Install(nodes);
|
||||
|
||||
will finish configuring the devices and channel. The first line declares the
|
||||
device container mentioned above and the second does the heavy lifting. The
|
||||
@@ -504,7 +504,7 @@ by the ``PointToPointHelper``, the ``Attributes`` previously set in the
|
||||
helper are used to initialize the corresponding ``Attributes`` in the
|
||||
created objects.
|
||||
|
||||
After executing the ``pointToPoint.Install (nodes)`` call we will have
|
||||
After executing the ``pointToPoint.Install(nodes)`` call we will have
|
||||
two nodes, each with an installed point-to-point net device and a single
|
||||
point-to-point channel between them. Both devices will be configured to
|
||||
transmit data at five megabits per second over the channel which has a two
|
||||
@@ -518,7 +518,7 @@ installed on our nodes. The next two lines of code will take care of that.
|
||||
::
|
||||
|
||||
InternetStackHelper stack;
|
||||
stack.Install (nodes);
|
||||
stack.Install(nodes);
|
||||
|
||||
The ``InternetStackHelper`` is a topology helper that is to internet stacks
|
||||
what the ``PointToPointHelper`` is to point-to-point net devices. The
|
||||
@@ -539,7 +539,7 @@ The next two lines of code in our example script, ``first.cc``,
|
||||
::
|
||||
|
||||
Ipv4AddressHelper address;
|
||||
address.SetBase ("10.1.1.0", "255.255.255.0");
|
||||
address.SetBase("10.1.1.0", "255.255.255.0");
|
||||
|
||||
declare an address helper object and tell it that it should begin allocating IP
|
||||
addresses from the network 10.1.1.0 using the mask 255.255.255.0 to define
|
||||
@@ -554,7 +554,7 @@ The next line of code,
|
||||
|
||||
::
|
||||
|
||||
Ipv4InterfaceContainer interfaces = address.Assign (devices);
|
||||
Ipv4InterfaceContainer interfaces = address.Assign(devices);
|
||||
|
||||
performs the actual address assignment. In |ns3| we make the
|
||||
association between an IP address and a device using an ``Ipv4Interface``
|
||||
@@ -584,11 +584,11 @@ created.
|
||||
|
||||
::
|
||||
|
||||
UdpEchoServerHelper echoServer (9);
|
||||
UdpEchoServerHelper echoServer(9);
|
||||
|
||||
ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
|
||||
serverApps.Start (Seconds (1.0));
|
||||
serverApps.Stop (Seconds (10.0));
|
||||
ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));
|
||||
serverApps.Start(Seconds(1.0));
|
||||
serverApps.Stop(Seconds(10.0));
|
||||
|
||||
The first line of code in the above snippet declares the
|
||||
``UdpEchoServerHelper``. As usual, this isn't the application itself, it
|
||||
@@ -608,7 +608,7 @@ to a node. Interestingly, the ``Install`` method takes a
|
||||
``NodeContainter`` as a parameter just as the other ``Install`` methods
|
||||
we have seen. This is actually what is passed to the method even though it
|
||||
doesn't look so in this case. There is a C++ *implicit conversion* at
|
||||
work here that takes the result of ``nodes.Get (1)`` (which returns a smart
|
||||
work here that takes the result of ``nodes.Get(1)`` (which returns a smart
|
||||
pointer to a node object --- ``Ptr<Node>``) and uses that in a constructor
|
||||
for an unnamed ``NodeContainer`` that is then passed to ``Install``.
|
||||
If you are ever at a loss to find a particular method signature in C++ code
|
||||
@@ -633,8 +633,8 @@ converted for you. The two lines,
|
||||
|
||||
::
|
||||
|
||||
serverApps.Start (Seconds (1.0));
|
||||
serverApps.Stop (Seconds (10.0));
|
||||
serverApps.Start(Seconds(1.0));
|
||||
serverApps.Stop(Seconds(10.0));
|
||||
|
||||
will cause the echo server application to ``Start`` (enable itself) at one
|
||||
second into the simulation and to ``Stop`` (disable itself) at ten seconds
|
||||
@@ -651,14 +651,14 @@ that is managed by an ``UdpEchoClientHelper``.
|
||||
|
||||
::
|
||||
|
||||
UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
|
||||
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
|
||||
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
|
||||
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
|
||||
UdpEchoClientHelper echoClient(interfaces.GetAddress(1), 9);
|
||||
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
|
||||
echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0)));
|
||||
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
|
||||
|
||||
ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
|
||||
clientApps.Start (Seconds (2.0));
|
||||
clientApps.Stop (Seconds (10.0));
|
||||
ApplicationContainer clientApps = echoClient.Install(nodes.Get(0));
|
||||
clientApps.Start(Seconds(2.0));
|
||||
clientApps.Stop(Seconds(10.0));
|
||||
|
||||
For the echo client, however, we need to set five different ``Attributes``.
|
||||
The first two ``Attributes`` are set during construction of the
|
||||
@@ -695,17 +695,17 @@ done using the global function ``Simulator::Run``.
|
||||
|
||||
::
|
||||
|
||||
Simulator::Run ();
|
||||
Simulator::Run();
|
||||
|
||||
When we previously called the methods,
|
||||
|
||||
::
|
||||
|
||||
serverApps.Start (Seconds (1.0));
|
||||
serverApps.Stop (Seconds (10.0));
|
||||
serverApps.Start(Seconds(1.0));
|
||||
serverApps.Stop(Seconds(10.0));
|
||||
...
|
||||
clientApps.Start (Seconds (2.0));
|
||||
clientApps.Stop (Seconds (10.0));
|
||||
clientApps.Start(Seconds(2.0));
|
||||
clientApps.Stop(Seconds(10.0));
|
||||
|
||||
we actually scheduled events in the simulator at 1.0 seconds, 2.0 seconds and
|
||||
two events at 10.0 seconds. When ``Simulator::Run`` is called, the system
|
||||
@@ -741,7 +741,7 @@ took care of the hard part for you. The remaining lines of our first
|
||||
|
||||
::
|
||||
|
||||
Simulator::Destroy ();
|
||||
Simulator::Destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -764,7 +764,7 @@ not) be generated.
|
||||
The simulation will stop automatically when no further events are in the
|
||||
event queue, or when a special Stop event is found. The Stop event is
|
||||
created through the
|
||||
``Simulator::Stop (stopTime);`` function.
|
||||
``Simulator::Stop(stopTime);`` function.
|
||||
|
||||
There is a typical case where ``Simulator::Stop`` is absolutely necessary
|
||||
to stop the simulation: when there is a self-sustaining event.
|
||||
@@ -791,9 +791,9 @@ in the first example program will schedule an explicit stop at 11 seconds:
|
||||
|
||||
::
|
||||
|
||||
+ Simulator::Stop (Seconds (11.0));
|
||||
Simulator::Run ();
|
||||
Simulator::Destroy ();
|
||||
+ Simulator::Stop(Seconds(11.0));
|
||||
Simulator::Run();
|
||||
Simulator::Destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ First, it has been enabled for IPv6 support with a command-line option:
|
||||
::
|
||||
|
||||
CommandLine cmd;
|
||||
cmd.AddValue ("useIpv6", "Use Ipv6", useV6);
|
||||
cmd.Parse (argc, argv);
|
||||
cmd.AddValue("useIpv6", "Use Ipv6", useV6);
|
||||
cmd.Parse(argc, argv);
|
||||
|
||||
If the user specifies ``useIpv6``, option, the program will be run
|
||||
using IPv6 instead of IPv4. The ``help`` option, available on all |ns3|
|
||||
@@ -124,40 +124,40 @@ some of the new lines of this diff:
|
||||
+ // Configure the plot. The first argument is the file name prefix
|
||||
+ // for the output files generated. The second, third, and fourth
|
||||
+ // arguments are, respectively, the plot title, x-axis, and y-axis labels
|
||||
+ plotHelper.ConfigurePlot ("seventh-packet-byte-count",
|
||||
+ "Packet Byte Count vs. Time",
|
||||
+ "Time (Seconds)",
|
||||
+ "Packet Byte Count");
|
||||
+ plotHelper.ConfigurePlot("seventh-packet-byte-count",
|
||||
+ "Packet Byte Count vs. Time",
|
||||
+ "Time(Seconds)",
|
||||
+ "Packet Byte Count");
|
||||
+
|
||||
+ // Specify the probe type, trace source path (in configuration namespace), and
|
||||
+ // probe output trace source ("OutputBytes") to plot. The fourth argument
|
||||
+ // specifies the name of the data series label on the plot. The last
|
||||
+ // argument formats the plot by specifying where the key should be placed.
|
||||
+ plotHelper.PlotProbe (probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes",
|
||||
+ "Packet Byte Count",
|
||||
+ GnuplotAggregator::KEY_BELOW);
|
||||
+ plotHelper.PlotProbe(probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes",
|
||||
+ "Packet Byte Count",
|
||||
+ GnuplotAggregator::KEY_BELOW);
|
||||
+
|
||||
+ // Use FileHelper to write out the packet byte count over time
|
||||
+ FileHelper fileHelper;
|
||||
+
|
||||
+ // Configure the file to be written, and the formatting of output data.
|
||||
+ fileHelper.ConfigureFile ("seventh-packet-byte-count",
|
||||
+ FileAggregator::FORMATTED);
|
||||
+ fileHelper.ConfigureFile("seventh-packet-byte-count",
|
||||
+ FileAggregator::FORMATTED);
|
||||
+
|
||||
+ // Set the labels for this formatted output file.
|
||||
+ fileHelper.Set2dFormat ("Time (Seconds) = %.3e\tPacket Byte Count = %.0f");
|
||||
+ fileHelper.Set2dFormat("Time (Seconds) = %.3e\tPacket Byte Count = %.0f");
|
||||
+
|
||||
+ // Specify the probe type, probe path (in configuration namespace), and
|
||||
+ // probe output trace source ("OutputBytes") to write.
|
||||
+ fileHelper.WriteProbe (probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes");
|
||||
+ fileHelper.WriteProbe(probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes");
|
||||
+
|
||||
Simulator::Stop (Seconds (20));
|
||||
Simulator::Run ();
|
||||
Simulator::Destroy ();
|
||||
Simulator::Stop(Seconds(20));
|
||||
Simulator::Run();
|
||||
Simulator::Destroy();
|
||||
|
||||
|
||||
The careful reader will have noticed, when testing the IPv6 command
|
||||
@@ -243,10 +243,10 @@ the GnuplotHelper object must be declared and configured:
|
||||
+ // Configure the plot. The first argument is the file name prefix
|
||||
+ // for the output files generated. The second, third, and fourth
|
||||
+ // arguments are, respectively, the plot title, x-axis, and y-axis labels
|
||||
+ plotHelper.ConfigurePlot ("seventh-packet-byte-count",
|
||||
+ "Packet Byte Count vs. Time",
|
||||
+ "Time (Seconds)",
|
||||
+ "Packet Byte Count");
|
||||
+ plotHelper.ConfigurePlot("seventh-packet-byte-count",
|
||||
+ "Packet Byte Count vs. Time",
|
||||
+ "Time (Seconds)",
|
||||
+ "Packet Byte Count");
|
||||
|
||||
|
||||
To this point, an empty plot has been configured. The filename prefix
|
||||
@@ -272,11 +272,11 @@ We use them here:
|
||||
+ // probe output trace source ("OutputBytes") to plot. The fourth argument
|
||||
+ // specifies the name of the data series label on the plot. The last
|
||||
+ // argument formats the plot by specifying where the key should be placed.
|
||||
+ plotHelper.PlotProbe (probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes",
|
||||
+ "Packet Byte Count",
|
||||
+ GnuplotAggregator::KEY_BELOW);
|
||||
+ plotHelper.PlotProbe(probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes",
|
||||
+ "Packet Byte Count",
|
||||
+ GnuplotAggregator::KEY_BELOW);
|
||||
|
||||
The first two arguments are the name of the probe type and the trace source path.
|
||||
These two are probably the hardest to determine when you try to use
|
||||
@@ -287,8 +287,8 @@ observe:
|
||||
|
||||
::
|
||||
|
||||
.AddTraceSource ("Tx", "Send IPv6 packet to outgoing interface.",
|
||||
MakeTraceSourceAccessor (&Ipv6L3Protocol::m_txTrace))
|
||||
.AddTraceSource("Tx", "Send IPv6 packet to outgoing interface.",
|
||||
MakeTraceSourceAccessor(&Ipv6L3Protocol::m_txTrace))
|
||||
|
||||
This says that ``Tx`` is a name for variable ``m_txTrace``, which has
|
||||
a declaration of:
|
||||
@@ -317,18 +317,18 @@ the data out of the probed Packet object:
|
||||
::
|
||||
|
||||
TypeId
|
||||
Ipv6PacketProbe::GetTypeId ()
|
||||
Ipv6PacketProbe::GetTypeId()
|
||||
{
|
||||
static TypeId tid = TypeId ("ns3::Ipv6PacketProbe")
|
||||
.SetParent<Probe> ()
|
||||
.SetGroupName ("Stats")
|
||||
.AddConstructor<Ipv6PacketProbe> ()
|
||||
.AddTraceSource ( "Output",
|
||||
"The packet plus its IPv6 object and interface that serve as the output for this probe",
|
||||
MakeTraceSourceAccessor (&Ipv6PacketProbe::m_output))
|
||||
.AddTraceSource ( "OutputBytes",
|
||||
"The number of bytes in the packet",
|
||||
MakeTraceSourceAccessor (&Ipv6PacketProbe::m_outputBytes))
|
||||
static TypeId tid = TypeId("ns3::Ipv6PacketProbe")
|
||||
.SetParent<Probe>()
|
||||
.SetGroupName("Stats")
|
||||
.AddConstructor<Ipv6PacketProbe>()
|
||||
.AddTraceSource("Output",
|
||||
"The packet plus its IPv6 object and interface that serve as the output for this probe",
|
||||
MakeTraceSourceAccessor(&Ipv6PacketProbe::m_output))
|
||||
.AddTraceSource("OutputBytes",
|
||||
"The number of bytes in the packet",
|
||||
MakeTraceSourceAccessor(&Ipv6PacketProbe::m_outputBytes))
|
||||
;
|
||||
return tid;
|
||||
}
|
||||
@@ -407,8 +407,8 @@ be seen in the filenames. Let's look at the code piece-by-piece:
|
||||
+ FileHelper fileHelper;
|
||||
+
|
||||
+ // Configure the file to be written, and the formatting of output data.
|
||||
+ fileHelper.ConfigureFile ("seventh-packet-byte-count",
|
||||
+ FileAggregator::FORMATTED);
|
||||
+ fileHelper.ConfigureFile("seventh-packet-byte-count",
|
||||
+ FileAggregator::FORMATTED);
|
||||
|
||||
The file helper file prefix is the first argument, and a format specifier
|
||||
is next.
|
||||
@@ -420,7 +420,7 @@ FORMATTED is specified) with a format string such as follows:
|
||||
|
||||
+
|
||||
+ // Set the labels for this formatted output file.
|
||||
+ fileHelper.Set2dFormat ("Time (Seconds) = %.3e\tPacket Byte Count = %.0f");
|
||||
+ fileHelper.Set2dFormat("Time (Seconds) = %.3e\tPacket Byte Count = %.0f");
|
||||
|
||||
Finally, the trace source of interest must be hooked. Again, the probeType and
|
||||
tracePath variables in this example are used, and the probe's output
|
||||
@@ -431,9 +431,9 @@ trace source "OutputBytes" is hooked:
|
||||
+
|
||||
+ // Specify the probe type, trace source path (in configuration namespace), and
|
||||
+ // probe output trace source ("OutputBytes") to write.
|
||||
+ fileHelper.WriteProbe (probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes");
|
||||
+ fileHelper.WriteProbe(probeType,
|
||||
+ tracePath,
|
||||
+ "OutputBytes");
|
||||
+
|
||||
|
||||
The wildcard fields in this trace source specifier match two trace sources.
|
||||
@@ -448,4 +448,3 @@ providing time series output has been added. The basic pattern described
|
||||
above may be replicated within the scope of support of the existing
|
||||
probes and trace sources. More capabilities including statistics
|
||||
processing will be added in future releases.
|
||||
|
||||
|
||||
@@ -822,9 +822,9 @@ use the indicated Code Wrapper macro:
|
||||
|
||||
.. sourcecode:: cpp
|
||||
|
||||
NS_BUILD_DEBUG (std::cout << "Part of an output line..." << std::flush; timer.Start ());
|
||||
DoLongInvolvedComputation ();
|
||||
NS_BUILD_DEBUG (timer.Stop (); std::cout << "Done: " << timer << std::endl;)
|
||||
NS_BUILD_DEBUG(std::cout << "Part of an output line..." << std::flush; timer.Start());
|
||||
DoLongInvolvedComputation();
|
||||
NS_BUILD_DEBUG(timer.Stop(); std::cout << "Done: " << timer << std::endl;)
|
||||
|
||||
By default ns3 puts the build artifacts in the ``build`` directory.
|
||||
You can specify a different output directory with the ``--out``
|
||||
@@ -1626,4 +1626,3 @@ The resulting text file can then be saved with any corresponding
|
||||
then
|
||||
echo "$gitDiff" >> version.txt
|
||||
fi
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -350,7 +350,7 @@ Recall that we have defined a logging component in that script:
|
||||
|
||||
::
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
|
||||
NS_LOG_COMPONENT_DEFINE("FirstScriptExample");
|
||||
|
||||
You now know that you can enable all of the logging for this component by
|
||||
setting the ``NS_LOG`` environment variable to the various levels. Let's
|
||||
@@ -363,14 +363,14 @@ Open ``scratch/myfirst.cc`` in your favorite editor and add the line,
|
||||
|
||||
::
|
||||
|
||||
NS_LOG_INFO ("Creating Topology");
|
||||
NS_LOG_INFO("Creating Topology");
|
||||
|
||||
right before the lines,
|
||||
|
||||
::
|
||||
|
||||
NodeContainer nodes;
|
||||
nodes.Create (2);
|
||||
nodes.Create(2);
|
||||
|
||||
Now build the script using ns3 and clear the ``NS_LOG`` variable to turn
|
||||
off the torrent of logging we previously enabled:
|
||||
@@ -426,12 +426,12 @@ in the following code,
|
||||
::
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
...
|
||||
|
||||
CommandLine cmd;
|
||||
cmd.Parse (argc, argv);
|
||||
cmd.Parse(argc, argv);
|
||||
|
||||
...
|
||||
}
|
||||
@@ -470,8 +470,8 @@ at the |ns3| ``Attribute`` system while walking through the
|
||||
::
|
||||
|
||||
PointToPointHelper pointToPoint;
|
||||
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
|
||||
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
|
||||
pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));
|
||||
|
||||
and mentioned that ``DataRate`` was actually an ``Attribute`` of the
|
||||
``PointToPointNetDevice``. Let's use the command line argument parser
|
||||
@@ -507,12 +507,12 @@ any ``set`` operations as in the following example,
|
||||
...
|
||||
|
||||
NodeContainer nodes;
|
||||
nodes.Create (2);
|
||||
nodes.Create(2);
|
||||
|
||||
PointToPointHelper pointToPoint;
|
||||
|
||||
NetDeviceContainer devices;
|
||||
devices = pointToPoint.Install (nodes);
|
||||
devices = pointToPoint.Install(nodes);
|
||||
|
||||
...
|
||||
|
||||
@@ -680,13 +680,13 @@ start with the following code,
|
||||
::
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
uint32_t nPackets = 1;
|
||||
|
||||
CommandLine cmd;
|
||||
cmd.AddValue("nPackets", "Number of packets to echo", nPackets);
|
||||
cmd.Parse (argc, argv);
|
||||
cmd.Parse(argc, argv);
|
||||
|
||||
...
|
||||
|
||||
@@ -696,7 +696,7 @@ instead of the constant ``1`` as is shown below.
|
||||
|
||||
::
|
||||
|
||||
echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));
|
||||
echoClient.SetAttribute("MaxPackets", UintegerValue(nPackets));
|
||||
|
||||
Now if you run the script and provide the ``--PrintHelp`` argument, you
|
||||
should see your new ``User Argument`` listed in the help display.
|
||||
@@ -778,7 +778,7 @@ from C++ programs could be used:
|
||||
|
||||
#include <iostream>
|
||||
...
|
||||
int main ()
|
||||
int main()
|
||||
{
|
||||
...
|
||||
std::cout << "The value of x is " << x << std::endl;
|
||||
@@ -838,12 +838,12 @@ generated by many scripts.
|
||||
|
||||
Let's just jump right in and add some ASCII tracing output to our
|
||||
``scratch/myfirst.cc`` script. Right before the call to
|
||||
``Simulator::Run ()``, add the following lines of code:
|
||||
``Simulator::Run()``, add the following lines of code:
|
||||
|
||||
::
|
||||
|
||||
AsciiTraceHelper ascii;
|
||||
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
|
||||
pointToPoint.EnableAsciiAll(ascii.CreateFileStream("myfirst.tr"));
|
||||
|
||||
Like in many other |ns3| idioms, this code uses a helper object to
|
||||
help create ASCII traces. The second line contains two nested method calls.
|
||||
@@ -998,7 +998,7 @@ The code used to enable pcap tracing is a one-liner.
|
||||
|
||||
::
|
||||
|
||||
pointToPoint.EnablePcapAll ("myfirst");
|
||||
pointToPoint.EnablePcapAll("myfirst");
|
||||
|
||||
Go ahead and insert this line of code after the ASCII tracing code we just
|
||||
added to ``scratch/myfirst.cc``. Notice that we only passed the string
|
||||
|
||||
Reference in New Issue
Block a user