KRTS虚拟网络适配器和 Windows 连接
简介
在本教程中,我们将了解使用网络模块和 Windows NDIS 连接进行通信的几个用例。我们假设您已经熟悉网络模块的基本功能。
使用网络模块,可以创建多个虚拟网络适配器,这些适配器在内部与实际物理适配器或空适配器连接。此外,还可以打开与 Windows 网络堆栈连接的虚拟网络适配器。对于支持 Ethernet-over-EtherCAT (EoE) 的 EtherCAT 从站,也可以打开与数据包接口或 Windows 的连接。基于此功能,有几个用例。其中一些将在后面介绍。
创建虚拟网络适配器
如果我们想打开一个物理网络适配器卡,我们首先必须找到它。因此,我们将 KS_enumDevices 命名为 "NET"。有关详细信息,请参阅 如何查找设备 教程。
int index;
char pDeviceName[256];
// ...
ksError = KS_enumDevices("NET", index, pDeviceName, KSF_ACTIVE);
确定网络适配器的全名后,我们通过调用 KS_openNetworkAdapter 打开它。
KSHandle hAdapter;
ksError = KS_openNetworkAdapter(&hAdapter, pDeviceName, NULL, 0);
通过该调用,我们在内部创建一个物理网络适配器和一个附加到它的虚拟网络适配器。注意:每个虚拟网络适配器都有一个唯一的以太网地址。要将多个虚拟网络适配器连接到一个物理网络适配器,我们还使用 KS_openNetworkAdapterEx。通过这种方式,通过一个以太网端口将多个网络连接分组成为可能。
KSHandle hFirstAdapter;
ksError = KS_openNetworkAdapter(&hFirstAdapter, pDeviceName, NULL, 0);
KSHandle hSecondAdapter;
ksError = KS_openNetworkAdapterEx(&hSecondAdapter, hFirstAdapter, NULL, 0);
KSHandle hThirdAdapter;
ksError = KS_openNetworkAdapterEx(&hThirdAdapter, hFirstAdapter, NULL, 0);
如果要在没有物理设备的情况下连接多个虚拟网络适配器,则可以创建 null 适配器。因此,只需为 hConnection 句柄传递 null。
KSHandle hFirstAdapter;
ksError = KS_openNetworkAdapterEx(&hFirstAdapter, KS_INVALID_HANDLE, NULL, 0);
KSHandle hSecondAdapter;
ksError = KS_openNetworkAdapterEx(&hSecondAdapter, hFirstAdapter, NULL, 0);
KSHandle hThirdAdapter;
ksError = KS_openNetworkAdapterEx(&hThirdAdapter, hFirstAdapter, NULL, 0);
虚拟网卡应用场景
场景一:在没有物理设备的情况下建立 Windows 连接
也可以在 Kithara 实时环境和 Windows NDIS 网络堆栈之间创建连接。如果您有一个 Windows 控制应用程序,该应用程序旨在通过以太网连接到另一台 PC 上运行的实时系统,则这可能很有用。将其迁移到一台 PC 上装有 Windows 和 Kithara 的系统时,您可以使用此功能。
另一个优点是 Windows 应用程序可以与 Kithara 实时环境通信,而无需额外的网卡。
为了在 Windows NDIS 网络堆栈和 Kithara 实时环境之间建立连接,我们首先创建一个带有 KS_openAdapterEx 且没有连接句柄的空适配器。然后我们使用 KS_openAdapterEx 作为连接句柄,并使用标志 'KSF_PASS_THROUGH'。
KSHandle hRealtimeAdapter;
ksError = KS_openNetworkAdapterEx(&hRealtimeAdapter, KS_INVALID_HANDLE, NULL, 0);
KSHandle hWindowsAdapter;
ksError = KS_openNetworkAdapterEx(&hWindowsAdapter, hRealtimeAdapter, NULL, KSF_PASS_THROUGH);
你也可以这样写:
KSHandle hWindowsAdapter;
ksError = KS_openNetworkAdapterEx(&hWindowsAdapter、KS_INVALID、NULL、KSF_PASS_THROUGH);
KSHandle hRealtimeAdapter;
ksError = KS_openNetworkAdapterEx(&hRealtimeAdapter, hWindowsAdapter, NULL, 0);
备注:有关实现详细信息,请参阅示例"smp\NetworkUdpWindowsConnection"。
场景二:通过实时以太网适配器实现 Windows 通信的隧道
如果您只想使用一个物理网络适配器进行实时通信和 Windows 通信,则可以通过实时连接对优先级较低的帧进行隧道传输。
为此,我们首先创建一个实时网络适配器,然后使用 'KS_openNetworkAdapterEx' 创建一个到 Windows 网络堆栈的网络桥,并将实时适配器和 'KSF_PASS_THROUGH' 作为参数传递。
KSHandle hRealtimeAdapter;
ksError = KS_openNetworkAdapter( &hRealtimeAdapter, pDeviceName, NULL, 0);
KSHandle hWindowsAdapter;
ksError = KS_openNetworkAdapterEx(&hWindowsAdapter,hRealtimeAdapter,0, KSF_PASS_THROUGH);
备注:有关实现详细信息,请参阅示例 'smp\NetworkEthernetTunnel'。
场景三:使用 Kithara 功能的非实时 Windows 硬件
在某些情况下,您希望直接从 Kithara 实时环境访问的网络设备,我们可以通过 Windows 重定向通信。因此,有必要在 Windows 网络环境中创建一个网桥,将连接到 Kithara 和您要使用的设备连接起来。这甚至可以是 WLAN 适配器。
可以创建网桥。例如,在 Windows 7 上,使用"开始"→控制面板→"网络和共享中心"→更改适配器设置。标记要桥接的网络适配器,然后单击鼠标右键。然后选择 "Bridge Connections"。注意:通信通过 Windows 重定向,并非实时通信。
在示例代码中,我们通过 Windows 网络设备打开 Kithara EtherCAT 主站。首先,我们创建一个 null 适配器,其中包含到 Windows 网络堆栈的网桥。接下来,我们创建一个实时适配器,该适配器具有到 null 适配器的网桥。最后但并非最不重要的一点是,我们使用 Kithara 实时环境适配器创建一个 EtherCAT 主站。
KSHandle hWindowsAdapter;
ksError = KS_openNetworkAdapterEx(&hWindowsAdapter, null, NULL, KSF_PASS_THROUGH);
KSHandle hRealtimeAdapter;
ksError = KS_openNetworkAdapterEx(&hRealtimeAdapter, hWindowsAdapter, NULL, 0);
KSHandle hMaster;
ksError = KS_createEcatMaster(&hMaster, hRealtimeAdapter, null, null, 0);
场景四:连接到 EoE EtherCAT 从站
Ethernet-over-EtherCAT (EoE) 是 EtherCAT 工业以太网标准的邮箱协议。这样,EtherCAT 从站和 EtherCAT 主站之间的以太网连接通过 EtherCAT 通信建立隧道。例如,有像 Beckhoff EL6601 这样的从站,它有一个以太网交换机端口,或者其他有 Web 服务器来解决配置问题。
要将这些 EoE 端点连接到 Windows 网络堆栈,只需将 EtherCAT 从站句柄传递给 KS_openNetworkAdapterEx 并带有标志 'KSF_PASS_THROUGH' 即可。
KSHandle hRealtimeAdapter;
ksError = KS_openAdapter(&hRealtimeAdapter, pDeviceName, NULL, 0);
// ...
KSHandle hMaster;
ksError = KS_createEcatMaster(&hMaster, hRealtimeAdapter, ...);
KSHandle hEoESlave;
ksError = KS_createEcatSlave(&hEoESlave, ...);
// ...
KSHandle hEoeAdapter;
ksError = KS_openNetworkAdapterEx(&hEoeAdapter, hEoESlave, NULL, KSF_PASS_THROUGH);
备注:有关实现的详细信息,请参阅示例 'smp\EtherCATEoeWindowsConnection'。
项目示例
通过UD实现内核层与Windows通信
- 应用层主要代码
cpp
#include "NetworkUdpWindowsConnection.h"
#pragma pack(push, 8)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#if !(defined(_MSC_VER) && (_MSC_VER <= 1500))
#include <winsock2.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#pragma pack(pop)
#pragma comment(lib, "Ws2_32.lib")
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 注意这是Demo版本
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const char pCustomerNumber[] = "DEMO";
//--------------------------------------------------------------------------------------------------------------
// 消息接收的回调线程
//--------------------------------------------------------------------------------------------------------------
KSError __stdcall _recvThread(void* pArgs) {
KSError ksError = KS_OK;
CallBackData* pData = (CallBackData*)pArgs;
while (!pData->finished_) {
//----------------------------------------------------------------------------------------------------------
// 等待内核层事件通知
//----------------------------------------------------------------------------------------------------------
ksError = KS_waitForEvent(
pData->hEvent_,
0,
0);
if (ksError != KS_OK)
return ksError;
int length;
byte pBuf[2048];
while (KS_getPipe(pData->hPipe_, &pBuf, 2048, &length, 0) == KS_OK) {
outputTxt((char*)pBuf);
}
}
outputTxt("Thread has been finished.");
return ksError;
}
//--------------------------------------------------------------------------------------------------------------
// Ip输出
//--------------------------------------------------------------------------------------------------------------
char* ipv4ToString(char* dest, uint addr) {
addr = KS_ntohl(addr);
sprintf(dest, "%d.%d.%d.%d", addr >> 24 & 0xFF,
addr >> 16 & 0xFF,
addr >> 8 & 0xFF,
addr >> 0 & 0xFF);
return dest;
}
//--------------------------------------------------------------------------------------------------------------
// 项目入口
//--------------------------------------------------------------------------------------------------------------
void runSample() {
outputTxt("***** Kithara example program 'NetworkUdpWindowsConnection' *****");
#if defined(_MSC_VER) && (_MSC_VER <= 1500)
outputTxt("Sorry, your compiler does not support this example.");
return;
#else
KSError ksError;
ksError = KS_openDriver(
pCustomerNumber);
if (ksError) {
outputErr(ksError, "KS_openDriver", "Unable to open the driver!");
return;
}
KSSystemInformation systemInfo;
systemInfo.structSize = sizeof(KSSystemInformation);
ksError = KS_getSystemInformation(
&systemInfo,
KSF_NO_FLAGS);
if (ksError != KS_OK) {
outputErr(ksError, "KS_getSystemInformation", "Unable to get system information to distinguish bitsize !");
KS_closeDriver();
return;
}
KSHandle hKernel;
ksError = KS_loadKernel(
&hKernel,
systemInfo.isSys64Bit ?
"NetworkUdpWindowsConnection_64.dll" :
"NetworkUdpWindowsConnection_32.dll",
NULL,
NULL,
KSF_KERNEL_EXEC);
if (ksError != KS_OK) {
outputErr(ksError, "KS_loadKernel", "Unable to load DLL! Is the DLL in the search path?");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 创建共享内存
//------------------------------------------------------------------------------------------------------------
CallBackData* pAppPtr;
CallBackData* pSysPtr;
ksError = KS_createSharedMem(
(void**)&pAppPtr,
(void**)&pSysPtr,
"NetworkUdpWindowsConnectionData",
sizeof(CallBackData),
0);
if (ksError != KS_OK) {
outputErr(ksError, "KS_createSharedMem", "Failed to allocate shared memory");
KS_closeDriver();
return;
}
strcpy(pAppPtr->msg_, "Hello Windows");
//------------------------------------------------------------------------------------------------------------
// 创建一个空的网络适配器
//------------------------------------------------------------------------------------------------------------
KSHandle hAdapter;
ksError = KS_openNetworkAdapterEx(
&hAdapter,
KS_INVALID_HANDLE,
NULL,
0);
if (ksError != KS_OK) {
outputErr(ksError, "KS_openNetworkAdapterEx", "Failed to open adapter");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 配置网络适配器
//------------------------------------------------------------------------------------------------------------
KSIPConfig config;
KS_makeIPv4(&config.localAddress, 192, 168, 6, 100);
KS_makeIPv4(&config.subnetMask, 255, 255, 255, 0);
KS_makeIPv4(&config.gatewayAddress, 0, 0, 0, 0);
char ipString[16];
outputTxt("Current local IP address: ", false);
outputTxt(ipv4ToString(ipString, config.localAddress));
outputTxt("Current subnet mask: ", false);
outputTxt(ipv4ToString(ipString, config.subnetMask));
outputTxt("Current gateway address: ", false);
outputTxt(ipv4ToString(ipString, config.gatewayAddress));
outputTxt("Otherwise change the example!");
outputTxt(" ");
ksError = KS_execNetworkCommand(
hAdapter,
KS_NETWORK_SET_IP_CONFIG,
&config,
0);
if (ksError != KS_OK) {
outputErr(ksError, "KS_execNetworkCommand", "Failed to set IP config");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 基于空的网络适配器,创建一个用于和Window通信的网络适配器
//------------------------------------------------------------------------------------------------------------
KSHandle hWinAdapter;
ksError = KS_openNetworkAdapterEx(
&hWinAdapter,
hAdapter,
NULL,
KSF_PASS_THROUGH);
if (ksError != KS_OK) {
outputErr(ksError, "KS_openNetworkAdapterEx", "Failed to open adapter");
KS_closeDriver();
return;
}
waitTime(100 * ms);
//------------------------------------------------------------------------------------------------------------
// 创建一个管道,用于传输内核数据
//------------------------------------------------------------------------------------------------------------
ksError = KS_createPipe(
&pAppPtr->hPipe_,
"NetworkUdpWindowsConnectionPipe",
1500,
100,
NULL,
KSF_MESSAGE_PIPE);
if (ksError != KS_OK) {
outputErr(ksError, "KS_createPipe", "Unable to create pipe!");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 创建事件
//------------------------------------------------------------------------------------------------------------
ksError = KS_createEvent(
&pAppPtr->hEvent_,
"NetworkUdpWindowsConnectionEvent",
0);
if (ksError != KS_OK) {
outputErr(ksError, "KS_createEvent", "Unable to create finish event!");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 创建线程
//------------------------------------------------------------------------------------------------------------
ksError = KS_createThread(
_recvThread,
pAppPtr,
NULL);
if (ksError != KS_OK) {
outputErr(ksError, "KS_createThread", "Unable to create the thread!");
KS_closeDriver();
return;
}
KSHandle hCallBack;
ksError = KS_createKernelCallBack(
&hCallBack,
hKernel,
"_callBack",
pSysPtr,
KSF_DIRECT_EXEC,
0);
if (ksError != KS_OK) {
outputErr(ksError, "KS_createCallBack", "Failed to create callback");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 指定套接字地址
//------------------------------------------------------------------------------------------------------------
const ushort USER_PORT = 0xaaaa;
KSSocketAddr socketAddr;
socketAddr.family = KS_FAMILY_IPV4;
socketAddr.port = KS_htons(USER_PORT);
socketAddr.address[0] = config.localAddress;
//------------------------------------------------------------------------------------------------------------
// 打开UDP通信,注意使用的网络适配器是主适配器,而非Windows适配器
//------------------------------------------------------------------------------------------------------------
ksError = KS_openSocket(
&pAppPtr->hSocket_,
hAdapter,
&socketAddr,
KS_PROTOCOL_UDP,
KSF_CLIENT);
if (ksError != KS_OK) {
outputErr(ksError, "KS_openSocket", "Failed to open socket");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 创建UDP消息回调
//------------------------------------------------------------------------------------------------------------
pAppPtr->remoteAddr.family = KS_FAMILY_IPV4;
pAppPtr->remoteAddr.port = KS_htons(0);
KS_makeIPv4(pAppPtr->remoteAddr.address, 0, 0, 0, 0);
//------------------------------------------------------------------------------------------------------------
// 安装接收消息回调
//------------------------------------------------------------------------------------------------------------
pAppPtr->finished_ = false;
ksError = KS_installSocketHandler(
pAppPtr->hSocket_,
KS_SOCKET_RECV,
hCallBack,
0);
if (ksError != KS_OK) {
outputErr(ksError, "KS_installSocketHandler", "Failed to install receive handler");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 注意在执行一下代码,之前需要配置虚拟网卡的IP地址
// 方法: 控制面板-》网络和 Internet-》网络连接
// 找到虚拟网卡右击属性 -网络- Internet协议属性4(IPv4),设置IP地址和掩码
//------------------------------------------------------------------------------------------------------------
outputTxt("Please adjust the IP-config of the Windows Adapter to 192.168.6.200 and subnet mask 255.255.255.0");
inputTxt("Press <enter> when ready");
outputTxt(" ");
//------------------------------------------------------------------------------------------------------------
// 创建一个Windows socket 模拟应用层或者外部应用与内核通信
//------------------------------------------------------------------------------------------------------------
WSADATA wsaData = {0};
WSAStartup(MAKEWORD(2, 2), &wsaData);
outputTxt("open Windows socket");
outputTxt(" ");
SOCKET sock = INVALID_SOCKET;
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
outputErr(KSERROR_INTERNAL, "socket", "Failed to create socket");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 发送消息给内核层
//------------------------------------------------------------------------------------------------------------
sockaddr_in recvAddr;
recvAddr.sin_family = AF_INET;
recvAddr.sin_port = htons(USER_PORT);
recvAddr.sin_addr.s_addr = inet_addr("192.168.6.100");
outputTxt("sendto Windows socket");
outputTxt(" ");
int result = sendto(sock, "Hello Kithara", 14, 0, (SOCKADDR*)&recvAddr, sizeof(recvAddr));
if (result == SOCKET_ERROR) {
outputErr(KSERROR_UNKNOWN, "sendto", "Failed to send to socket");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 内核层回复消息到应用层
//------------------------------------------------------------------------------------------------------------
outputTxt("recvFrom Windows socket");
outputTxt(" ");
sockaddr_in senderAddr;
char pBuf[64];
int fromLen = sizeof(sockaddr_in);
result = recvfrom(sock, pBuf, 14, 0, (SOCKADDR*)&senderAddr, &fromLen);
if (result == SOCKET_ERROR) {
outputErr(KSERROR_UNKNOWN, "recvfrom", "Failed to receive from socket");
KS_closeDriver();
return;
}
outputTxt((char*)pBuf);
//------------------------------------------------------------------------------------------------------------
// 退出
//------------------------------------------------------------------------------------------------------------
outputTxt("close Windows socket");
outputTxt(" ");
closesocket(sock);
WSACleanup();
pAppPtr->finished_ = true;
ksError = KS_setEvent(
pAppPtr->hEvent_);
if (ksError != KS_OK)
outputErr(ksError, "KS_setEvent", "Setting event failed!");
waitTime(500 * ms);
ksError = KS_closeSocket(
pAppPtr->hSocket_);
if (ksError != KS_OK)
outputErr(ksError, "KS_closeSocket", "Unable to close Socket!");
ksError = KS_closeNetwork(
hWinAdapter, 0);
if (ksError != KS_OK)
outputErr(ksError, "KS_closeNetwork", "Unable to close Adapter!");
ksError = KS_closeNetwork(
hAdapter, 0);
if (ksError != KS_OK)
outputErr(ksError, "KS_closeNetwork", "Unable to close Adapter!");
ksError = KS_removeCallBack(
hCallBack);
if (ksError != KS_OK)
outputErr(ksError, "KS_removeCallBack", "Unable to remove the callback!");
ksError = KS_closeEvent(
pAppPtr->hEvent_);
if (ksError != KS_OK)
outputErr(ksError, "KS_closeEvent", "Unable to close event!");
ksError = KS_removePipe(
pAppPtr->hPipe_);
if (ksError != KS_OK)
outputErr(ksError, "KS_removePipe", "Unable to remove the pipe!");
ksError = KS_freeSharedMem(
pAppPtr);
if (ksError != KS_OK)
outputErr(ksError, "KS_freeSharedMem", "Unable to remove shared memory!");
ksError = KS_freeKernel(
hKernel);
if (ksError != KS_OK)
outputErr(ksError, "KS_freeKernel", "Unable to free the kernel!");
ksError = KS_closeDriver();
if (ksError != KS_OK)
outputErr(ksError, "KS_closeDriver", "Unable to close the driver!");
waitTime(500 * ms);
outputTxt("End of program 'NetworkUdpWindowsConnection'.");
#endif // defined(_MSC_VER) && (_MSC_VER <= 1200)
}
- 内核层主要代码
cpp
#include "NetworkUdpWindowsConnection.h"
//------ 回调------
extern "C" KSError __declspec(dllexport) __stdcall _callBack(void* pArgs, void* pContext) {
CallBackData* pData = (CallBackData*)pArgs;
KSError ksError;
int length;
do {
//----------------------------------------------------------------------------------------------------------
// 接收UDP消息回调
//----------------------------------------------------------------------------------------------------------
ksError = KS_recvFromSocket(
pData->hSocket_,
&pData->remoteAddr,
pData->pBuf_,
2048,
&length,
0);
if (ksError && KSERROR_CODE(ksError) != KSERROR_NO_DATA_AVAILABLE)
return ksError;
if (ksError == KS_OK) {
//--------------------------------------------------------------------------------------------------------
// 通知应用层
//--------------------------------------------------------------------------------------------------------
ksError = KS_putPipe(
pData->hPipe_,
&pData->pBuf_,
length,
NULL,
0);
//--------------------------------------------------------------------------------------------------------
// 通过事件唤醒线程接收数据
//--------------------------------------------------------------------------------------------------------
KS_setEvent(
pData->hEvent_);
KS_sendToSocket(
pData->hSocket_,
&pData->remoteAddr,
pData->msg_,
14,
NULL,
0);
}
} while (ksError == KS_OK);
return KS_OK;
}
#define WIN32_LEAN_AND_MEAN
#pragma pack(push, 8)
#include <windows.h>
#pragma pack(pop)
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID pReserved) {
return TRUE;
}
- 共享内存结构体
. 注意一字节对齐
cpp
#include "../_KitharaSmp/_KitharaSmp.h"
struct CallBackData {
KSHandle hSocket_;
KSSocketAddr remoteAddr;
KSHandle hEvent_;
KSHandle hPipe_;
byte pBuf_[2048];
char msg_[14];
bool finished_;
};
补充配置IP地址:
运行效果:
也可使用其他网络工具与内核通信: