RTSS 应用程序可以使用 NAL API 函数集(以前缀 RtNal 开头)。使用 NAL 的应用程序通常运行第 2 层协议来发送和接收数据,并且不需要使用 RT-TCP/IP 栈或套接字 API。
本章节描述创建使用 NAL 的实时应用程序所需的步骤。
创建实时应用程序
RTX64 应用程序模板为创建 RTX64 应用程序提供了一种结构。按照如下过程,使用 Microsoft Visual Studio 中的 RTX64 应用程序模板,创建 RTX64 项目。
创建 RTX64 应用项目:
- 在 Visual Studio 中创建一个新项目;
- 选择 C 或 C++ 作为模板类型;
- 选择 RTX64 应用程序(RTX64 Application)模板。该模板是一个单页对话框,显示默认的项目设置,并提供可选的应用程序设置;
- 指定以下选项:
- 对于 SDK 版本(SDK Version),选择 RTX64 4.0 或更高版本;
- 取消选择 RT-TCP/IP 支持(RT-TCP/IP Support);
- 单击完成(Finish)创建项目。这将根据上述选项生成 RTX64 解决方案和项目。
设置附加项目设置:
- 右键单击项目名称,然后从下拉菜单中选择属性(Properties);
- 添加以下属性:
在属性页面中… | 添加… | 对于这些配置… |
---|---|---|
链接器/输入(Linker/Input) | RtNal.lib | RtssRelease, RtssDebug |
添加 Include 到头文件:
将下面 include 条目添加到应用程序头文件中现有 include 下方,且在 Rtapi.h 之后:
- #include <Rtnapi.h>
- #include <RtNalApi.h>
Rebuild 应用程序:
- 选择 RtssDebug 配置并 Rebuild 应用程序。
注意:如果构建应用程序时遇到问题,将您的项目属性与 NAL 提供的 NalDataStream 示例的属性进行比较。
编写应用程序
NAL 应用程序通常:
- 获取并运行一个或多个队列;它需要单独的队列来发送和接收数据;
- 每个队列运行一个接收线程(或定时器);
- 从一个或多个线程发送数据(或定时器);
发送线程可能会等待应用程序定时器设置的事件标志来发送数据包。
注意:RTX64NAL 进程上下文中没有运行的接收线程。当没有应用程序附加到驱动队列时,所有在线路上接收到的数据包都将被丢弃。
接收和发送线程的优先级必须小于驱动程序配置的中断线程的优先级(默认为64)。发送线程优先级应小于发送完成线程的优先级(默认为 62)。
如果队列被另一个线程释放,则所有使用队列句柄的 API 调用都会失败,并返回错误代码 ERROR_INVALID_HANDLE。
注意:main 函数必须等到所有线程完成后才能退出。
注意:如果 RtNal 被封装到 RTDLL 库中,并且不被加载 RTSS 进程使用,则在退出主进程之前不得释放该库。
关于使用 NAL API 的完整信息,请参阅 RTX64 网络抽象层 (NAL) API 参考(RTX64 Network Abstraction Layer (NAL) API Reference)和 NalDataStream 示例。
典型的应用程序顺序
下面是构成一个典型应用程序的步骤。
步骤:
- 调用 RtNalInit;
- 为应用程序所需的所有队列调用 RtNalAcquireQueue ;
- 为所有获取的队列以及 RTNAL_QUEUE_CAPABILITIES 结构体准备应用程序上下文结构体;
- 启动接收线程;
- 为所有获取的队列调用 RtNalConfigureQueue;
- 启动发送线程;
- 主线程等待停止事件,或某些信号来停止应用程序并设置停止事件;
- 主线程等待所有线程完成;
- 主线程释放所有队列和其他应用程序资源,并释放分配的内存;
- 主线程返回;
关于使用 NAL API 的完整信息,请参阅 RTX64 网络抽象层 (NAL) API 参考(RTX64 Network Abstraction Layer (NAL) API Reference)和 NalDataStream 示例。
典型的接收线程
以下是一个典型的接收线程,其循环运行,直到线程被停止事件唤醒,或 API 调用之一失败并出现错误代码 ERROR_INVALID_HANDLE。
步骤:
- 等待接收并停止通知事件句柄;
- 调用 RtNalGetReceivePacketCount 获取接收计数;
- 循环调用 RtNalReceive (上一步获取到的计数次);
- 如果 RtNalReceive 失败且错误代码为 ERROR_INVALID_HANDLE,则跳出此循环和外循环;
- 如果 RtNalReceive 成功,则处理接收包;
通知事件
通知事件用来通知应用程序:已接收的数据包,已发送的数据包,链路更改和正在释放的队列。事件句柄在 RtNalAcquireQueue 结构体中返回。通常,事件由 NAL 设置,应用程序线程等待事件句柄。应用程序还可以设置停止事件,让其线程知道它想要终止。
- 应用程序不需要显式关闭通知事件句柄。它们由 RtNalReleaseQueue 中的 NAL 关闭;
- 停止事件是手动重置的事件。所有其他通知事件都是自动重置事件;
- 接收通知用于从驱动程序中断服务线程 (IST) 唤醒应用程序接收线程。如果运行的是接收定时器而不是线程,则不需要使用接收事件。注意,除非在驱动程序中禁用接收中断,否则其仍将执行。使用 RtNalEnableReceivePolling 时,不得在队列上配置接收通知;
- 发送完成通知应该配置为对(通过 RtNalTransmitEx 发送的)数据包使用发送完成回调。详细信息,参阅移植实时网络过滤器驱动程序以使用 NAL(Porting a Real-Time Network Filter Driver to Use the NAL)。
注意:应用程序不能直接使用发送完成事件句柄。
- 当应用程序收到链接更改事件时,应使用 ENIOLINKSTATUS 命令调用 RtNalIoctl 查明链接是打开还是关闭。NalDataStream 示例程序包含此功能的演示 (GetLinkStatus)。DataStream 示例程序在发送线程中等待链接更改事件;
- 停止通知始终是启用的。停止事件将在以下情况设置:队列被 RtNalReleaseQueue 调用释放,或应用程序没有释放的队列被 NAL 退出清理(包括 RtssKill 的情况)。
获取队列
- 要获取设备的友好名称,先调用 RtNalGetNumberOfQueues,再通过调用 RtNalGetQueueInfoByIndex 迭代队列,从索引 0 开始并递增。这样,就可以看到为系统配置的所有队列信息;
- 有关 DisplayAllConfiguredQueues 的使用,请参阅 NalDataStream 示例程序;
- 在调用 RtNalAcquireQueue 之前,将设备的友好名称,接收或发送队列类型,搜索方法和事件标志掩码写入 RTNAL_QUEUE_CRITERIA 结构体;
- 搜索方法允许用户使用设备名称或 PCI 总线位置。使用指定编号的设备队列,或者可用的队列,或者默认队列(接收未过滤的数据包)来接收;
- 将标志设置为 RTNAL_USE_RX_EVENT_FLAG | RTNAL_USE_LINK_EVENT_FLAG,表示接收线程中的接收队列使用接收,链接更改和停止事件接收数据包;
- 将标志设置为 RTNAL_USE_LINK_EVENT_FLAG,表示轮询定时器中的接收队列使用链接更改和停止事件接收数据包;
- 将标志设置为 RTNAL_USE_LINK_EVENT_FLAG,表示发送队列与 RtNalTransmit (不需要发送完成回调时可使用 RtNalTransmitEx)使用链路更改和停止事件发送数据包;
- 将标志设置为 RTNAL_USE_LINK_EVENT_FLAG | RTNAL_USE_TRANSMIT_COMPLETE_EVENT_FLAG,表示发送队列与 RtNalTransmit (需要发送完成回调)使用链接更改,发送完成和停止事件发送数据包;
- 使用指向结构体 RTNAL_QUEUE_CRITERIA,RTNAL_QUEUE 和RTNAL_QUEUE_EVENTS 的指针调用 RtNalAcquireQueue;
- 成功返回后,系统队列信息和关联设备将在 RTNAL_QUEUE 结构体中返回。通知事件句柄在 RTNAL_QUEUE_EVENTS 结构体中返回。返回的信息包含:
- 正确的MAC地址
- 驱动程序实例索引
- 驱动程序队列索引
- 驱动程序配置的接收数据包缓冲区的数量
- 驱动程序配置的发送数据包缓冲区的数量
- 驱动程序使用的最大数据包大小。注意,驱动程序可能会在不考虑以太网 FCS 的情况下报告最大数据包大小,但仍将 FCS 放入接收数据包缓冲区中。应用程序必须向 RtNalReceive 使用的缓冲区中添加 4 字节。例如,如果报告的最大数据包大小为 1514,则应用程序必须为 RtNalReceive 使用 1518 字节缓冲区。
配置队列以发送或接收数据
NAL 应用程序可执行以下操作使队列正常发送和接收数据:
- 准备自己的应用程序数据结构,它代表 NAL 的队列应用程序上下文
- 存储队列句柄,该句柄将在所有使用队列的 API 中调用;
- 从 RTNAL_QUEUE 和 RTNAL_QUEUE_EVENTS 结构体复制数据,或设置指向这些结构体的指针;
- 初始化 RTNAL_QUEUE_CAPABILITIES 结构体。该结构体包含指向应用程序回调函数的指针。回调来自 RtNalReceive。fpRtnDecodePacket 从 RtNalReceive 和 RtNalTransmit 回调。fpRtnReceivePacketCallback 从 RtNalReceive,RtNalReceiveWithCallback 和轮询定时器调用。
应用程序使用队列句柄,应用程序上下文指针,以及指向 RTNAL_QUEUE_CAPABILITIES 结构体的指针调用 RtNalConfigureQueue。应用程序上下文指针不能为 NULL。
编写接收线程
编写接收线程应遵循的规则:
- 通常,应用程序在配置队列之前启动接收线程;
- 接收线程应使用接收和停止事件句柄等待多个对象。调用 RtNalGetReceivePacketCount 以确定调用 RtNalReceive 的次数。或者,应用程序可以调用 RtNalReceive,直到 RtNalReceive 失败并返回错误代码 ERROR_NO_DATA;
- 从 RtNalReceive 成功返回后,接收到的数据包数据将复制到应用程序 fpRtnGetPacket 回调返回的数据包(位于 fpRtnDecodePacket 回调返回的指针处)。接收到的数据包长度将作为参数传递给 fpRtnGetPacket 。
应用程序现在可以处理收到的数据包了。
注意:
- 当接收线程(为一个队列提供服务)被停止事件唤醒,或任何 API 调用失败并返回了 ERROR_INVALID_HANDLE(队列在另一线程中释放) 错误代码时,该接收线程应返回;
- 如果 RtNalReceive 失败并显示不同的错误代码,则可能是由于驱动程序接收错误,fpRtnGetPacket 返回 NULL,应用程序缓冲区太小,或接收数据包回调返回 TRUE 以丢弃数据包。
使用接收数据包回调
以下是接收数据包回调的使用规则:
注意:RtNalE1000 驱动不支持 RtndReceiveWithCallback。
- 接收数据包回调在 RtNalConfigureQueue 的 RTNAL_QUEUE_CAPABILITIES 结构体(fpRtnReceiveCallback)中提供;
- fpRtnReceiveCallback 函数由驱动程序直接从 RtndReceive 或 RtndReceiveWithCallback 调用;
- 如果 fpRtnReceiveCallback 返回 TRUE,则不调用 fpRtnGetPacket 或 fpRtnDecodePacket 函数,且 RtNalReceive 返回 FALSE,错误代码为 ERROR_DISCARDED;
- 如果使用了 RtNalReceiveWithCallback,则需要使用 FpRtnReceiveCallback,而不是 FpRtnGetPacket 或 FpRtnDecodePacket 回调;
- 轮询计定时器由 RtNalEnableReceivePolling 调用 RtNalReceiveWithCallback 启动;
发送数据包
以下是发送数据包的规则:
- 数据包缓冲区必须是完全格式化的以太网数据包。源 MAC 地址作为 RtNal_DEVICE_INFO(函数 RtNalAcquireQueue 中 RTNAL_QUEUE 的一部分)中信息的一部分被返回。如果设备 MAC 填写了源 MAC 地址,则应用程序无需提供它;
- 应用程序使用指向(应用程序格式的)数据包结构的指针调用 RtNalTransmit。应用程序函数 fpRtnDecodePacket 通过该数据包指针回调。fpRtnDecodePacket 必须填充以下数据:
- 指向数据缓冲区的指针
- 数据包长度(以字节为单位)
- 该队列的应用程序上下文指针
- 当发生停止事件或任何 API 调用失败并出现错误代码 ERROR_INVALID_HANDLE 时,发送线程(为一个队列提供服务)应返回。
使用 RtNalTransmitEx 发送多个数据包
注意:RtNalE1000 驱动不支持 RtndTransmitEx。
RtNalTransmitEx 在一次调用中发送多个以太网帧。帧必须由 RtNalAllocateFrame 预先分配,它返回一个指向 RTNAL_FRAME 结构体的指针,其中包含帧的虚拟地址和物理地址。应用程序不得更改帧缓冲区的虚拟和物理地址。RTNAL_FRAME结构体还包含要发送的数据包的大小和其他信息。
RtNalTransmitEx 使用指向 RTNAL_FRAME 的指针数组发送多个以太网数据包。应用程序还可以提供回调,以便对发送完成线程中的每个或某些帧进行调用。对于应用程序想要接收回调的数据包,必须将指向发送完成回调的指针写入 RTNAL_FRAME 结构体。
要使发送完成回调,必须为此队列启用发送通知。当使用发送通知获取队列时,NAL 在应用程序进程的上下文中,为每个队列运行一个额外的发送完成线程。该线程在理想处理器上运行,并具有为接口配置的优先级。即使传输完成事件句柄是从 RtNalAcquireQueue 的结构体 RTNAL_QUEUE_EVENTS 中返回的,应用程序也不能直接使用它。