一、遇到问题的描述
在开发“局域网查找设备”这个功能时候,一般都会使用局域网广播技术。常见的问题时在编写程序的时候在自己电脑上使用没有问题,但是在用户或测试机器上,出现“无法查找到设备的问题”。使用WireShark软件监听局域网,可以发现是在自己软件查找设备的时候,没有正常发送出去局域网广播。
二、找到问题的真正原因
- 初步找到原因
既然是广播没有发送出去,那就开始调试与测试,经过在多台电脑上对比测试,发现不能正常发出广播的电脑上有个虚拟网卡。而能正常使用的查找设备功能的电脑上没有任何虚拟网卡。因此推断是“虚拟网卡的影响”,为了验证是否是虚拟网卡导致的问题,因此现在把这个虚拟网卡禁用试试看。操作步骤是,选中这个虚拟网卡,然后右键选择“禁用”。这样就没有虚拟网卡的干扰了,然后使用自己软件的“查找设备功能”发现可以正常使用了,可以查找到设备。那么原因就确定了,就是因为虚拟网卡导致无法正常发出广播。
有虚拟网卡以及禁用截图如下: - 深入分析虚拟网卡导致问题的原因
观察到的问题现象是,只有一个网卡的时候,查找设备功能可以正常使用。但是当增加了一个虚拟网卡,就导致广播无法发送。进一步测试发现,如果电脑上有2张实体网卡的时候,也会导致“查找设备功能”无法使用。 因此呢,推测是自己的程序只使用了电脑上的一张默认网卡来通信(包括查找设备中用的广播)。因此呢在只有一张网卡的电脑上,可以正常运行,但是有多张网卡和有虚拟网卡的时候,默认的网卡只能是多张网卡其中的一张。因此导致自己的程序只在其中一张网卡上正常发送出了广播。而在其他网卡上没有发出任何广播。因此导致多张网卡和有虚拟网卡的时候,有可能导致连接下位的网卡无法发出广播。导致无法查找到设备。(以上结论可以使用WireShark软件验证)。 - 额外说明
- 虚拟网卡的来源:电脑上安装有虚拟机软件的时候,那么你电脑上就会多出一张“虚拟网卡”
- 虚拟网卡对虚拟机的作用:虚拟网卡是虚拟出来的一张网卡,用于虚拟机跳过寄主电脑直接接入局域网,从而使虚拟机里面的系统和寄主计算机处于同一局域网下。
- 多网卡导致无法查找到设备的典型案例
三、解决方案
- 简单处理问题
原因以及明确了,在无法查找到设备电脑上,查看一下系统中的网卡。如果有虚拟网卡,把虚拟网卡右键禁用即可。
如果是因为有多张网卡导致,那么依次禁用其中一张网卡,然后验证是否可以查找到设备。最终确定一张唯一可用的网卡即可。
- 编程处理问题
虽然处理方法1可以快速解决问题,但是用户体验不好,给用户一种软件不稳定的感觉。同时因为这个问题也会给安装技术人员或者售后增加负担。因此静下来,从编程的角度彻底把这个问题解决掉吧。
由于问题原因都搞明白了,那么编程思路有2条。
- 软件在查找设备之前,现在使用系统库函数确定所用电脑有几张网卡,获取这些网卡的名字,从名字上过滤掉“虚拟网卡”,只使用一张非“虚拟网卡”来发送广播。从而解决问题。(虚拟网卡的名字和普通网卡名字有明显区别)
- 那么在电脑上有3张以及3张以上的电脑上,上面的方法就不太好了,因为非“虚拟网卡”有2张及2张以上。到底该选哪张呢?解决办法也其实也简单粗暴。即把所有非“虚拟网卡”的网卡都作为广播发送端。这样的话无论你的下位机与电脑的哪张网卡在同一个局域网下,都可以查找到设备。更简单粗暴单实用的是不区分虚拟网卡和物理网卡,让所有电脑上的网卡都发送查找设备的广播并监听下位反馈数据包。
四、实际编程
这里仅列出,在各个开发平台上怎样获取多个网卡的Ip地址。使用此IP地址作为发送广播时候指定的本地IP地址。使用不同本地地址发送广播,即可达到使用不同本机网卡发送广播的目的。
有关怎样使用不同网卡IP地址,发送广播的实例代码,请参考“2.9-局域网查找设备的实现代码(支持有虚拟网卡)”。
- Qt
QList<QString> MyNet::GetIpListOfComputer() {
QList<QString> ret_list;
QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces();
foreach(QNetworkInterface interfaceItem, interfaceList)
{
if(interfaceItem.flags().testFlag(QNetworkInterface::IsUp)
&&interfaceItem.flags().testFlag(QNetworkInterface::IsRunning)
&&interfaceItem.flags().testFlag(QNetworkInterface::CanBroadcast)
&&interfaceItem.flags().testFlag(QNetworkInterface::CanMulticast)
&&!interfaceItem.flags().testFlag(QNetworkInterface::IsLoopBack)
//在这里通过名字过滤掉“虚拟网卡”
//&&!interfaceItem.humanReadableName().contains("VMware")
//&&!interfaceItem.humanReadableName().contains("vnic") //mac parallels
//&&!interfaceItem.humanReadableName().contains("Npcap")
//在Windows下使用此过滤会导致通信出问题,因此这里采用,即便是虚拟网卡也当初正常网卡来发送广播,即所有可用监测到的网卡都用了发送广播,从而解决问题
)
{
QList<QNetworkAddressEntry> addressEntryList=interfaceItem.addressEntries();
foreach(QNetworkAddressEntry addressEntryItem,addressEntryList)
{
if(addressEntryItem.ip().protocol()==QAbstractSocket::IPv4Protocol)
{
//这里打印出电脑上的所有网卡,
qDebug()<<"------------------------------------------------------------";
qDebug()<<"Adapter Name:"<<interfaceItem.name();
qDebug()<<"Adapter Address:"<<interfaceItem.hardwareAddress();
qDebug()<<"IP Address:"<<addressEntryItem.ip().toString();
qDebug()<<"IP Mask:"<<addressEntryItem.netmask().toString();
qDebug() << "可过滤的网卡名字" << interfaceItem.humanReadableName();
ret_list.append(addressEntryItem.ip().toString());
}
}
}
}
//返回所有可用网卡的Ip地址,注:每个网卡有自己的地址(当然有的网卡其实没有接入局域网,因此虽然有ip地址,但是其实无法完成任何通信)
return ret_list;
}
- C#Winform
private List<String> GetIpListOfTheNetInterface() {
List<String> ipList = new List<string>();
NetworkInterface[] NetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface NetworkIntf in NetworkInterfaces)
{
IPInterfaceProperties IPInterfaceProperties = NetworkIntf.GetIPProperties();
UnicastIPAddressInformationCollection UnicastIPAddressInformationCollection =
IPInterfaceProperties.UnicastAddresses;
foreach (UnicastIPAddressInformation UnicastIPAddressInformation in
UnicastIPAddressInformationCollection)
{
if (UnicastIPAddressInformation.Address.AddressFamily
== AddressFamily.InterNetwork)
{
String ip = UnicastIPAddressInformation.Address.ToString();
if (!ip.Equals("127.0.0.1")) {
ipList.Add(ip);
}
}
}
}
return ipList;
}