当前位置:   article > 正文

C++实现的windows系统下的WIFI管理_windows wifi native开发

windows wifi native开发

最近项目中需要扫描和连接windows系统的WiFi无线热点,在网上CSDN了一些,发现基本上都是基于 QT---Native Wifi functions 应用(WiFi有密码连接) 这篇文章转来转去的,这篇文章比较笼统,大概就是API接口的链接放上去;考虑到WiFi管理功能在windows程序开发中遇到的机会较多,Native Wifi 的API接口在MSDN里都有,但是一些接口的使用以及状态的获取,没有一个总结性的说明,导致没办法短时间做出比较完整的WiFi管理功能。所以,我总结一套比较常用的windows下WiFi管理逻辑,满足 扫描、连接、断开、状态判断、获取密码的功能。

0.WiFi管理的准备

        windows管理WiFi需要程序代码创建一个Client,接口是WlanOpenHandle,返回一个ClientHandle供程序使用,同时通过接口WlanRegisterNotification注册回调函数,获取所有无线接口的状态通知,比如扫描完成、正在断开连接、连接完成之类的,都可以从这个接口获取到,代码示例:

  1. bool WiFiManager::InitialHandle()
  2. {
  3. DWORD dwResult = 0;
  4. DWORD dwCurVersion = 0;
  5. DWORD dwMaxClient = 2;
  6. if (m_wlanHandle == NULL)
  7. {
  8. if ((dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &m_wlanHandle)) != ERROR_SUCCESS)
  9. {
  10. std::cout << "wlanOpenHandle failed with error: " << dwResult << std::endl;
  11. m_wlanHandle = NULL;
  12. return false;
  13. }
  14. // 注册消息通知回调
  15. if ((dwResult = WlanRegisterNotification(m_wlanHandle, WLAN_NOTIFICATION_SOURCE_ALL, TRUE, WLAN_NOTIFICATION_CALLBACK(OnNotificationCallback), NULL, nullptr, nullptr)) != ERROR_SUCCESS)
  16. {
  17. std::cout << "wlanRegisterNotification failed with error: " << dwResult << std::endl;
  18. m_wlanHandle = NULL;
  19. return false;
  20. }
  21. }
  22. return true;
  23. }

1.首先是WiFi的扫描功能。

       桌面右下角的那个无线图标,点开弹出无线热点界面后,会发起对WLAN无线网卡的扫描,来获取当前无线网卡探测到的热点。win10系统,只有点开这个界面才会扫描获取新的热点列表(这也是我们之前调用命令行管理WiFi遇到的列表无法更新的问题)。对于系统,我试过win10的,系统不会主动对网卡进行扫描,只会每隔1分钟扫描当前列表的状态(虽然回调函数也会返回WlanScan的扫描完成信号),所以我们的代码不主动发起对网卡扫描,就无法获得新的热点列表

Native Wifi实现对网卡列表扫描的接口是 WlanScan ,API中有一句话(complete a WlanScan function request in 4 seconds.),所以这个扫描过程会在4s内完成,扫描完成的信号可以通过回调函数获得。示例代码如下,获取网卡接口,再对每个网卡接口扫描热点:

  1. //获取Wlan的网卡接口数据
  2. dwResult = WlanEnumInterfaces(m_wlanHandle, NULL, &pIfList);
  3. if (dwResult != ERROR_SUCCESS)
  4. {
  5. return false;
  6. }
  7. for (i = 0; i < (int)pIfList->dwNumberOfItems; i++) //遍历每个网卡
  8. {
  9. pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
  10. dwResult = StringFromGUID2(pIfInfo->InterfaceGuid, (LPOLESTR)&GuidString,
  11. sizeof(GuidString) / sizeof(*GuidString));
  12. // 向无线网卡发送探测请求,WlanScan探测会在4秒内陆续更新WIFIList
  13. dwResult = WlanScan(m_wlanHandle, (const GUID*)(&pIfInfo->InterfaceGuid), NULL, NULL, NULL);
  14. if (dwResult != ERROR_SUCCESS)
  15. {
  16. return false;
  17. }
  18. }

2.获取WiFi热点列表。

       和扫描差不多,先调用WlanEnumInterfaces 获取电脑使能的无线网卡信息,然后对每个无线网卡调用WlanGetAvailableNetworkList 获取每个网卡上可用的网络列表,列表结构为 WLAN_AVAILABLE_NETWORK_LIST ,每个List里的项为 WLAN_AVAILABLE_NETWORK,这个结构里包含了每个热点的基本信息:

  1. typedef struct _WLAN_AVAILABLE_NETWORK {
  2. WCHAR strProfileName[WLAN_MAX_NAME_LENGTH];//配置文件名称
  3. DOT11_SSID dot11Ssid; //热点名称
  4. DOT11_BSS_TYPE dot11BssType;
  5. ULONG uNumberOfBssids;
  6. BOOL bNetworkConnectable; //是否可连接
  7. WLAN_REASON_CODE wlanNotConnectableReason; //不可连接的原因
  8. ULONG uNumberOfPhyTypes;
  9. DOT11_PHY_TYPE dot11PhyTypes[WLAN_MAX_PHY_TYPE_NUMBER];
  10. BOOL bMorePhyTypes;
  11. WLAN_SIGNAL_QUALITY wlanSignalQuality;//信号质量
  12. BOOL bSecurityEnabled;
  13. DOT11_AUTH_ALGORITHM dot11DefaultAuthAlgorithm;
  14. DOT11_CIPHER_ALGORITHM dot11DefaultCipherAlgorithm;
  15. DWORD dwFlags;//连接状态与是否有配置文件的位或
  16. DWORD dwReserved;
  17. } WLAN_AVAILABLE_NETWORK, *PWLAN_AVAILABLE_NETWORK;

     解析对应字段基本可以满足常见的WiFi信息功能使用,获取代码参考如下:

  1. for (i = 0; i < (int)pIfList->dwNumberOfItems; i++)
  2. {
  3. pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
  4. dwResult = WlanGetAvailableNetworkList(m_wlanHandle,
  5. &pIfInfo->InterfaceGuid,
  6. 2,
  7. NULL,
  8. &pBssList);
  9. if (dwResult != ERROR_SUCCESS)
  10. {
  11. std::cout << "WlanGetAvailableNetworkList failed with error:" << dwResult;
  12. return false;
  13. }
  14. for (j = 0; j < pBssList->dwNumberOfItems; j++)
  15. {
  16. pBssEntry = (WLAN_AVAILABLE_NETWORK*)&pBssList->Network[j];
  17. WiFiInfo info;
  18. //获取SSID
  19. char ssid[36];
  20. memcpy(ssid, pBssEntry->dot11Ssid.ucSSID, pBssEntry->dot11Ssid.uSSIDLength);
  21. ssid[pBssEntry->dot11Ssid.uSSIDLength] = 0;
  22. Utf8ToUnicode(info.cstrSSID, ssid);
  23. //信号强度
  24. info.nSignalValue = pBssEntry->wlanSignalQuality;
  25. //连接状态
  26. info.bLinked = pBssEntry->dwFlags & WLAN_AVAILABLE_NETWORK_CONNECTED;
  27. }
  28. ....
  29. }

3.WiFi热点密码的设置与修改

        这个功能必须要说WiFi的配置文件了,在windows命令行输入 :

netsh wlan show profile

        可以获取系统已经生成的WiFi配置文件,对于用户已经连接的热点,系统会生成一个热点对应的配置文件,保存热点的密码等相关设置,WiFi的自动连接也是读取这些记录的profile,它的格式是一个xml文件,字段解析详见 WLAN_profile Schema,每个字段对应的配置项不再赘述。

        我们只说获取和修改WiFI密码字段,其实就是获取和修改xml中的 keyMaterial (sharedKey) Element 项。

        获取WiFi热点的profile xml文件通过接口 WlanGetProfile ,这里需要注意 pdwFlags 参数要设置WLAN_PROFILE_GET_PLAINTEXT_KEY ,

DWORD dwFlags = WLAN_PROFILE_GET_PLAINTEXT_KEY | WLAN_PROFILE_USER;

   否则获取到的密码是被系统加密过的,被加密过的字段需要使用 CryptUnprotectData 系统函数解码,比较麻烦,所以直接获取密码明文。获取WiFi密码的代码示例:

  1. DWORD dwFlags = WLAN_PROFILE_GET_PLAINTEXT_KEY | WLAN_PROFILE_USER;
  2. DWORD dwGrantedAccess = WLAN_READ_ACCESS;
  3. DWORD dwResult = WlanGetProfile(m_wlanHandle,
  4. &pIfInfo->InterfaceGuid,
  5. info.cstrSSID,
  6. NULL,
  7. &info.pProfileXml,
  8. &dwFlags,
  9. &dwGrantedAccess);
  10. if (dwResult == ERROR_SUCCESS)
  11. {
  12. //获取密码
  13. CString cstrXml = info.pProfileXml;
  14. int nFirstIndex = cstrXml.Find(_T("<keyMaterial>"));
  15. if (nFirstIndex != -1)
  16. {
  17. int nLastIndex = cstrXml.Find(_T("</keyMaterial>"));
  18. CString strKey = CString(((LPCTSTR)cstrXml) + nFirstIndex + 13, nLastIndex - (nFirstIndex + 13));
  19. //解码key,如果字段被加密了,设置过WLAN_PROFILE_GET_PLAINTEXT_KEY 这个不用
  20. BYTE byteKey[1024] = { 0 };
  21. DWORD dwLength = 1024;
  22. DATA_BLOB dataOut, dataVerify;
  23. BOOL bRes = CryptStringToBinary(strKey, strKey.GetLength(), CRYPT_STRING_HEX, byteKey, &dwLength, 0, 0);
  24. if (bRes)
  25. {
  26. dataOut.cbData = dwLength;
  27. dataOut.pbData = (BYTE*)byteKey;
  28. if (CryptUnprotectData(&dataOut, NULL, NULL, NULL, NULL, 0, &dataVerify))
  29. {
  30. TCHAR str[MAX_PATH] = { 0 };
  31. wsprintf(str, L"%hs", dataVerify.pbData);
  32. strKey = str;
  33. }
  34. }
  35. info.cstrPassword = strKey;
  36. }
  37. }

        与获取profile接口 WlanGetProfile  对应的是 WlanSetProfile 写入一个 xml文件。

  1. DWORD WlanSetProfile(
  2. HANDLE hClientHandle,
  3. const GUID *pInterfaceGuid,
  4. DWORD dwFlags,
  5. LPCWSTR strProfileXml,
  6. LPCWSTR strAllUserProfileSecurity,
  7. BOOL bOverwrite,
  8. PVOID pReserved,
  9. DWORD *pdwReasonCode
  10. );

      我们将要设置的xml内容通过 strProfileXml 参数传入,对于修改已存在的profile,传入strProfileXml的同时,bOverwrite 要设置成 true表示重写,否则 WlanSetProfile 会调用失败,返回 ERROR_ALREADY_EXISTS错误。代码示例:

  1. void setProfile(CString& curSSID, CString targetKey, PWLAN_AVAILABLE_NETWORK pNet, GUID interfaceGuid)
  2. {
  3. CStringW szProfileXML(""); //Profile XML流
  4. wchar_t* wscProfileXML = NULL;
  5. /*组合参数XML码流*/
  6. CString szTemp("");
  7. /*头*/
  8. szProfileXML += _T("<?xml version=\"1.0\"?><WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\"><name>");
  9. /*name,一般与SSID相同*/
  10. szTemp = curSSID;
  11. szProfileXML += szTemp;
  12. /*SSIDConfig*/
  13. szProfileXML += ("</name><SSIDConfig><SSID><name>");
  14. char ssid[36];
  15. memcpy(ssid, pNet->dot11Ssid.ucSSID, pNet->dot11Ssid.uSSIDLength);
  16. ssid[pNet->dot11Ssid.uSSIDLength] = 0;
  17. szProfileXML += ssid;
  18. szProfileXML += ("</name></SSID></SSIDConfig>");
  19. /*connectionType*/
  20. szProfileXML += ("<connectionType>");
  21. switch (pNet->dot11BssType)
  22. {
  23. case dot11_BSS_type_infrastructure:
  24. szProfileXML += "ESS";
  25. break;
  26. case dot11_BSS_type_independent:
  27. szProfileXML += "IBSS";
  28. break;
  29. case dot11_BSS_type_any:
  30. szProfileXML += "Any";
  31. break;
  32. default:
  33. wprintf(L"Unknown BSS type");
  34. return false;
  35. }
  36. szProfileXML += ("</connectionType><MSM><security><authEncryption><authentication>");
  37. switch (pNet->dot11DefaultAuthAlgorithm)
  38. {
  39. case DOT11_AUTH_ALGO_80211_OPEN:
  40. szProfileXML += "open";
  41. wprintf(L"Open 802.11 authentication\n");
  42. break;
  43. case DOT11_AUTH_ALGO_80211_SHARED_KEY:
  44. szProfileXML += "shared";
  45. wprintf(L"Shared 802.11 authentication");
  46. break;
  47. case DOT11_AUTH_ALGO_WPA:
  48. szProfileXML += "WPA";
  49. wprintf(L"WPA-Enterprise 802.11 authentication\n");
  50. break;
  51. case DOT11_AUTH_ALGO_WPA_PSK:
  52. szProfileXML += "WPAPSK";
  53. wprintf(L"WPA-Personal 802.11 authentication\n");
  54. break;
  55. case DOT11_AUTH_ALGO_WPA_NONE:
  56. szProfileXML += "none";
  57. wprintf(L"WPA-NONE,not exist in MSDN\n");
  58. break;
  59. case DOT11_AUTH_ALGO_RSNA:
  60. szProfileXML += "WPA2";
  61. wprintf(L"WPA2-Enterprise 802.11 authentication\n");
  62. break;
  63. case DOT11_AUTH_ALGO_RSNA_PSK:
  64. szProfileXML += "WPA2PSK";
  65. wprintf(L"WPA2-Personal 802.11 authentication\n");
  66. break;
  67. default:
  68. wprintf(L"Unknown authentication");
  69. return false;
  70. }
  71. szProfileXML += ("</authentication><encryption>");
  72. switch (pNet->dot11DefaultCipherAlgorithm)
  73. {
  74. case DOT11_CIPHER_ALGO_NONE:
  75. szProfileXML += "none";
  76. break;
  77. case DOT11_CIPHER_ALGO_WEP40:
  78. szProfileXML += "WEP";
  79. break;
  80. case DOT11_CIPHER_ALGO_TKIP:
  81. szProfileXML += "TKIP";
  82. break;
  83. case DOT11_CIPHER_ALGO_CCMP:
  84. szProfileXML += "AES";
  85. break;
  86. case DOT11_CIPHER_ALGO_WEP104:
  87. szProfileXML += "WEP";
  88. break;
  89. case DOT11_CIPHER_ALGO_WEP:
  90. szProfileXML += "WEP";
  91. break;
  92. case DOT11_CIPHER_ALGO_WPA_USE_GROUP:
  93. wprintf(L"USE-GROUP not exist in MSDN");
  94. default:
  95. wprintf(L"Unknown encryption");
  96. return false;
  97. }
  98. szProfileXML += ("</encryption></authEncryption><sharedKey><keyType>passPhrase</keyType><protected>false</protected><keyMaterial>");
  99. szProfileXML += targetKey;
  100. /*尾*/
  101. szProfileXML += ("</keyMaterial></sharedKey></security></MSM></WLANProfile>");
  102. /*XML码流转换成双字节*/
  103. wscProfileXML = szProfileXML.GetBuffer();
  104. if (NULL == wscProfileXML)
  105. {
  106. wprintf(L"Change wscProfileXML fail\n");
  107. return false;
  108. }
  109. /*设置网络参数*/
  110. DWORD dwReasonCode = 0;
  111. DWORD dwResult = WlanSetProfile(m_wlanHandle, &interfaceGuid,
  112. 0x00, wscProfileXML, NULL, TRUE, NULL, &dwReasonCode);
  113. if (ERROR_SUCCESS != dwResult)
  114. {
  115. return false;
  116. }
  117. }

3.WiFi的连接

        连接接口是 WlanConnect,连接之前,需要判断当前要连接的热点是否有profile(热点列表 WLAN_AVAILABLE_NETWORK字段里有),没有profile的话需要自己写一个xml,调用WlanSetProfile 配置下去,需要注意的是 WlanConnect 的返回值是立即返回的,表示调用的成功或失败原因,并不表示热点的连接状态,热点的连接状态需要我们前面提到的回调函数里获取。代码示例:

  1. //连接WIFi
  2. WLAN_AVAILABLE_NETWORK wlanAN = pBssList->Network[j];
  3. WLAN_CONNECTION_PARAMETERS wlanConnPara;
  4. wlanConnPara.wlanConnectionMode = wlan_connection_mode_profile; //YES,WE CONNECT AP VIA THE PROFILE
  5. wlanConnPara.strProfile = wlanAN.strProfileName; // set the profile name
  6. wlanConnPara.pDot11Ssid = NULL; // SET SSID NULL
  7. wlanConnPara.dot11BssType = dot11_BSS_type_infrastructure; //dot11_BSS_type_any,I do not need it this time.
  8. wlanConnPara.pDesiredBssidList = NULL; // the desired BSSID list is empty
  9. wlanConnPara.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK; //it works on my WIN78
  10. dwResult = WlanConnect(m_wlanHandle, &pIfInfo->InterfaceGuid, &wlanConnPara, NULL);
  11. if (dwResult == ERROR_SUCCESS)
  12. {
  13. //WlanConnect是立即返回的,所有返回值没意义,通过注册的回调函数OnNotificationCallback获取连接结果
  14. }

4.回调函数获取状态

        对于回调函数 WLAN_NOTIFICATION_CALLBACK,参数1 PWLAN_NOTIFICATION_DATA,NotificationSource 字段 对WLAN系统消息进行分类,NotificationCode表示系统消息的具体消息类型

        参数2是NotificationCode消息类型的数据指针,指向该类消息的具体内容;

        我们可以通过回调函数获取WLAN连接的各种状态提示,示例如下:

  1. void OnNotificationCallback(PWLAN_NOTIFICATION_DATA data, PVOID context)
  2. {
  3. if (data != NULL && data->NotificationSource == WLAN_NOTIFICATION_SOURCE_ACM)
  4. {
  5. switch (data->NotificationCode)
  6. {
  7. case wlan_notification_acm_scan_complete:
  8. case wlan_notification_acm_scan_fail:
  9. WiFiManager::GetInstance().SetConnectResult(NULL, data->NotificationCode);
  10. break;
  11. case wlan_notification_acm_connection_start:
  12. case wlan_notification_acm_connection_complete:
  13. case wlan_notification_acm_connection_attempt_fail:
  14. case wlan_notification_acm_disconnecting:
  15. case wlan_notification_acm_disconnected:
  16. {
  17. PWLAN_CONNECTION_NOTIFICATION_DATA connection = (PWLAN_CONNECTION_NOTIFICATION_DATA)data->pData;
  18. WiFiManager::GetInstance().SetConnectResult(connection, data->NotificationCode);
  19. }
  20. break;
  21. default:
  22. break;
  23. }
  24. }
  25. }

下面是我写的WiFi管理工具源码,IDE是VS2015:

wifimanagerapidemo.zip

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/252349
推荐阅读
相关标签
  

闽ICP备14008679号