opnet学习初步——琐碎记录

时间:2022-01-08 05:26:00

       近期要做一个简单的无线ad hoc模型,其中路由部分采用OLSR路由协议,MAC部分需要自定义,物理层采用跳频模型。

       首先,搭建一个可以通信的多节点ad hoc网络。此时可以使用modeler自带的wizard。进入方式如下。

opnet学习初步——琐碎记录opnet学习初步——琐碎记录opnet学习初步——琐碎记录

       创建出来的节点网络如下所示,可以通过拓扑下拉中的轨迹定义里,设置每个节点的移动形式。

opnet学习初步——琐碎记录

       按照上述设置完成后,需要额外设置每个节点的traffic,才能使通信成功存在。

opnet学习初步——琐碎记录

       另外注意,如果采用jammer模型,需要设置jammer的影响时间间隔,如果设置的过长会导致影响失败。

opnet学习初步——琐碎记录

       在通信成功,并可以找到方法阻止节点通信阶段之后,我进入了很长一个时间的错误学习,方式是阅读代码。这段时间进展极其缓慢。其原因在于不熟悉OPNET的函数集。

       之后,我改变了策略,大致学习了如何在modeler中编程,及常见的函数。

       以下列出个人认为比较重要的函数,及其使用方法。

1.     创建一个指定格式的包

mac_frame_ptr = op_pk_create_fmt(“fddi_mac_fr”);//括号里面是格式的名字,可以通过pk.m文件定义,也可以采取现有的包格式,在declared里面

op_pk_nfd_set(mac_frame_ptr, “svc_class”, svc_class);

op_pk_nfd_set(mac_frame_ptr, “dest_addr”, dest_addr);

op_pk_nfd_set(mac_frame_ptr, “src_addr”, src_addr);

op_pk_nfd_set(mac_frame_ptr, “info”, pdu_ptr);//给该格式的packet的补贴字段赋值

 opnet学习初步——琐碎记录

2.     获取中断流中的包

pkptr = op_pk_get(op_intrpt_strm());//如果需要获得连续的输入流(包)。可以采用op_strm_pksize()得到队列中包的数量

op_pk_nfd_get(pkptr,”int_value”,&i);//放到i的地址

 

if (op_subq_pk_insert(0,pkptr,OPC_QPOS_HEAD) != OPC_QINS_OK)

{

op_pk_destroy(pkptr);//插入失败则销毁该包 在HELP Symbolic Constants中有描述

}

 opnet学习初步——琐碎记录

3.     队列函数集——插入与清空

subq_index = op_intrpt_code();//确定哪些子队列正在传输

if(op_subq_empty(subq_index) == OPC_FALSE)

{

pkptr = op_subq_pk_remove(subq_index,OPQ_QPOS_HEAD);

op_pk_send_quiet(pkptr,subq_index);//使用安静模式传输

}

4.     标识、拓扑和内部模型访问函数集

own_id = op_id_self();//返回类型是objid标识本进程处理器或队列的对象ID

op_ima_obj_attr_get(own_id, “service_rate”,&service_rate);//获取对象的属性,其中service rate需要预先定义

ppid = op_topo_parent(own_id);//得到特定对象父对象的对象id

op_ima_obj_attr_get(ppid, “service_rate”,&service_rate);//获取对象的属性

同理还有子id op_topo_child()

5.     中断函数集(很关键,描述4个例子)

a.     op_intrpt_schedule_self()

pkptr = op_subq_pk_access(0,OPC_QPOS_HEAD);

pk_len = op_pk_total_size_get(pkptr);

pk_svc_time = pk_len/service_rate;

op_intrpt_schedule_self(op_sim_time()+pk_svc_time, 0);

server_busy = 1;

在特定时间里面,为激活进程调度一个中断;常见的是等待ACK,此时需要采用op_ev_cancel(evh)取消进程

b.     op_intrpt_type()

type = op_intrpt_type();//获取中断类型

switch(type)

{

case(OPC_INTRPT_STRM):

break;

case(OPC_INTRPT_SELF):

break;

case(OPC_INTRPT_STAT)://一般用在MAC层判断无线信道是否空闲

break;

default:

}

c.     op_intrpt_code()返回值是int,和当前中断相关的数字代码,便于从中断中读取用户自定义的代码可以获得该中断的目的,当有几个不同目的的中断存在时,该值非常必要,例如存在多个超时自中断。

If (op_intrpt_code() == X25C_T13)

 

d.     op_intrpt_strm() 返回值是int类型,当前中断的流索引,当一个包通过op_pk_deliver()或者op_pk_send()转入,或者通过op_strm_access()读取时,该值被明确定义

if (op_intrpt_type() == OPC_INTRPT_STRM && op_intrpt_strm() == LOW_LAYER_INPUT_STREAM)

{

eth_mac_phys_pk_accept();

}

else if (op_intrpt_type() == OPC_INTRPT_STRM && op_intrpt_strm() == HIGH_LAYER_INPUT_STREAM)

{

eth_mac_llc_pk_accept();

 

}

6.     统计量函数集
if  (conn_id < FRMSC_PVC_COMM_STAT_COUNT):

frms_ete_del_lhandle = op_stat_reg(“Frame Relay PVC. Delay(sec)”, conn_id, OPC_STAT_LOCAL);//参数一是统计量,参数二是索引号,参数三是局部或全局索引

 

7.     分布函数集

主要有两个函数

op_dist_load()返回值是一个指向分布函数的指针

job_type_dist = op_dist_load(“uniform_int”,1,job_type_range)

if (job_type_dist == OPC_NIL)://抛出错误

jsd_gen_error(“unable to load job type distribution”);

   op_dist_outcome()通过调用获得分布结果,返回值是根据特定分布得到的随机值

   next_pk_arrvl_time = op_sim_time() + op_dist_outcome(int_arrival_distptr);

   if (next_pk_arrvl_time < gen_end_time || gen_end_time == FRMSC_ARRL_END_OF_SIM)

   {

       op_intrpt_schedule_self(next_pk_arrvl_time,FRMSC_FR_APPL_TRAF_GEN);

}

8.     事件与仿真函数集

op_ev_cancel()

this_event = op_ev_current();

next_event = op_ev_next_local(this_event);

while (op_ev_valid(next_event))

{

If (op_ev_type(next_event) == OPC_INTRPT_SELF && op_ev_code(next_event) == RETRANS_TIMER)

{

retrains_timer = next_event;

next_event = op_ev_next_local(retrains_timer);

op_ev_cancel(retrains_timer);

}

else

next_event = op_ev_next_local(next_event);

}

9.     接口函数集

for (i = send_window_low; i != rcv_seq; INC(i))

{

if (window[i].format & x25C_GFI_D_BIT)

{

    iciptr = op_ici_create(“x25_data”);

    op_ici_attr_set(iciptr, “src address”, chan_vars -> local_addr);

    op_ici_install(iciptr);

    op_pk_send_forced(pkptr, strm_index);

op_ici_install(OPC_NIL);

}

}

OPNET中,可以使用ICI来进行进程之间的数据传递,所谓的ICI就是Interface Control Information(接口控制信息),它是一种特殊的数据结构,类似与C语言中的结构体,格式可以用ICI Editor进行定义。在《OPNET仿真建模大解密》中说ICI应用场合有(1)模拟层间原语,就是网络各层次之间传递的不包含在数据包内的信息,如物理层单元数据指示(包含是否TDC,是否加扰等)。(2)模拟进程交互中的消息。
//----------------------------------------------------------------
以下来自OPNET帮助文档

  仿真核心通过绑定(installation机制将一个ICI与事件关联,每一个进程(processor可以有多个进程)在任意时刻只能绑定一个ICI。当进程创建时默认没有ICI绑定,通过调用KP op_ici_install()来实现绑定(需要先通过op_ici_create创建一个ICI),然后仿真核心会自动将该进程产生的事件同该ICI关联,也可以是subsequent events事实上,该ICI地址会一直同该进程产生的所有事件关联,直到被另一个ICI替换为止,为避免ICI与事件间不必要的关联,可以在事件调度结束后重置ICI(就是绑定空ICI,即op_ici_install(OPC_NIL)。但是事实上只要在接收进程不处理ICI,那不必要的关联也不会带来什么损失。

ICI是动态的,必须由进程创建,KP op_ici_create()返回ICI地址,之后的操作都通过该地址进行。

  关于ICI最重要的操作包括创建,销毁,绑定,这些决定了何时ICI与事件关联,中间还包括其它操作,如更改和提取信息。注意由于ICI并没有严格限制归哪个进程所有,所以就像包,任何进程只要能得到ICI地址,就可以进行这些操作。例如,ICI可以被创建进程销毁,也可以被接收进程销毁,类似的多个进程可以同时修改ICI,而这些修改对所有进程可见

  仿真核心对ICI的操作流程没有严格的限制,但是不用的ICI应被销毁,以避免进程间不必要的复杂的交互。ICI的生存期可以分成两种:

(一)每次使用,创建并绑定

1,源进程创建ICIop_ici_create()

2,源进程保存信息到ICIop_ici_attr_set_***()

3,源进程绑定ICIop_ici_install()

4,源进程产生事件(发送包/自中断……

5,当事件发生并导致中断时,被中断的进程获得ICIop_intrpt_ici()

6,被中断进程获得信息,op_ici_attr_get_***()

7,被中断进程销毁ICIop_ici_destroy(),结束。

  注意这种每次使用创建的ICI,源进程在产生事件后不应操作ICI了,取得信息及销毁ICI的工作由目的进程处理。

(二)永久的ICI

  在某些情况,上一种ICI会导致一系列的对同种ICI的创建、销毁操作,这时更简单的方案是创建ICI一次并不销毁。源进程只需要每次更新ICI的信息并绑定(绑定操作是需要的,因为源进程可能使用多个ICI),然后产生事件。这时对ICI的操作过程如下:

1SV当中定义ICI

1,源进程在初始化时创建ICI(也可以是其它时候,但是只创建一次),op_ici_create()

2,源进程保存信息到ICIop_ici_attr_set_***()

3,源进程绑定ICIop_ici_install()

4,源进程产生事件(发送包/自中断……

5,当事件发生并导致中断时,被中断的进程获得ICIop_intrpt_ici()

6,被中断进程获得信息,op_ici_attr_get_***()

不需要销毁,结束。

//--------------------------------------------------------------------------------------

具体操作,转载自http://hi.baidu.com/freshairjhl/item/888e5e0f437e57113a53eef3

 

比较重要的中断类型常数

OPNET中断类型常数

描述

OPC_INTRPT_FAIL

节点或链路失效中断

OPC_INTRPT_RECOVER

节点或链路恢复中断

OPC_INTRPT_PROCEDURE

程序调用中断

OPC_INTRPT_SELF

自中断

OPC_INTRPT_STRM

流中断

OPC_INTRPT_STAT

状态中断

OPC_INTRPT_REMOTE

来自外地进程的遥远中断

OPC_INTRPT_BEGSIM

仿真开始中断

OPC_INTRPT_ENDSIM

仿真结束中断

OPC_INTRPT_ACCESS

进程模块用来获取包流队列中封包的中断

OPC_INTRPT_MCAST

多播中断

OPC_INTRPT_PROCESS

 进程调用中断

 

OPNET MODELER进程建模

填写1.协议需求

a.   接收从高层来的数据

b.   通过物理层转发数据

c.    发送下一个信号前要求ACK

d.   高层的数据排队等待

e.   如果在时间截止的时候,ACK没有返回,则重传

f.    如果链路断了,当链路重建时重传

 

填写2.定义独立的模块

opnet学习初步——琐碎记录

填写3.进程分解

opnet学习初步——琐碎记录

填写4.事件枚举

opnet学习初步——琐碎记录

 

事件名称

事件描述

中断类型

帧到达

上层递交帧

STRM

超时

重传时钟超时

SELF

收到确认

收到帧的确认

STRM

链路失效

链路失效

FAILURE

链路恢复

链路恢复

RECOVERY

启动

进程启动

BEGSIM

 

事件响应表描述了进程模型在不同状态下对各种事件的响应行为

状态

事件

转换条件

行为

目的状态

初始化

启动

空闲

 

 

状态

事件

转换条件

行为

目的状态

空闲

帧到达

存储帧

发送帧

设置时钟

等待确认

链路失效

链路失效

 

 

状态

事件

转换条件

行为

目的状态

等待确认

帧到达

帧排队

等待确认

超时

复制帧

发送帧

设置时钟

等待确认

 

收到确认

队列空

取消时钟

销毁帧复制

空闲

 

队列非空

取消时钟

销毁帧复制

取出队首帧

复制帧

发送帧

设置时钟

等待确认

 

链路失效

 

 

等待确认且链路失效

 

 

状态

事件

转换条件

行为

目的状态

链路失效

帧到达

帧排队

链路失效

链路恢复

队列空

空闲

 

队列非空

取出队首帧,复制帧,发送帧,设置时钟

等待确认

 

 

 

状态

事件

转换条件

行为

目的状态

等待确认且链路失效

帧到达

帧排队

等待确认且链路失效

确认超时

设置重发标志

等待确认且链路失效

链路恢复

有重发标志

取消重发标志,取出队首帧,复制帧,发送帧,设置时钟

等待确认

 

 

无重发标志

等待确认

 

 

 

Modeler进程建模学习笔记(《OPNET Modeler与网络仿真》)

状态的含义,所有状态之间是互斥和互补的,加起来形成了进程状态空间的全集(类似FPGA状态机)。当进程收到中断后,会从一个状态转移到另一个状态

强制与非强制状态

进入强制状态的仿真,仿真核心不允许其停留在该状态,而是立刻转移到下一个状态。编程时一般只填写进入代码的部分,因为进入和离开相同。

非强制状态则相反,进入非强制状态后的仿真,将停留在那个状态等到触发唤起。对OPNET来说,真正的状态为非强制状态。此时进程可以由仿真核心唤起,也可以调用op_pro_invoke()唤起。

初始状态可以是强制状态,也可以是非强制状态

每个状态都有相应的动作与其对应,对Proto—C来说,这些动作为执行代码,上半部分为进入执行代码,下半部分为离开执行代码,进入执行代码是进程进入该状态时执行的动作,离开执行代码是离开该状态时执行的动作。

初始状态根据仿真开始中断(begin simulationinterrupt)和是否强制状态分为4种情况。

(1)     一般而言,初始状态为强制状态,仿真开始中断被启用。下一个状态的进入代码不能包含和处理主流中断有关的内容。比较常见的是,下一个状态为非强制内容,比如idle,等待主流中断唤起,并在离开代码中执行。如果下一个状态为非强制且不处理主流中断,则可以和初始状态合并为一个初始非强制状态。

(2)     初始状态为强制状态,仿真开始中断被关闭。此时出发进入初始状态的是主流中断。

(3)     初始状态非强制,仿真中断开启。初始化在初始状态的进入代码里,初始状态的离开代码可以对主流中断进行处理。可能是多状态

(4)     初始状态非强制,仿真中断关闭,不常见。

 opnet学习初步——琐碎记录

转移与离开代码的关系

转移包括:源代码、目的状态、转移条件、转移执行代码。

如果源代码是强制状态,转移代码和源状态的进入、离开为一体

如果源代码是非强制状态,转移代码和离开代码一体

转移条件可以写在离开代码中,要考虑所有转移条件的可能,与状态类似,转移条件互补和互斥

 

使用Modeler编程概述

对一个典型的Modeler程序来说,需要进行代码编写的地方包括:进程模型中的状态变量、临时变量、头区、函数区、进入和离开代码、转移代码以及外部头文件和源文件。

状态变量类似函数里的静态变量。

临时变量在函数中定义,在进程唤起的过程中不保持原来的值。

函数的声明在头区或头文件中,相应的函数具体定义需要放在函数区或外部文件中。函数的入口需要使用FINFOUT。有利于OPNET在编译及运行中发生错误时进行定位。

外部文件

 

核心函数KPKernel Procedure),指的是可以被进程模型和收发信机管道阶段座位中断被调度的C/C++函数,或者普通C/C++函数灯所调用的函数。

OPNET有自定义的数据类型

opnet学习初步——琐碎记录

动画实体(暂不描述)

布尔类型:OPC_TRUE\OPC_FALSE;声明 BOOLEAN bool

符合代码类型:OPC_COMPCODE_SUCCESS\OPC_COMPCODE_FAILURE;表示某一操作是否成功;声明Compcodecomp_status

概率分布类型:用来描述随机数与特定数值输出间的概率函数PDF对应关系;概率分布类型一般包含一个枚举表;声明 Distribution* dist_ptr

事件句柄类型(Event Handle):用来唯一表述一个待决的仿真事件,事件句柄不是简单的整形或者指针,而是数据结构;声明 Evhandle evh

统计量句柄类型(Statisticshandle):被用来确认动态生成的全局或者局部统计量,一般在stat包中的KP来注册;声明 Stathandle stat_handle

接口控制信息(ICI):与仿真中断相关的结构型数据集合,是进程间通信的手段(层与层之间传输信息);声明ICI* ici_ptr

链表类型:数据元素的集合,储存于双向链中,数据可以在任意指定位置上加入和删除;声明List* list_ptr;

对象标识(Object ID):表示某一仿真对象,由ID IMA TOPO PK函数集声明;声明Objid objid

流量导入模型:

包(Packet):描述数据封装和传输的最基本仿真实体;声明Packet* pkptr

进程句柄(Process Handle):用来唯一标识仿真过程中的每个激活的进程;pro包中的KP操作;声明Prohandle proh

以上都是最近学到的琐碎记录,真正的初步学习完成还需要在进行了一次编程之后。希望在下个月的博客中,能够成功总结编程中遇到的问题。