当需要QualNet与外部程序通信时,可以使用外部接口。QualNet自带的GUI接口用于可视化仿真,SOCKET接口用于传输数据,还提供了interfacetutorial接口演示接口的设计与实现。以下介绍外部接口的注册与使用,并提供自定义接口步骤供参考。
1 接口注册
1.1 注册函数
~/main/external.cpp中定义函数EXTERNAL_UserFunctionRegistration,由内核在仿真开始时调用。此函数用于注册外部接口(通过调用EXTERNAL_RegisterExternalInterface函数)和由接口实现的回调函数(通过调用EXTERNAL_RegisterFunction函数)。
(1)EXTERNAL_RegisterExternalInterface:此函数向QualNet注册一个新的外部接口,并创建必要的数据结构。此函数必须在需要外部接口作为参数的任何其他函数之前调用。
EXTERNAL_Interface* EXTERNAL_RegisterExternalInterface(
EXTERNAL_InterfaceList *list,
char *name,
EXTERNAL_PerformanceParameters params,
ExternalInterfaceType type);
参数:
list:外部接口列表,一般用&node->partitionData->interfaceList。
name:外部接口名称,随意起。
params:性能参数。当前不支持任何性能参数,因此传递EXTERNAL_NONE。
type:外部接口类型,在external.h中由enum ExternalInterfaceType列出。
返回值:指向新注册外部接口的指针。
(2)EXTERNAL_RegisterFunction:该函数为外部接口注册回调函数。
void EXTERNAL_RegisterFunction(
EXTERNAL_Interface *iface,
EXTERNAL_FunctionType type,
EXTERNAL_Function function);
参数:
type:函数类型,在external.h中由enum EXTERNAL_FunctionType列出。
function:调用函数的指针,注册时该参数前用(EXTERNAL_Function)强制类型转换。
一旦注册了接口及其回调函数,QualNet就开始使用该接口。QualNet根据需要调用已注册的回调函数。
1.2 回调函数
接口开发者通过提供执行某些任务的回调函数来确定外部接口的行为。接口开发者可以有八个回调函数(在external.h中由enum ExternalInterfaceType列出)可选。外部接口可能不需要所有八个回调函数,在这种情况下,接口只需要实现和注册它需要的函数。QualNet按需要调用注册的回调函数。
回调函数有:
(1)Initialize:这是在创建节点和协议之前调用的初始化函数。此函数用于设置数据结构,并初始化仿真所需的服务。
void InterfaceInitializeFunction(
EXTERNAL_Interface *iface,
NodeInput *nodeInput)
参数:
iface:接口结构。
nodeInput:节点输入数据结构,包含配置信息。
(2)InitializeNodes:这是在创建节点和协议之后调用的初始化函数。此函数将在仿真开始之前立即调用。这个函数用于建立独立的单个节点或协议,以便与外部接口交互操作。此外,接口可能初始化用于时间管理的数据(如在外部时间和仿真时间之间建立关联)。
void InterfaceInitializeNodesFunction(
EXTERNAL_Interface *iface,
NodeInput *nodeInput)
(3)Time:调用此函数来查询外部接口的时间。
clocktype InterfaceTimeFunction(EXTERNAL_Interface *iface)
返回值:外部实体的当前时间(以纳秒为单位)。0对应于仿真的开始。
(4)SimulationHorizon:这个函数由内核调用,以查询外部接口的仿真视界。仿真视界的值控制着仿真时钟的前进。接口增加视界,表示仿真时钟可以向前移动。内核执行小于并朝向视界的事件。一旦仿真到达视界,执行一个循环,在该循环中它调用此函数和接收函数(下面解释),直到视界再次被推进以允许执行更多的事件。
void InterfaceSimulationHorizonFunction(EXTERNAL_Interface *iface)
(5)PacketDropped:当丢包时调用此函数。由于有很多地方可以丢弃数据包,因此默认情况下不包括对此函数的支持。添加对此回调的支持是界面开发人员的职责。
void InterfacePacketDroppedFunction(EXTERNAL_Interface *iface)
(6)Receive:此函数从外部接口检索信息,并将消息注入QualNet。
void InterfaceReceiveFunction(EXTERNAL_Interface *iface)
(7)Forward:此函数将信息转发回外部源。它本质上与接收函数刚好相反。
void InterfaceForwardFunction(
EXTERNAL_Interface *iface,
void *forwardData,
int forwardSize)
参数:
forwardData:指向需要转发的数据的指针。由于外部接口最初创建了该数据,因此外部接口应该知道如何解释数据。
forwardSize:转发数据的字节大小。
(8)Finalize:在仿真结束时调用此函数。用于释放内存和停止外部接口启动的服务。
void InterfaceFinalizeFunction(EXTERNAL_Interface *iface)
1.3 自定义新接口示例
第一步:在external.h中enum ExternalInterfaceType枚举中添加接口类型名EXTERNAL_MYINTERFACE(添加至倒数第二个位置)。
第二步:在external.cpp中函数EXTERNAL_UserFunctionRegistration添加注册接口及需要使用的回调函数(代码中接口名为MyInterface,配置文件中接口名为MY-INTERFACE)。
if (EXTERNAL_ConfigStringPresent(nodeInput, "MY-INTERFACE"))
{
// Register InterfaceTutorial interface
iface = EXTERNAL_RegisterExternalInterface(
list,
"MyInterface",
EXTERNAL_NONE,
EXTERNAL_MYINTERFACE);
// Register InitializeNodes function
EXTERNAL_RegisterFunction(
iface,
EXTERNAL_INITIALIZE_NODES,
(EXTERNAL_Function) MyInterfaceInitializeNodes);
// Register Receive function
EXTERNAL_RegisterFunction(
iface,
EXTERNAL_RECEIVE,
(EXTERNAL_Function) MyInterfaceReceive);
// Register Forward function
EXTERNAL_RegisterFunction(
iface,
EXTERNAL_FORWARD,
(EXTERNAL_Function) MyInterfaceForward);
// Register Finalize function
EXTERNAL_RegisterFunction(
iface,
EXTERNAL_FINALIZE,
(EXTERNAL_Function) MyInterfaceFinalize);
// Set the time management to real-time, with 0 lookahead
EXTERNAL_SetTimeManagementRealTime(
iface,
0);
// Set the minimum delay between calls
EXTERNAL_SetReceiveDelay(
iface,
100 * MILLI_SECOND);
}
第三步:在需要使用该接口的代码中,添加调用回调函数代码,接口名称使用MyInterface。
第四步:在需要使用该接口的配置场景中,在.config文件中添加“MY-INTERFACE YES”字样,以启用该接口。
1.4 实用函数
外部接口API提供了几个实用函数,简化了接口开发人员的工作。实用函数分为三类:外部接口API 实用函数;从外部接口注入流量的函数;操作系统专用的套接字实用函数。
1.4.1 外部接口API 实用函数
文件~/include/WallClock.h定义了一个C++类WallClock,该类用于跟踪仿真过程中经过的实时时间。类实现暂停、恢复和读取wall-clock的方法。其他外部接口API实用函数定义在~/include中的external.h、external_util.h和partition.h中。
(1)EXTERNAL_SetTimeManagementRealTime:此函数自动同步QualNet仿真时间和实时时间。此函数打开时间管理并指定lookahead值。在内部,这个功能注册一个用真实时间作为视界值的视界函数。lookahead值是允许QualNet在实时时间之上运行的时间量。
void EXTERNAL_SetTimeManagementRealTime(
EXTERNAL_Interface *iface,
clocktype lookahead);
参数lookahead:仿真将被允许在未来多远的时间里运行。
(2)EXTERNAL_ChangeRealTimeLookahead:此函数修改lookahead值。此函数只能在EXTERNAL_SetTimeManagementRealTime之后调用。
void EXTERNAL_ChangeRealTimeLookahead(
EXTERNAL_Interface *iface,
clocktype lookahead);
(3)EXTERNAL_QueryExternalTime:该函数使用接口的时间回调函数返回外部接口的时间。
clocktype EXTERNAL_QueryExternalTime(EXTERNAL_Interface *iface);
返回值:外部接口的时间。如果没有为接口定义时间回调函数,则该函数将返回EXTERNAL_MAX_TIME。
(4)EXTERNAL_QuerySimulationTime:此函数返回当前仿真时间。
clocktype EXTERNAL_QuerySimulationTime(EXTERNAL_Interface *iface);
返回值:当前仿真时间。
(5)EXTERNAL_QueryRealTime:此函数以QualNet时间格式返回挂钟时间(以纳秒为单位)。
clocktype EXTERNAL_QueryRealTime();
返回值:真实(挂钟)时间。
(6)EXTERNAL_QueryCPUTime:此函数返回QualNet使用的CPU时间量。接口对该函数的第一次调用返回0;随后由同一接口对该函数的调用返回自第一次调用函数以来使用的CPU时间。
clocktype EXTERNAL_QueryCPUTime(EXTERNAL_Interface *iface);
返回值:函数的第一次调用返回0;后续调用返回自第一次调用以来使用的CPU时间。
(7)EXTERNAL_Sleep:此函数使QualNet在指定的时间内睡眠。根据使用的平台,睡眠时间可能更长。
void EXTERNAL_Sleep(clocktype amount);
参数:amount:睡眠时间。
(8)EXTERNAL_SetReceiveDelay:此函数设置接收函数的两个连续调用之间的最小延迟。防止接收函数被调用得太频繁,这可能是默认的。
void EXTERNAL_SetReceiveDelay(
EXTERNAL_Interface *iface,
clocktype delay);
参数:delay:最小延迟。
(9)EXTERNAL_ForwardData:此函数不带时间戳将数据发送回外部源。转发函数接收并处理该数据。
void EXTERNAL_ForwardData(
EXTERNAL_Interface *iface,
Node *node,
void *forwardData,
int forwardSize,
EXTERNAL_ForwardData_ReceiverOpt FwdReceiverOpt = EXTERNAL_ForwardDataAssignNodeID_On);
参数:
Node:转发数据的节点。
forwardData:转发数据。
FwdReceiverOpt:选项,是否存储转发数据的接收节点。
(10)EXTERNAL_RemoteForwardData:此函数与EXTERNAL_ForwardData类似,只不过它将数据发送回位于不同分区上的外部接口。
void EXTERNAL_RemotForwardData(
EXTERNAL_Interface *iface,
Node* node,
void *forwardData,
int forwardSize,
int partitionId,
clocktype delay);
参数:
partitionId:外部接口所在的分区的标识符。
delay:数据转发的延迟。
(11)EXTERNAL_GetInterfaceByName:此函数在接口列表中搜索具有指定名称的接口。
EXTERNAL_Interface* EXTERNAL_GetInterfaceByName(
EXTERNAL_InterfaceList *list,
char *name);
参数:
list:外部接口列表。
name:接口名称。
返回值:如果找到接口,则为接口;如果未找到,则为NULL。
(12)EXTERNAL_Bootstrap:此函数在仿真初始化早期由内核调用(创建线程之前),并为外部接口代码提供一个位置来检查仿真命令行参数。
void EXTERNAL_Bootstrap(
int argc,
char * argv [],
SimulationProperties * simProps,
PartitionData * partitionData);
参数:
argc:命令行中参数个数。
argv:命令行中的参数。
simProps:分区仿真的全局属性。
partitionData:指向此分区的数据的指针。
注意:如果您希望添加新的命令行选项供外部接口使用,则必须在~/main/partition.cpp中更新函数PARTITION_ParseArgv,以包括检查新选项;否则,该函数将输出未知的选项错误,仿真器将退出。
(13)EXTERNAL_SetSimulationEndTime:该函数设置仿真的结束时间。
void EXTERNAL_SetSimulationEndTime(
EXTERNAL_Interface* iface,
clocktype endTime = 0);
参数:endTime为仿真结束的时间。如果省略此参数,则仿真将在当前时间结束。
(14)PARTITION_ClientStateSet:此函数在指定的分区中设置或替换指向指定客户端状态的指针。
void PARTITION_ClientStateSet(
PartitionData* partitionData,
const char* stateName,
void* clientState);
参数:
partitionData:指向此分区的数据的指针。
stateName:用于定位客户端状态信息的名称。
clientState:指向客户端希望存储的数据结构的指针。
(15)PARTITION_ClientStateFind:此函数在指定的分区中搜索指定客户端的状态,并返回指向该分区的指针。
void* PARTITION_ClientStateFind(
PartitionData* partitionData,
const char* stateName)
返回值:指向客户端状态的指针。如果找不到状态,则函数返回NULL。
(16)PARTITION_GetRealTimeMode:该函数检查仿真是否在实时模式下执行。
bool PARTITION_GetRealTimeMode (PartitionData * partitionData);
返回值:如果仿真在实时模式下运行,则为true;否则为false。
(17)PARTITION_SetRealTimeMode:此函数设置仿真执行模式。
void PARTITION_SetRealTimeMode(
PartitionData * partition,
bool runAsRealtime);
参数:runAsRealtime实时运行指示。
1.4.2 从外部接口注入流量的函数
~/include/external_util.h定义了一些用于外部接口的通用QualNet实用函数,包括使用UDP和TCP在应用层发送数据包的函数,创建映射、启用/禁用节点、移动节点和更改节点方向等。
EXTERNAL_SendDataAppLayerUDP: 此函数使用UDP在两个节点之间发送一个数据包。默认情况下,函数立即发送数据。默认的目标应用程序是APP_FORWARD。 APP_FORWARD只是简单将数据转发回到外部接口。
EXTERNAL_SendDataAppLayerTCP: 此函数使用TCP在两个节点之间发送数据。 第一次在一对节点之间调用此函数时建立TCP连接。默认情况下,函数立即发送数据。与EXTERNAL_SendDataAppLayerUDP可以使用UDP向任何应用程序传递数据不同,EXTERNAL_SendDataAppLayerTCP只向应用程序APP_FORWARD传递数据。 APP_FORWARD只是简单将数据转发回到外部接口。
EXTERNAL_SendDataNetworkLayer: 此函数在网络层的两个节点之间发送数据。 此函数不同于UDP和TCP发送数据函数,因为它发送IP数据包。 IP数据包的内容(例如传输层的头部或应用程序数据)必须由用户创建。数据包不传递给接口的转发函数。 默认情况下,函数立即发送数据。
EXTERNAL_CreateMapping:此函数在键和值之间创建映射。 键可以是任何类型和任何长度的,例如IP地址、MAC地址或泛型字符串。值可以是任何东西(只是一个通用指针)。 正确使用映射值是外部接口的责任。
EXTERNAL_ResolveMapping:此函数解析由函数EXTERNAL_CreateMapping创建的映射。如果存在指定键的映射,则函数返回0;在这种情况下,与键关联的值是放置在value参数中,值的大小放置在valueSize参数中。如果不存在指定键的映射,则函数返回非零值,value和valueSize参数无效。
EXTERNAL_ActivateNode: 此函数激活节点,以便它可以开始处理事件。
EXTERNAL_DectivateNode: 此函数使节点失活,从而停止处理事件。
EXTERNAL_ChangeNodePosition:此函数更改节点的位置。此函数可与任何坐标系一起工作。节点方向不变,坐标值的有效范围取决于地形数据。检查坐标值是否在适当的范围内,如果它们不在适当的范围内,坐标值将转换为适当范围内的值。
EXTERNAL_ChangeNodeOrientation: 此函数更改节点的方向。节点位置不变,检查方位/仰角值是否在适当的范围内,如果它们不在适当的范围内,则方位/仰角值被转换为适当范围内的值。
EXTERNAL_ChangeNodePositionAndOrientation: 此函数改变节点的位置和方向。 该函数使用两个坐标系。坐标值的有效范围取决于地形数据。检查位置坐标和方位角/仰角值是否在适当的范围内,如果不在适当的范围内,坐标值和方位角/仰角值被转换为适当范围内的值。
EXTERNAL_ChangeNodePositionOrientationAndVelocityAtTime: 此函数在用户指定的时间更新节点的位置、方向和速度矢量。速度矢量与节点位置类似,可拆分为三个分量,只是单位为距离/秒。速率参数也必须提供,以准确的提供速度矢量,通常用米/秒为单位。检查坐标值、方位角、仰角和速度值是否在适当的范围内,如果没有,则转换为适当的范围。
EXTERNAL_ChangeNodePositionOrientationAndVelocityAtTime: 此函数与前一函数雷同,只是不提供速率参数。
EXTERNAL_ChangeNodeVelocityAtTime: 此函数在用户指定的时间更新节点的速度矢量。速度矢量与地形坐标系使用同一距离单位/秒。速率参数也必须提供,以准确的提供速度矢量,以米/秒为单位。
EXTERNAL_ConfigStringPresent: 此函数检查配置文件中的字符串。通常,这在接口注册期间用于查看配置(.config)文件中是否包含接口名称。函数只检查字符串的存在,而不是与该字符串关联的值。
EXTERNAL_ConfigStringIsYes: 此函数检查字符串的配置文件,如果该字符串等于“YES”,则返回true。通常,在接口注册期间使用这种方法来查看接口名称是否包含 在配置文件中。
1.4.3 操作系统专用的实用套接字函数
~/include/external_socket.h包含对套接字程序员有用的函数。第一组函数用于对可变大小的数组进行操作。下一组函数实现主机到网络的字节排序,最后一组函数实现套接字代码。
1.4.3.1 用于可变大小数组操作的函数
~/include/external_socket.h定义了一个名为EXTERNAL_VarArray的结构,它是一个可变大小的数组。external_socket.h还定义了以下操作EXTERNAL_VarArray的函数。
EXTERNAL_VarArrayInit:此函数初始化一个类型为EXTERNAL_VarArray的数组,并为该数组分配内存。
EXTERNAL_VarArrayAccomodateSize:此函数将EXTERNAL_VarArray的数组的最大大小增加到至少指定的大小。
EXTERNAL_VarArrayAppendData:此函数将数据添加到EXTERNAL_VarArray类型数组的末尾。如果有必要,将增加数组的大小。
EXTERNAL_VarArrayConcatString:此函数将字符串添加到EXTERNAL_VarArray类型数组的末尾,包括终止NULL字符。该函数假定数组中的前一个数据也是一个字符串,例如,以零字符终止的几个字节的数据。如果不是这种情况,则应改用EXTERNAL_VarArrayAppendData函数。
EXTERNAL_VarArrayFree:此函数释放分配给类型EXTERNAL_VarArray的数组的所有内存。一旦数组的使用结束,必须调用此函数。
1.4.3.2 主机到网络的字节顺序
这些函数在~/include/external_socket.h中实现,将变量的字节顺序从主机反转到网络顺序,反之亦然。
EXTERNAL_hton:此函数将数据从主机字节顺序转换为网络字节顺序。
EXTERNAL_ntoh:此函数将数据从网络字节顺序转换为主机字节顺序。
1.4.3.3 外部套接字函数
~/include/external_socket.h中实现。
EXTERNAL_SocketInit:此函数初始化套接字。必须在单个套接字上的任何其他套接字API调用之前调用此函数。
EXTERNAL_SocketValid:此函数检查套接字连接是否有效,即套接字已打开且未发生错误。TRUE,如果套接字有效;FALSE,如果无效。
EXTERNAL_SocketListen:此函数侦听套接字上的连接。它接受套接字结构listenSocket和端口号port作为参数。listenSocket用于侦听传入的连接。如果创建了成功的套接字连接,则将套接字参数connectSocket分配新创建的套接字连接,该连接被设置为非阻塞模式。如果listenSocket已经在之前调用EXTERNAL_SocketListen初始化,则忽略端口参数。返回值:EXTERNAL_NoError(如果成功);错误指示(如果未成功)。
EXTERNAL_SocketConnect:此函数连接到侦听套接字。套接字设置为非阻塞模式。返回值:EXTERNAL_NoError(如果成功);错误指示(如果未成功)。
EXTERNAL_SocketSend:此功能将数据发送到已连接的套接字。发送可能会导致阻塞:如果阻塞参数为FALSE,则返回EXTERNAL_DataNotSent,不发送任何数据;如果阻塞参数是TRUE,则函数阻塞至数据能被发送。返回值:EXTERNAL_NoError(如果成功);错误指示(如果未成功)。
EXTERNAL_SocketSend:这是上述重载函数的包装器。返回值:EXTERNAL_NoError(如果成功);错误指示(如果未成功)。
EXTERNAL_SocketRecv:此函数接收连接套接字上的数据。发送可能会导致阻塞:如果阻塞参数为false,则receiveSize参数被设置为阻塞前接收的数据总量,该值可以是0和-1之间的任何值。返回值:EXTERNAL_NoError(如果成功);错误指示(如果未成功)。
EXTERNAL_SocketClose:此功能关闭套接字。必须为侦听或连接的每个套接字调用此函数。返回值:EXTERNAL_NoError(如果成功);错误指示(如果未成功)。