最近项目中需要扫描和连接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,注册回调函数,获取所有无线接口的状态通知,比如扫描完成、正在断开连接、连接完成之类的,都可以从这个接口获取到,代码示例:

bool WiFiManager::InitialHandle()
{DWORD dwResult = 0;DWORD dwCurVersion = 0;DWORD dwMaxClient = 2;if (m_wlanHandle == NULL){if ((dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &m_wlanHandle)) != ERROR_SUCCESS){std::cout << "wlanOpenHandle failed with error: " << dwResult << std::endl;m_wlanHandle = NULL;return false;}// 注册消息通知回调if ((dwResult = WlanRegisterNotification(m_wlanHandle, WLAN_NOTIFICATION_SOURCE_ALL, TRUE, WLAN_NOTIFICATION_CALLBACK(OnNotificationCallback), NULL, nullptr, nullptr)) != ERROR_SUCCESS){std::cout << "wlanRegisterNotification failed with error: " << dwResult << std::endl;m_wlanHandle = NULL;return false;}}return true;
}

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

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

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

    //获取Wlan的网卡接口数据dwResult = WlanEnumInterfaces(m_wlanHandle, NULL, &pIfList);if (dwResult != ERROR_SUCCESS){return false;}for (i = 0; i < (int)pIfList->dwNumberOfItems; i++) //遍历每个网卡{pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];dwResult = StringFromGUID2(pIfInfo->InterfaceGuid, (LPOLESTR)&GuidString,sizeof(GuidString) / sizeof(*GuidString));// 向无线网卡发送探测请求,WlanScan探测会在4秒内陆续更新WIFIListdwResult = WlanScan(m_wlanHandle, (const GUID*)(&pIfInfo->InterfaceGuid), NULL, NULL, NULL);if (dwResult != ERROR_SUCCESS){return false;}}

2.获取WiFi热点列表。

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

typedef struct _WLAN_AVAILABLE_NETWORK {WCHAR                  strProfileName[WLAN_MAX_NAME_LENGTH];//配置文件名称DOT11_SSID             dot11Ssid; //热点名称DOT11_BSS_TYPE         dot11BssType;ULONG                  uNumberOfBssids;BOOL                   bNetworkConnectable; //是否可连接WLAN_REASON_CODE       wlanNotConnectableReason; //不可连接的原因ULONG                  uNumberOfPhyTypes;DOT11_PHY_TYPE         dot11PhyTypes[WLAN_MAX_PHY_TYPE_NUMBER];BOOL                   bMorePhyTypes;WLAN_SIGNAL_QUALITY    wlanSignalQuality;//信号质量BOOL                   bSecurityEnabled;DOT11_AUTH_ALGORITHM   dot11DefaultAuthAlgorithm;DOT11_CIPHER_ALGORITHM dot11DefaultCipherAlgorithm;DWORD                  dwFlags;//连接状态与是否有配置文件的位或DWORD                  dwReserved;
} WLAN_AVAILABLE_NETWORK, *PWLAN_AVAILABLE_NETWORK;

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

for (i = 0; i < (int)pIfList->dwNumberOfItems; i++)
{pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];dwResult = WlanGetAvailableNetworkList(m_wlanHandle,&pIfInfo->InterfaceGuid,2,NULL,&pBssList);if (dwResult != ERROR_SUCCESS){std::cout << "WlanGetAvailableNetworkList failed with error:" << dwResult;return false;}for (j = 0; j < pBssList->dwNumberOfItems; j++){pBssEntry = (WLAN_AVAILABLE_NETWORK*)&pBssList->Network[j];WiFiInfo info;//获取SSIDchar ssid[36];memcpy(ssid, pBssEntry->dot11Ssid.ucSSID, pBssEntry->dot11Ssid.uSSIDLength);ssid[pBssEntry->dot11Ssid.uSSIDLength] = 0;Utf8ToUnicode(info.cstrSSID, ssid);//信号强度info.nSignalValue = pBssEntry->wlanSignalQuality;//连接状态info.bLinked = pBssEntry->dwFlags & WLAN_AVAILABLE_NETWORK_CONNECTED;}
....
}

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密码的代码示例:

DWORD dwFlags = WLAN_PROFILE_GET_PLAINTEXT_KEY | WLAN_PROFILE_USER;
DWORD dwGrantedAccess = WLAN_READ_ACCESS;
DWORD dwResult = WlanGetProfile(m_wlanHandle,&pIfInfo->InterfaceGuid,info.cstrSSID,NULL,&info.pProfileXml,&dwFlags,&dwGrantedAccess);
if (dwResult == ERROR_SUCCESS)
{//获取密码CString cstrXml = info.pProfileXml;int nFirstIndex = cstrXml.Find(_T("<keyMaterial>"));if (nFirstIndex != -1){int nLastIndex = cstrXml.Find(_T("</keyMaterial>"));CString strKey = CString(((LPCTSTR)cstrXml) + nFirstIndex + 13, nLastIndex - (nFirstIndex + 13));//解码key,如果字段被加密了,设置过WLAN_PROFILE_GET_PLAINTEXT_KEY 这个不用BYTE byteKey[1024] = { 0 };DWORD dwLength = 1024;DATA_BLOB dataOut, dataVerify;BOOL bRes = CryptStringToBinary(strKey, strKey.GetLength(), CRYPT_STRING_HEX, byteKey, &dwLength, 0, 0);if (bRes){dataOut.cbData = dwLength;dataOut.pbData = (BYTE*)byteKey;if (CryptUnprotectData(&dataOut, NULL, NULL, NULL, NULL, 0, &dataVerify)){TCHAR str[MAX_PATH] = { 0 };  wsprintf(str, L"%hs", dataVerify.pbData);strKey = str;}} info.cstrPassword = strKey;}
}

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

DWORD WlanSetProfile(HANDLE     hClientHandle,const GUID *pInterfaceGuid,DWORD      dwFlags,LPCWSTR    strProfileXml,LPCWSTR    strAllUserProfileSecurity,BOOL       bOverwrite,PVOID      pReserved,DWORD      *pdwReasonCode
);

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

void setProfile(CString& curSSID, CString targetKey, PWLAN_AVAILABLE_NETWORK pNet, GUID interfaceGuid)
{   CStringW szProfileXML("");  //Profile XML流wchar_t* wscProfileXML = NULL;/*组合参数XML码流*/CString szTemp("");/*头*/szProfileXML += _T("<?xml version=\"1.0\"?><WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\"><name>");/*name,一般与SSID相同*/szTemp = curSSID;szProfileXML += szTemp;/*SSIDConfig*/szProfileXML += ("</name><SSIDConfig><SSID><name>");char ssid[36];memcpy(ssid, pNet->dot11Ssid.ucSSID, pNet->dot11Ssid.uSSIDLength);ssid[pNet->dot11Ssid.uSSIDLength] = 0;szProfileXML += ssid;szProfileXML += ("</name></SSID></SSIDConfig>");/*connectionType*/szProfileXML += ("<connectionType>");switch (pNet->dot11BssType){case dot11_BSS_type_infrastructure:szProfileXML += "ESS";break;case dot11_BSS_type_independent:szProfileXML += "IBSS";break;case dot11_BSS_type_any:szProfileXML += "Any";break;default:wprintf(L"Unknown BSS type");return false;}szProfileXML += ("</connectionType><MSM><security><authEncryption><authentication>");switch (pNet->dot11DefaultAuthAlgorithm){case DOT11_AUTH_ALGO_80211_OPEN:szProfileXML += "open";wprintf(L"Open 802.11 authentication\n");break;case DOT11_AUTH_ALGO_80211_SHARED_KEY:szProfileXML += "shared";wprintf(L"Shared 802.11 authentication");break;case DOT11_AUTH_ALGO_WPA:szProfileXML += "WPA";wprintf(L"WPA-Enterprise 802.11 authentication\n");break;case DOT11_AUTH_ALGO_WPA_PSK:szProfileXML += "WPAPSK";wprintf(L"WPA-Personal 802.11 authentication\n");break;case DOT11_AUTH_ALGO_WPA_NONE:szProfileXML += "none";wprintf(L"WPA-NONE,not exist in MSDN\n");break;case DOT11_AUTH_ALGO_RSNA:szProfileXML += "WPA2";wprintf(L"WPA2-Enterprise 802.11 authentication\n");break;case DOT11_AUTH_ALGO_RSNA_PSK:szProfileXML += "WPA2PSK";wprintf(L"WPA2-Personal 802.11 authentication\n");break;default:wprintf(L"Unknown authentication");return false;}szProfileXML += ("</authentication><encryption>");switch (pNet->dot11DefaultCipherAlgorithm){case DOT11_CIPHER_ALGO_NONE:szProfileXML += "none";break;case DOT11_CIPHER_ALGO_WEP40:szProfileXML += "WEP";break;case DOT11_CIPHER_ALGO_TKIP:szProfileXML += "TKIP";break;case DOT11_CIPHER_ALGO_CCMP:szProfileXML += "AES";break;case DOT11_CIPHER_ALGO_WEP104:szProfileXML += "WEP";break;case DOT11_CIPHER_ALGO_WEP:szProfileXML += "WEP";break;case DOT11_CIPHER_ALGO_WPA_USE_GROUP:wprintf(L"USE-GROUP not exist in MSDN");default:wprintf(L"Unknown encryption");return false;}szProfileXML += ("</encryption></authEncryption><sharedKey><keyType>passPhrase</keyType><protected>false</protected><keyMaterial>");szProfileXML += targetKey;/*尾*/szProfileXML += ("</keyMaterial></sharedKey></security></MSM></WLANProfile>");/*XML码流转换成双字节*/wscProfileXML = szProfileXML.GetBuffer();if (NULL == wscProfileXML){wprintf(L"Change wscProfileXML fail\n");return false;}/*设置网络参数*/DWORD dwReasonCode = 0;DWORD dwResult = WlanSetProfile(m_wlanHandle, &interfaceGuid,0x00, wscProfileXML, NULL, TRUE, NULL, &dwReasonCode);if (ERROR_SUCCESS != dwResult){return false;}
}

3.WiFi的连接

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

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

4.回调函数获取状态

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

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

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

void OnNotificationCallback(PWLAN_NOTIFICATION_DATA data, PVOID context)
{if (data != NULL && data->NotificationSource == WLAN_NOTIFICATION_SOURCE_ACM){switch (data->NotificationCode){case wlan_notification_acm_scan_complete:case wlan_notification_acm_scan_fail:WiFiManager::GetInstance().SetConnectResult(NULL, data->NotificationCode);break;case wlan_notification_acm_connection_start:case wlan_notification_acm_connection_complete:case wlan_notification_acm_connection_attempt_fail:case wlan_notification_acm_disconnecting:case wlan_notification_acm_disconnected:{PWLAN_CONNECTION_NOTIFICATION_DATA connection = (PWLAN_CONNECTION_NOTIFICATION_DATA)data->pData;WiFiManager::GetInstance().SetConnectResult(connection, data->NotificationCode);}break;default:break;}}
}

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

wifimanagerapidemo.zip

C++实现的windows系统下的WIFI管理相关推荐

  1. Windows系统下查询WiFi信道及强度等信息

    netsh wlan show interface查询当前网卡连接的wifi 对应的信号强度&信道 netsh wlan show networks mode=bssid 查询周围所有WiFi

  2. Windows系统下的包管理器chocolatey

    官方文档 https://chocolatey.org/docs#packages 安装 以管理员权限打开命令提示符窗口,然后输入以下命令即可: @powershell -NoProfile -Exe ...

  3. 在Mac系统下架设WiFi热点,对比Win7

    看到<Win7无线电脑变WiFi热点实战体会>一文后,对比Mac平台,Windows系统确实做的不够. 在mac下自己架设wifi应该来说还是非常稀疏平常的事,这还得得益于都是苹果的设计. ...

  4. windows10 oracle自动备份,Windows系统下oracle 自动备份数据库

    Windows系统下oracle 自动备份数据库 1.创建批处理文件(.bat) 2.建立windows 定时任务 2.1创建任务 2.2 常规处 ->填写任务名称 2.3触发器 2.4 操作 ...

  5. 转: windows系统下mysql出现Error 1045(28000) Access Denied for user 'root'@'localhost'

    windows系统下mysql出现Error 1045(28000) Access Denied for user 'root'@'localhost' 转自 http://zxy5241.space ...

  6. bat小工具_小程序反编译工具在windows系统下的调用脚本

    点击上方蓝字可以订阅哦 之前的文章中介绍了小程序的反编译工具,工具在 Linux 系统下使用时执行bingo.sh [xxx.wxapkg]就可以. 有小伙伴不知道在windows系统下如何使用,这里 ...

  7. windows系统下jenkins环境搭建与基本使用

    一. windows 系统下搭建jenkins环境 1.1 jenkins环境搭建和构建job流程图 1.2 安装jdk JDK下载地址:  http://www.oracle.com/technet ...

  8. windows系统下批量修改文件后缀

    在Windows系统下批量修改文件后缀要用到 ren命令.在使用win10系统的朋友们肯定会发现系统会给自己推荐一些开机锁屏壁纸, 今天就用这个命令批量保存这些壁纸. 这些壁纸文件目录在: C:\Us ...

  9. ubuntu系统下用kazam软件录制的视频不能在windows系统下播放的解决方案

    ubuntu系统下用kazam软件录制的视频不能在windows系统下播放的解决方案 参考文章: (1)ubuntu系统下用kazam软件录制的视频不能在windows系统下播放的解决方案 (2)ht ...

  10. nginx+tomcat实现Windows系统下的负载均衡搭建教程

    下面小编就为大家分享一篇nginx+tomcat实现Windows系统下的负载均衡搭建教程,具有很好的参考价值,希望对大家有所帮助 刚入行没多久就听过'负载均衡'的大名,到现在因为工作接触的少,所以没 ...

最新文章

  1. Crunch Bang(linux)安装Webstorm上手
  2. Andoird --- Json 经典异常:org.json.JSONException: Unterminated string at character
  3. SqlServer在附加数据库时提示:无法打开物理文件**.mdf 操作系统错误拒绝访问
  4. 作者:李海生(1974-),男,博士,食品安全大数据技术北京市重点实验室、北京工商大学计算机与信息工程学院教授、研究生导师...
  5. 程序员的搞笑日常:写给1024的程序员们,现在的你们还在加班吗?
  6. android 上划卡住tab_Android SlidingTabLayout用法禁止ViewPager滑动
  7. hudi延迟日志命名
  8. css为什么要用浮动_CSS问题和解决
  9. vue中:key 和react 中key={} 的作用,以及ref的特性?
  10. 配置oracle方言类,Oracle环境下的Hibernate方言配置
  11. 配置LVS + Keepalived高可用负载均衡集群之图文教程
  12. C++ STL vector详解
  13. ZUC密码(C语言实现)
  14. 如何解决切换双系统导致windows没声音的问题
  15. 幼儿园案例经验迁移_【投石问路】让案例分析成为幼儿教师自我成长的阶梯
  16. Kienct与Arduino学习笔记(1) 基础知识之Arduino’Kinect‘Processing
  17. java 修改表格颜色代码_workBook设置单元格颜色方法
  18. 【区块链论文整理】SIGMOD篇(一)
  19. git版本控制以及分支管理
  20. 高一下册计算机教案,高一下册数学必修二教案

热门文章

  1. 支配树 Dominator Tree
  2. 微信屏蔽网址解决办法:实现被微信屏蔽的网址在微信内正常访问
  3. H5微信授权登录 H5支付 外部浏览器微信支付 前端一个函数搞
  4. 联想V450笔记本 加装固态硬盘
  5. inl和dnl matlab_AD的一些指标——INL与DNL
  6. 真机调试错误 Reason: image not found想到的
  7. HDU6357——Hills And Valleys
  8. win7音量控制图标不见了怎么办
  9. 单片机中段程序_单片机程序延时方法详细介绍
  10. c语言实现mysql通配符_Mysql的C语言接口简单实现电话本功能