近期要做一个简单的无线ad hoc模型,其中路由部分采用OLSR路由协议,MAC部分需要自定义,物理层采用跳频模型。
首先,搭建一个可以通信的多节点ad hoc网络。此时可以使用modeler自带的wizard。进入方式如下。
创建出来的节点网络如下所示,可以通过拓扑下拉中的轨迹定义里,设置每个节点的移动形式。
按照上述设置完成后,需要额外设置每个节点的traffic,才能使通信成功存在。
另外注意,如果采用jammer模型,需要设置jammer的影响时间间隔,如果设置的过长会导致影响失败。
在通信成功,并可以找到方法阻止节点通信阶段之后,我进入了很长一个时间的错误学习,方式是阅读代码。这段时间进展极其缓慢。其原因在于不熟悉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的补贴字段赋值 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中有描述 } 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. 统计量函数集 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)模拟进程交互中的消息。 仿真核心通过“绑定(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,源进程创建ICI,op_ici_create() 2,源进程保存信息到ICI,op_ici_attr_set_***() 3,源进程绑定ICI,op_ici_install() 4,源进程产生事件(发送包/自中断……) 5,当事件发生并导致中断时,被中断的进程获得ICI,op_intrpt_ici() 6,被中断进程获得信息,op_ici_attr_get_***() 7,被中断进程销毁ICI,op_ici_destroy(),结束。 注意这种每次使用创建的ICI,源进程在产生事件后不应操作ICI了,取得信息及销毁ICI的工作由目的进程处理。 (二)永久的ICI 在某些情况,上一种ICI会导致一系列的对同种ICI的创建、销毁操作,这时更简单的方案是创建ICI一次并不销毁。源进程只需要每次更新ICI的信息并绑定(绑定操作是需要的,因为源进程可能使用多个ICI),然后产生事件。这时对ICI的操作过程如下: 1,SV当中定义ICI 1,源进程在初始化时创建ICI(也可以是其它时候,但是只创建一次),op_ici_create() 2,源进程保存信息到ICI,op_ici_attr_set_***() 3,源进程绑定ICI,op_ici_install() 4,源进程产生事件(发送包/自中断……) 5,当事件发生并导致中断时,被中断的进程获得ICI,op_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.定义独立的模块
填写3.进程分解
填写4.事件枚举
事件名称 | 事件描述 | 中断类型 |
帧到达 | 上层递交帧 | STRM |
超时 | 重传时钟超时 | SELF |
收到确认 | 收到帧的确认 | STRM |
链路失效 | 链路失效 | FAILURE |
链路恢复 | 链路恢复 | RECOVERY |
启动 | 进程启动 | BEGSIM |
事件响应表描述了进程模型在不同状态下对各种事件的响应行为
状态 | 事件 | 转换条件 | 行为 | 目的状态 |
初始化 | 启动 | 无 | 无 | 空闲 |
状态 | 事件 | 转换条件 | 行为 | 目的状态 |
空闲 | 帧到达 | 无 | 存储帧 发送帧 设置时钟 | 等待确认 |
链路失效 | 无 | 无 | 链路失效 |
状态 | 事件 | 转换条件 | 行为 | 目的状态 |
等待确认 | 帧到达 | 无 | 帧排队 | 等待确认 |
超时 | 无 | 复制帧 发送帧 设置时钟 | 等待确认 | |
| 收到确认 | 队列空 | 取消时钟 销毁帧复制 | 空闲 |
| 队列非空 | 取消时钟 销毁帧复制 取出队首帧 复制帧 发送帧 设置时钟 | 等待确认 | |
| 链路失效 |
|
| 等待确认且链路失效 |
状态 | 事件 | 转换条件 | 行为 | 目的状态 |
链路失效 | 帧到达 | 无 | 帧排队 | 链路失效 |
链路恢复 | 队列空 | 无 | 空闲 | |
| 队列非空 | 取出队首帧,复制帧,发送帧,设置时钟 | 等待确认
|
状态 | 事件 | 转换条件 | 行为 | 目的状态 |
等待确认且链路失效 | 帧到达 | 无 | 帧排队 | 等待确认且链路失效 |
确认超时 | 无 | 设置重发标志 | 等待确认且链路失效 | |
链路恢复 | 有重发标志 | 取消重发标志,取出队首帧,复制帧,发送帧,设置时钟 | 等待确认
| |
| 无重发标志 | 无 | 等待确认
|
Modeler进程建模学习笔记(《OPNET Modeler与网络仿真》)
状态的含义,所有状态之间是互斥和互补的,加起来形成了进程状态空间的全集(类似FPGA状态机)。当进程收到中断后,会从一个状态转移到另一个状态
强制与非强制状态
进入强制状态的仿真,仿真核心不允许其停留在该状态,而是立刻转移到下一个状态。编程时一般只填写进入代码的部分,因为进入和离开相同。
非强制状态则相反,进入非强制状态后的仿真,将停留在那个状态等到触发唤起。对OPNET来说,真正的状态为非强制状态。此时进程可以由仿真核心唤起,也可以调用op_pro_invoke()唤起。
初始状态可以是强制状态,也可以是非强制状态
每个状态都有相应的动作与其对应,对Proto—C来说,这些动作为执行代码,上半部分为进入执行代码,下半部分为离开执行代码,进入执行代码是进程进入该状态时执行的动作,离开执行代码是离开该状态时执行的动作。
初始状态根据仿真开始中断(begin simulationinterrupt)和是否强制状态分为4种情况。
(1) 一般而言,初始状态为强制状态,仿真开始中断被启用。下一个状态的进入代码不能包含和处理主流中断有关的内容。比较常见的是,下一个状态为非强制内容,比如idle,等待主流中断唤起,并在离开代码中执行。如果下一个状态为非强制且不处理主流中断,则可以和初始状态合并为一个初始非强制状态。
(2) 初始状态为强制状态,仿真开始中断被关闭。此时出发进入初始状态的是主流中断。
(3) 初始状态非强制,仿真中断开启。初始化在初始状态的进入代码里,初始状态的离开代码可以对主流中断进行处理。可能是多状态
(4) 初始状态非强制,仿真中断关闭,不常见。
转移与离开代码的关系
转移包括:源代码、目的状态、转移条件、转移执行代码。
如果源代码是强制状态,转移代码和源状态的进入、离开为一体
如果源代码是非强制状态,转移代码和离开代码一体
转移条件可以写在离开代码中,要考虑所有转移条件的可能,与状态类似,转移条件互补和互斥
使用Modeler编程概述
对一个典型的Modeler程序来说,需要进行代码编写的地方包括:进程模型中的状态变量、临时变量、头区、函数区、进入和离开代码、转移代码以及外部头文件和源文件。
状态变量类似函数里的静态变量。
临时变量在函数中定义,在进程唤起的过程中不保持原来的值。
函数的声明在头区或头文件中,相应的函数具体定义需要放在函数区或外部文件中。函数的入口需要使用FIN和FOUT。有利于OPNET在编译及运行中发生错误时进行定位。
外部文件
核心函数KP(Kernel Procedure),指的是可以被进程模型和收发信机管道阶段座位中断被调度的C/C++函数,或者普通C/C++函数灯所调用的函数。
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;
以上都是最近学到的琐碎记录,真正的初步学习完成还需要在进行了一次编程之后。希望在下个月的博客中,能够成功总结编程中遇到的问题。