一、简介
本例程将仿真一个简单的包交换网络,它包括四个周边节点和一个中心节点,周边节点用来产生业务,而中心节点将这些业务转交给相应的目的节点(周边节点中的一个),网络拓扑结构如下图所示:
上面的拓扑结构包含两种类型节点模型,即周边节点和中心交换节点。本例程的目的是仿真一个周边节点发出的业务能够通过中心交换节点路由至另一个目的周边节点。
从中心交换节点中看,假设包是以随机的方式来自四个周边节点,每个包包含目的地址,目的地址可以用一个整数来表示不同的目的周边节点,中心节点接收到包后通过对目的地址的解析最后选择一个合适的发信机将包送往目的地。
二、创建新的包格式
File——>New,选择Packet Format,创建一个新的包格式。
双击,按照下图设置包域的属性:
设置完成后命名这个包格式并保存,关闭当前的编辑器。
三、创建新的链路模型
File——>New,选择 Link Model,创建一个新的链路模型
得到:
支持的链路类型按照下图进行设置:
支持的链路类型选择 ptdup,表示该链路只支持点对点双工连接。
各项属性值的设置如下图所示:
更正:上面图的数据包应该选择“PacketMFa”
ecc model是错误纠错模式,这里选择ecc_zero_err,即取消链路的纠错功能;
error model是链路的干扰模式,这里选择error_zero_err,即链路无干扰;
prodel model是传播延时计算模式,这里选择dpt_prodel,即计算点对点传播延时;
txdel model是传输延时计算模式,这里选择dpt_txdel,即计算点对点传输延时。
取消勾选 Support all packet formats 和 Support unformatted packets,包格式只支持前面自己创建的那一个。
这里还要申明一下外部函数link_delay,对于OPNET9.0以上的版本,如果不申明该外部函数的话,在编译dpt_prodel时会因为找不到该函数而出现错误。
在File——>Declared External Files下找打外部函数 link_delay,在其前面打勾然后点击 OK 即可,如下图所示:
保存该链路模型:
四、创建中心交换节点模型
在中心交换节点中,配置了四对点对点收发机,从而在物理上能够支持与四个周边节点互联互通。
中心交换节点如何实现寻址和包交换呢?每个有向包流(以某个进程模型为参考,某个包流进入该进程或者离开该进程,因此称之为有向包流)有一个唯一的索引号,这个索引号总是和某个收信机(对应进入包流)或者某个发信机(对应离开包流)唯一对应,而收信机和发信机又和某个周边节点唯一对应,因此可以直接用流索引号作为交换包的依据。当然为了增强网络的稳健性,我们也可以建立一个目的地址和流索引(可以看作是物理地址)的映射表。为了简单起见,本例程将采用前一种方法实现寻址和包交换。
File——>New,选择 Node Model,创建一个新的节点模型:
节点编辑器中各图标的含义以及我们要用到的图标:
在新建的节点模型中按照下图所示放置并命名各个对象:
接下来需要定义收发信机的模型属性,按住Shift键,依次以鼠标左键单击所有的收发信机(8个),注意不要选中包流线。选中后,在其中的一个收信机或发信机模块上单击鼠标右键,编辑其属性。
按照下图的标注依次设置数据速率为9600(与前面链路模型的设置保持一致),包格式选择前面自己创建的那一个,然后点击OK按钮即可:
更正:上面图中应该是“PacketMFa”。
在编辑完其中一个的属性之后,在Apply to selected objects前面打勾,将前面选中的8个收发信机都设置成这样,点击OK按钮:
接下来定义节点模型的界面属性,在Interfaces菜单中选择Node Interfaces,将该节点设置为固定节点,如下图所示进行设置:
然后保存该中心交换节点模型:
五、创建中心交换节点的进程模型
在中心节点模型中,处理机模块hub从收信机模块接收一个包,根据目的地址将其转发到正确的发信机模块。hub模块通过包流连接收信机和发信机。数据包到达时将产生一个中断并被hub模块接收。因为该中断是整个节点中唯一期望的中断,所以hub模块进程模型的有限状态机只需要两个状态:一个非强制的空闲状态作为事件之间的等待; 一个强制状态来包含处理数据包的代码。
File——>New,选择 Process Model,创建一个新的进程模型
1.创建状态
创建状态:
一般:idle 非强制状态 休眠状态,hub 进程接收到中断后将从休眠状态(idle 非强制状态)激活执行代码处理包(绿色的强制状态)。
2.创建状态线并编辑属性
创建转移线:
右键转移线——>Edit Attributes,在condition栏添加状态转移条件,executive属性设置为route_pk()。
注:状态之间由状态转移线相连接,实线表示无条件转移,虚线表示条件转移,其上标注的文字表示转移条件所对应的宏。单击选中需要条件转移的线,在线上右键选择 Edit Attributes 中的condition栏设置其转移条件。状态转移线默认为实线, condition栏设置上转移条件后变为虚线。
同理,另一个转移线:
状态转移线添加完成:
3.定义宏
PK_ARRVL条件判断 hub 进程接收的中断类型是否是流中断,在OPNET中以常量OPC_INTRPT_STRM表示流中断,如果进程异常的接收到其他类型的中断则状态会因为找不到转移条件导致出错,因此为idle状态创建了一个指向自身的default的转移线,即其他条件不满足则该条件满足。给idle连接一个自身转移线,将其condition属性设置为default。当仿真运行时,仿真核心将处在事件列表队首的事件变成中断,并将它发送至合适的模块,假设进程模型接收中断,并且处在idle状态,如果 ARRIVAL条件满足,则顺利转移到arrival状态,但是进程模型也可能收到异常中断,ARRIVAL条件不能满足,这时如果没有其他满足条件的转移,模块将找不到目的状态而出错,default 这个默认转移就是起着最后一道防线的作用。
点击工具栏的“HB”按钮定义宏并Ctrl+S保存:
定义宏的代码如下:
#define PK_ARRVL (op_intrpt_type () == OPC_INTRPT_STRM)
当中断到达模块时,仿真核心调用 op_intrpt_type() 函数,将其返回值与 OPNET 常量 OPC_INTRPT_STRM 相比较,如果值相等说明这个中断是由于包到达引起的,这时 ARRIVAL 条件满足。
4.定义函数
点击工具栏的函数块按钮,编写route_pk()的代码并Ctrl+S保存:
代码如下:
static void route_pk(void)
{
int dest_address;
Packet* pkptr;
FIN(route_pk()); //函数开始
pkptr = op_pk_get (op_intrpt_strm ()); //从合适的输入流中取得包
op_pk_nfd_get (pkptr, "dest_address", &dest_address); //析取包中的目的域,目的地址就是输出流索引,将目的地址的值保留在本地变量中
op_pk_send (pkptr, dest_address); //根据目的地址将数据包发送给相应的收信机
FOUT; //函数结束
}
注意:在编写函数时必须使用FIN(function begin)、FOUT(function out)、FRET(function return)等界定函数范围的标识符,而且必须使它们配对。
5.更改进程属性
接下来更改进程属性,在Interfaces菜单中选择 Process Interfaces,按照下图设置属性值:
begsim intrpt 是仿真开始中断;endsim intrpt 是仿真结束中断。
在各个过程模型的过程接口有一些属性:如 begsim intrpt、endsim intrpt、failure intrpt、regular intrpt 等,在设计模块时,这些属性的设置很关键,其是 begsim intrpt 的设置,其影响 init 的状态的 Enter 代码何时执行,如果是 enabled,那么仿真一开始,即仿真0时刻,可以对 process 进行初始化,process 被触发后,即执行 init 的 Enter 代码。如果是 disabled 的话,就不会被 kernel 触发。如果仿真结束时需要进行一些工作(如变量的收集,内存的释放等),则需要 enable endsim intrpt。regular intrpt 可用来做定时器,在process interface 设定了 intrpt interval 之后,仿真核心每隔该时间量触发一次 regular 中断。
6.保存进程
保存该进程模型:
7.编译进程
最后,点击工具栏最右边按钮进行编译进程:
如果出现以下错误:
是因为环境变量没有配置好,配置完下面两个环境变量问题即可得到解决:
添加环境变量C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include
添加环境变量C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin
8.将进程模块指定给节点模型
下面需要把该进程模块指定给中心交换节点模型,首先打开节点模型,在节点模型中的 hub 进程上右键编辑属性,将上面创建的进程模型指配给 hub 进程,如下图所示:
更正:应该是“ProcessMFa”。
以上工作完成后保存该节点模型。
六、创建周边节点模型
当周边节点生成一个包时,必须给这个包指定一个目的地址,然后将它发往中心节点。如果周边节点接收到一个包时,它必须计算该包的端对端延时。 因此周边节点必须包括一个业务生成模块、一个进程模块、一对点对点发信机模块和一对点对点收信机模块来完成这些任务。
创建节点模型
新建节点模型Node Model,周边节点模型按照下图所示放置并命名各个对象:
src模块的属性设置如下图所示:
要把packet interarrival time这个东西提升属性变成promoted,提升属性即仿真的时候可以指定这个属性值为多个值,这样就能够进行仿真对比。
接下来,需要改变收发信机的信道速率和支持的包格式,以匹配指定的链路模型。
首先定义收发机模型属性,按住Shift键,鼠标依次在周边节点模型中单击收信机和发信机,编辑其属性,过程与中心节点模型的类似,如下图所示(注意勾选Apply to selected objects):
在Interfaces菜单中选择Node Interfaces,找到支持的节点类型属性表,除了 fixed 外的节点类型对应的 Supported 属性设 置为 no,表明该节点只能作为固定节点,如下图所示进行设置:
属性重命名
属性重命名可以简化复杂的属性名称,或者扩展过于简化的名称,总之就是可以帮我们更好的理解该属性的作用或功能。
当某个属性是由底层提升得来的,它的名称就会变得很冗长而且没有意义,这时可能需要把它的名称简化。
本例程将为包到达间隔属性重新命名,属性重命名过程如下:
Interfaces——>Node Interfaces 对话框中选择 Rename/Merge…按钮:
在 Unmodified Attributes 栏中找到要更名的属性 src.Packet Interarrival Time(包到达间隔属性),然后单击图中按钮:
点击后在 Promotion Name 文本栏中中输入新的名字 source interarrival time,然后单击 OK 关闭重命名对话框:
指定一系列预定值给某个属性
可以指定一系列预定值给某个属性,这样属性的设置可以通过界面来选择, 这将给用户提供方便。
为属性指定预定值有下面几个好处:
限制属性取值的范围。
用户可以直观地根据预定值的名称来选择相应的参数。
用户不需要输入具体值,从下拉列表中选择即可。
接下来为 source interarrival time 属性指定预定值:
在 Node Interfaces 对话框中,在Attributes 栏中选择新命名的 source interarrival time 属性,然后点击右边的 Edit Properties :
这时出现 Attribute:source interarrival time 对话框,在 Symbol Map 表中,将所有 Symbol 对应的 Status 变为 suppress:
然后增加 4 个符号与属性真实值的映射项,即预定值:
然后点击“OK”即可。
隐藏属性
隐藏属性可以避免用户看到不需要设置参数的属性项,从而能够简化用户界面。
隐藏属性不会影响仿真结果。
周边节点的许多属性与仿真无关。为了避免混淆,需要隐藏这些属性,将 Attribute:source interarrival time 对话框中Attributes除了 source interarrival time 外的所有属性的 Status 改为 hidden,然后单击 OK 按钮关闭节点界面对话框,即可隐藏成功。
最后保存节点模型:
七、创建周边节点进程模型
周边节点的处理模块主要有两个功能:(1)为包分配目的地址并且发送出去。 (2)计算包的端对端延时。
为了完成以上的任务,进程模型需要设置两个状态:一个初试化 initial 状态,一个 idle 状态。
从 File 菜单中选择 New…,从弹出的菜单中选择 Process Model,单击 OK 按钮,创建进程模型。
1.创建状态
创建进程模型成功后在编辑窗口中放置两个状态:
接下来改变状态的属性:
在第一个状态上单击鼠标右键,在弹出的菜单中选择 Set name 将其改名为 init, 并且选择 Make State Forced 使其变为强制的(forced),这时状态颜色变为绿色。
将第二个状态更名为 idle,(保持它为红色的非强制 unforced 状态)。
在 init 状态中,进程模型将加载一个从 0~3 的均匀分布概率函数。
2.创建状态转移线
下一步,需要为状态创建转移线。
右键状态转移线,选择 Edit Attributes :
其他同理,最后加入状态转移的进程模型如下图:
xmt()转移执行函数产生将调用概率函数随即产生目的地址,并将其分配给来自业务生成模块的包,然后再将它发送出去。
rcv()转移执行函数作用是在接收到包是计算其端对端延时,并且将结果写入全局统计量。
3.定义宏
点击“HB”按钮输入以下代码并Ctrl+S保存:
/* 定义包流 */
#define RCV_IN_STRM 0
#define SRC_IN_STRM 1
#define XMT_OUT_STRM 0
/* 条件宏定义 */
#define SRC_ARRVL (op_intrpt_type () == OPC_INTRPT_STRM && op_intrpt_strm () == SRC_IN_STRM)
#define RCV_ARRVL (op_intrpt_type () == OPC_INTRPT_STRM && op_intrpt_strm () == RCV_IN_STRM)
RCV_IN_STRM 和 SRC_IN_STRM 对应数据包的输入流索引号,而 XMT_OUT_STRM 为输出流索引号,输入输出都是相对当前进程模块(proc)而言的,它们对应与proc模块相连的某条包流,连接关系一旦确定,它们的索引号是常数。
之所以要放在头文件中定义这些端口号,是为了修改方便而且避免混淆。
4.定义状态变量
单击编辑状态变量工具按钮“SV”定义状态变量,在状态变量对话框中输入以下内容:
单击 OK 关闭对话框。
5.创建全局统计探针
下一步,需要创建一个全局统计探针收集包的端对端延时结果。
在进程模型的 Interfaces 菜单中选择 Global Statistics(全局统计量),在弹出的窗口中按照下图所示声明一个全局统计量:
将 Stat Name 属性命名为 ETE Delay,在探针描述(Description)文本栏中输入: Calculates ETE delay by subtracting packet creation time from current simulation time. 输入完成后Ctrl+S保存描述文件,最后点击OK。
接下来,需要为进程模型中的每个状态添加入口和出口执行代码。
6.为 init 状态添加入口执行代码
首先为 init 状态添加入口执行代码:
双击 init 状态的上半部打开其入口执行代码编辑框,输入以下代码:
address_dist = op_dist_load ("uniform_int", 0, 3);
ete_gsh = op_stat_reg ("ETE Delay", OPC_STAT_INDEX_NONE, OPC_STAT_GLOBAL);
7.函数xmt()
xmt()转移执行函数当 SRC_ARRVL 条件满足时(即包从业务生成模块到达 proc 模块) 才执行。该函数在将包发送之前要为它分配一个目的地址。
在函数快“FB”中输入以下代码:
static void xmt (void)
{
Packet* pkptr;
FIN (xmt());
pkptr = op_pk_get (SRC_IN_STRM); //从包流的输入流索引号获取数据包
op_pk_nfd_set_int32 (pkptr, "dest_address", (int)op_dist_outcome (address_dist));
//通过调用均匀概率分布函数指针(address_dist,在上面的init状态下定义),产生一个随机值,并将该值设置为包的"dest_address"域
op_pk_send (pkptr, XMT_OUT_STRM); //从包流的输出流索引号将包发送出去
FOUT;
}
第一行代码从包流的输入流索引号(SRC_IN_STRM)获取数据包。第二行代码通过调用 均匀概率分布函数指针(address_dist,它在 init 状态中定义)而产生一个随机值,将该值设置为包的"dest_address"域(请参考前面的包格式定义)。
最后一句从包流的输出流索引号(XMT_OUT_STRM)将包发送出去。
8.函数rcv()
rcv()转移执行函数当 RCV_ARRVL 条件满足(即包从收信机到达 proc 模块)时执行。
主要目的是计算端对端延时并写入全局统计探针。
继续在函数快“FB”中输入以下代码:
static void rcv (void)
{
Packet* pkptr;
double ete_delay;
FIN (rcv());
pkptr = op_pk_get (RCV_IN_STRM); //获取包指针
ete_delay = op_sim_time() - op_pk_creation_time_get (pkptr); //当前仿真时间减去包的创建时间得到包的端对端延时
op_stat_write (ete_gsh, ete_delay); //将计算的延时写入矢量结果文件中
op_pk_destroy (pkptr); //销毁包
FOUT;
}
第 7 行代码获取包指针(如前所述)。第二行代码通过将当前仿真时间减去包的创建时 间得到包的端对端延时。第 9 行代码将计算的延时写入矢量结果文件中,第 10 行代码最后销毁包。
最后Ctrl+S保存,并关闭函数编辑器。
9.激活“仿真开始”中断
在Interfaces菜单中选择Process Interfaces,从Process Interfaces对话框中,将begsim intrpt 属性变改为 enabled。
10.为节点指定进程模型
保存该进程模型:
回到周边节点模型编辑下,右键proc模块并为其指定刚才创建完成的周边节点的进程模块。
八、创建网络模型
现在已经建好了底层的节点、进程和链路模型,依据层次化建模的思想,现在可以构建网络模型。
File——>New——>Project 新建网络模型。
首先在按照 Topology——>Subnets——>Create Fixed Subnets… 在项目编辑窗口中放置一个subnet的模型并给其命名,如下图所示:
双击这个子网模块进入它的内部。
打开对象面板,搜索前面自己创建的中心交换节点模块和周边节点模块,按照下图依次放置节点模块。
中心节点:
将中心节点命名为cNode后点击Close关闭对话框。
周边节点:
链路:
连接完成后需要先验证链路的连接是否正确,点击 Topology——>Verify Links,或者直接用 Ctrl+L 快捷键调出链路检查窗口,选择 Verify links,点击 OK 验证,如果线路上有叉,就说明链路不通,连接有问题,如下图所示:
我出现错误原因:为中心节点的收发机配置的包格式出错。
注意:收发信机和链路属性(包格式、数据率等)必须和链路的相应属性匹配才能够使链路连通。
最终正确链路:
九、收集统计量
接下来收集统计量。
1. 全局统计量--端到端延时
在工程窗口的空白处右键,选择Choose Individual DES Statistics,打开 Global Statistics 列表,勾选全局统计量ETE Delay(End To End Delay,端到端延时),然后单击 OK 关闭对话框,如下图所示:
这是在周边节点中的处理模块定义过的全局统计探针。
2. 局部统计量--链路利用率
在 node_0 和 cNode 间的链路上右键,选择Choose Individual DES Statistics,按照下图勾选的选择上行和下行链路的利用率:
保存项目文件。
十、配置仿真
在这个例程中,包的大小和收发信机的速率都是恒定的,因此期望端到端延时也应该固定不变。然而,如果包的产生速率足够快,就会导致部分包在发信机队列中堆积,这时包的端对端延时加大。如果包的产生速率不定,有可能造成业务突发,因此端对端延时也会受影响。为了模拟这些行为,需要配置 source interarrival time 仿真属性,下面将给它指定两个值。
配置第一个仿真
在菜单栏的 DES——>Configure/Run Discrete Event Simulation (Advanced),右键天蓝色图标选择Edit Attributes:
在弹出的窗口中按下图所示配置仿真参数:
将随机种子 Seed 设置为 21,仿真时间设为 1000 seconds。
将仿真设置文件命名为_packetEx_sim1。
接下来给 source interarrival time 属性赋值为 4,这里的数值选择就是在前面创建周边节点模型时预设的四个值,即 4,8,40,80。
选择 4 后点击“Add”,然后选择未引用的仿真属性,单击OK按钮:
在仿真设置对话框中单击Value 栏,并从下拉列表中选择4,如图所示(下拉列表的效果是因为前面给属性指定了预定值):
最后单击OK关闭仿真设置对话框。
配置第二个仿真
下面将配置另一个仿真:
复制并且粘贴(Ctrl C,Ctrl V)刚刚配置的仿真_packetEx_sim1,新的仿真配置自动命名为_packetEx_sim2。
过程同上,将新仿真的 source interarrival time 属性赋值为 40,其他的参数保持不变,如下图所示:
Ctrl+S 保存仿真配置文件,将矢量结果文件 Vector file 命名为 _packetEx_sim2。
单击OK关闭仿真设置对话框。
从File 菜单中选择Save保存仿真配置文件。
十一、运行仿真
接下来就运行仿真,点击下图中仿真按钮:
第一个错误
出现下面页面,说明出错————进程丢失:
Aborted--仿真中止
这是因为我们用的模型库已经不是标准的模型库了,我们是自己创建的模型库,所以需要修改设置:就是将设置的Network Simulation Repository值stdmod删除,变回empty。具体而言:
在project编辑器下,edit->preference,打开后搜索repositories:
输入完毕repositories后回车键开始搜索,搜索完页面:
将Network Simulation Repository值stdmod删除,变回empty即可:
此时错误修改完成。
第二个错误
修改完上一个后又出现新错误:“LINK : fatal error LNK1181: 无法打开输入文件“kernel32.lib””
无法打开文件kernel32.lib,kernel32.lib是vs自带的库文件,缺少这个库的话是因为vs的配置有问题。一般都是VC++附加库目录的问题。在文件资源管理器中查找kernel.lib文件,在环境变量lib的设置中添加了这个文件夹,解决了该问题。
此问题得到解决。
第三个错误
修改完上一个后又出现新错误:有17个无法解析的外部文件,后缀均为.obj。
问题出现在了解决上一个错误时,电脑查找到三个kernel32.lib文件,我随便找到了一个文件的位置配置了环境变量,问题出在了配置的“x64”,这个OPNET项目是32位的,引入64位的引发冲突,所以会出现此问题。
解决办法:删掉为了kernel32.lib配置的环境变量路径中的“x64”:
问题解决。
运行仿真
正确仿真完成后界面如下:
右键选择 View Results 查看仿真结果。