2.8-局域網查找設備時有虛擬網卡干擾的編程處理方法(網絡UDP廣播包發不出去)

一、遇到問題的描述

在開發“局域網查找設備”這個功能時候,一般都會使用局域網廣播技術。常見的問題時在編寫程序的時候在自己電腦上使用沒有問題,但是在用戶或測試機器上,出現“無法查找到設備的問題”。使用WireShark軟件監聽局域網,可以發現是在自己軟件查找設備的時候,沒有正常發送出去局域網廣播。

二、找到問題的真正原因

  1. 初步找到原因
    既然是廣播沒有發送出去,那就開始調試與測試,經過在多臺電腦上對比測試,發現不能正常發出廣播的電腦上有個虛擬網卡。而能正常使用的查找設備功能的電腦上沒有任何虛擬網卡。因此推斷是“虛擬網卡的影響”,爲了驗證是否是虛擬網卡導致的問題,因此現在把這個虛擬網卡禁用試試看。操作步驟是,選中這個虛擬網卡,然後右鍵選擇“禁用”。這樣就沒有虛擬網卡的干擾了,然後使用自己軟件的“查找設備功能”發現可以正常使用了,可以查找到設備。那麼原因就確定了,就是因爲虛擬網卡導致無法正常發出廣播。
    有虛擬網卡以及禁用截圖如下:
    在這裏插入圖片描述
  2. 深入分析虛擬網卡導致問題的原因
    觀察到的問題現象是,只有一個網卡的時候,查找設備功能可以正常使用。但是當增加了一個虛擬網卡,就導致廣播無法發送。進一步測試發現,如果電腦上有2張實體網卡的時候,也會導致“查找設備功能”無法使用。 因此呢,推測是自己的程序只使用了電腦上的一張默認網卡來通信(包括查找設備中用的廣播)。因此呢在只有一張網卡的電腦上,可以正常運行,但是有多張網卡和有虛擬網卡的時候,默認的網卡只能是多張網卡其中的一張。因此導致自己的程序只在其中一張網卡上正常發送出了廣播。而在其他網卡上沒有發出任何廣播。因此導致多張網卡和有虛擬網卡的時候,有可能導致連接下位的網卡無法發出廣播。導致無法查找到設備。(以上結論可以使用WireShark軟件驗證)。
  3. 額外說明
  • 虛擬網卡的來源:電腦上安裝有虛擬機軟件的時候,那麼你電腦上就會多出一張“虛擬網卡”
  • 虛擬網卡對虛擬機的作用:虛擬網卡是虛擬出來的一張網卡,用於虛擬機跳過寄主電腦直接接入局域網,從而使虛擬機裏面的系統和寄主計算機處於同一局域網下。
  1. 多網卡導致無法查找到設備的典型案例
    在這裏插入圖片描述

三、解決方案

  1. 簡單處理問題

原因以及明確了,在無法查找到設備電腦上,查看一下系統中的網卡。如果有虛擬網卡,把虛擬網卡右鍵禁用即可。
如果是因爲有多張網卡導致,那麼依次禁用其中一張網卡,然後驗證是否可以查找到設備。最終確定一張唯一可用的網卡即可。

  1. 編程處理問題

雖然處理方法1可以快速解決問題,但是用戶體驗不好,給用戶一種軟件不穩定的感覺。同時因爲這個問題也會給安裝技術人員或者售後增加負擔。因此靜下來,從編程的角度徹底把這個問題解決掉吧。

由於問題原因都搞明白了,那麼編程思路有2條。

  • 軟件在查找設備之前,現在使用系統庫函數確定所用電腦有幾張網卡,獲取這些網卡的名字,從名字上過濾掉“虛擬網卡”,只使用一張非“虛擬網卡”來發送廣播。從而解決問題。(虛擬網卡的名字和普通網卡名字有明顯區別)
  • 那麼在電腦上有3張以及3張以上的電腦上,上面的方法就不太好了,因爲非“虛擬網卡”有2張及2張以上。到底該選哪張呢?解決辦法也其實也簡單粗暴。即把所有非“虛擬網卡”的網卡都作爲廣播發送端。這樣的話無論你的下位機與電腦的哪張網卡在同一個局域網下,都可以查找到設備。更簡單粗暴單實用的是不區分虛擬網卡和物理網卡,讓所有電腦上的網卡都發送查找設備的廣播並監聽下位反饋數據包。

四、實際編程

這裏僅列出,在各個開發平臺上怎樣獲取多個網卡的Ip地址。使用此IP地址作爲發送廣播時候指定的本地IP地址。使用不同本地地址發送廣播,即可達到使用不同本機網卡發送廣播的目的。
有關怎樣使用不同網卡IP地址,發送廣播的實例代碼,請參考“2.9-局域網查找設備的實現代碼(支持有虛擬網卡)”。

  1. 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;
}
  1. 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章