Window 无线网络处理相关博文:
【Window】获取电脑连接的网络信息
Window 平台操作无线网络相关的的问题,大多都是使用 wlanapi 库,以下连接Wifi也不例外。
要连接热点得必须有ssid 和 key.,window没有提供现成的函数,提供两个参数可以直接连接wifi,wifi 连接过程也是有学问的,
那就是要提供一个profile ,也就是一个符合某个协议的数据,里面提供了ssid和key.
具体参考:无线网络Profile类型与格式
不同的wifi 类型有不同的profile 格式,所以必须一一对应。
下面以WPA2-PSK类型为例,在msdn找到格式如下:
<?xml version="1.0" encoding="US-ASCII"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
<name>SampleWPA2PSK</name>
<SSIDConfig>
<SSID>
<name>SampleWPA2PSK</name>
</SSID>
</SSIDConfig>
<connectionType>ESS</connectionType>
<connectionMode>auto</connectionMode>
<autoSwitch>false</autoSwitch>
<MSM>
<security>
<authEncryption>
<authentication>WPA2PSK</authentication>
<encryption>AES</encryption>
<useOneX>false</useOneX>
</authEncryption>
</security>
</MSM>
</WLANProfile>
标签<name>就是存放ssid,那么key在哪里呢,官网解释如下:
The shared key has been omitted from this sample profile. If you try to use this sample profile to connect to a network, you will be prompted to enter a shared key. You can avoid this prompt by adding a sharedKey child element to the security element immediately following the authEncryption element.
The following snippet shows a sharedKey element that contains an unencrypted key. You must replace the comment <!-- insert key here -->
with the actual unencrypted key before using this snippet in a profile.
<sharedKey>
<keyType>passPhrase</keyType>
<protected>false</protected>
<keyMaterial> <!-- insert key here --> </keyMaterial>
</sharedKey>
注意:
<protected>标签里的布尔值表示了密码是否是加密后的ascii串,如果是纯文本就是false好了。
小提示:
命令行输入> netsh wlan show profile
能够显示当前主机存储那些网络的profile,那么我们就能够通过API获取到此profile.
知道了profile 设置,下面就是连接了。
直接上代码,针对wpa2-psk类型网络,我定义了如下宏方便字符串处理:
#define WPA2_PSK_ELEM1 L"WPA2_PSK1"
#define WPA2_PSK_ELEM2 L"WPA2_PSK2"
#define WPA2_PSK_ELEM3 L"WPA2_PSK3"
#define WPA2_PSK_REPLACE_LEN wcslen(L"WPA2_PSKX")
#define WPA2_PSK L"\
<?xml version='1.0'?>\
<WLANProfile xmlns = 'http://www.microsoft.com/networking/WLAN/profile/v1'> \
<name>WPA2_PSK1</name>\
<SSIDConfig>\
<SSID>\
<name>WPA2_PSK2</name>\
</SSID>\
</SSIDConfig>\
<connectionType>ESS</connectionType>\
<connectionMode>auto</connectionMode>\
<MSM>\
<security>\
<authEncryption>\
<authentication>WPA2PSK</authentication>\
<encryption>AES</encryption>\
<useOneX>false</useOneX>\
</authEncryption>\
<sharedKey>\
<keyType>passPhrase</keyType>\
<protected>false</protected>\
<keyMaterial>WPA2_PSK3</keyMaterial>\
</sharedKey>\
</security>\
</MSM>\
</WLANProfile>"
实在的连接代码:
DWORD dwError = ERROR_SUCCESS;
DWORD dwActualVresion;
dwError = WlanOpenHandle(1, NULL, &dwActualVresion, &wlanHandle);
if (dwError != ERROR_SUCCESS)
{
if (m_pConnecter)
m_pConnecter->Connect(false);
return ;
}
PWLAN_INTERFACE_INFO_LIST pInterfaceList = NULL;
dwError = WlanEnumInterfaces(wlanHandle, NULL, &pInterfaceList);
if (dwError != ERROR_SUCCESS)
{
if (m_pConnecter)
m_pConnecter->Connect(false);
return ;
}
if (pInterfaceList->dwNumberOfItems == 0)
{
if (m_pConnecter)
m_pConnecter->Connect(false);
return ;
}
bool findSsid = false;
PWLAN_AVAILABLE_NETWORK_LIST pAvailableNetworkList = NULL;
for (DWORD cur = 0; cur < pInterfaceList->dwNumberOfItems; ++cur)
{
GUID &guid = pInterfaceList->InterfaceInfo[cur].InterfaceGuid;
dwError = WlanGetAvailableNetworkList(wlanHandle, &guid, 2, NULL, &pAvailableNetworkList);
for (DWORD idx = 0; idx < pAvailableNetworkList->dwNumberOfItems; ++idx)
{
std::wstring wstr;
int nLen = pAvailableNetworkList->Network[idx].dot11Ssid.uSSIDLength;
wstr.resize(nLen, L' ');
int nResult = MultiByteToWideChar(CP_ACP, 0,
(char *)(pAvailableNetworkList->Network[idx].dot11Ssid.ucSSID), nLen,
(LPWSTR)wstr.c_str(), nLen);
if (nResult)
{
if (m_ssid == wstr)
{// find network
findSsid = true;
WlanDisconnect(wlanHandle, &guid, NULL);
wchar_t *xml = NULL;
DWORD access = 0;
DWORD flag = WLAN_PROFILE_GET_PLAINTEXT_KEY;
DWORD ret = WlanGetProfile(wlanHandle, &guid, wstr.c_str(), NULL, &xml, &flag, &access);
if (xml && access == WLAN_WRITE_ACCESS)
WlanDeleteProfile(wlanHandle, &guid, wstr.c_str(), NULL);
else
{// 没有删除权限
if (xml && m_pConnecter)
m_pConnecter->Connect(false);
}
DWORD reason;
std::wstring profile(WPA2_PSK);
size_t idx = profile.find(WPA2_PSK_ELEM1);
profile.replace(idx, WPA2_PSK_REPLACE_LEN, m_ssid.c_str());
idx = profile.find(WPA2_PSK_ELEM2);
profile.replace(idx, WPA2_PSK_REPLACE_LEN, m_ssid.c_str());
idx = profile.find(WPA2_PSK_ELEM3);
profile.replace(idx, WPA2_PSK_REPLACE_LEN, m_passwd.c_str());
ret = WlanSetProfile(wlanHandle, &guid, WLAN_PROFILE_USER, profile.c_str(), NULL, true, NULL, &reason);
if (ret == 0)
{
WLAN_CONNECTION_PARAMETERS param;
param.pDot11Ssid = NULL;
param.wlanConnectionMode = wlan_connection_mode_discovery_secure;
param.strProfile = profile.c_str();
param.pDesiredBssidList = NULL;
param.dot11BssType = dot11_BSS_type_infrastructure;
param.dwFlags = WLAN_CONNECTION_HIDDEN_NETWORK;
//call connect for waiting NotificationCallBack
WlanConnect(wlanHandle, &guid, ¶m, NULL);
ret = WlanRegisterNotification(wlanHandle, WLAN_NOTIFICATION_SOURCE_ACM,
true, this->INotificationCallBack, this, NULL, NULL);
if (ret != 0)
system("pause");
}
else if (ret == ERROR_ACCESS_DENIED)
{
if (m_pConnecter)
m_pConnecter->Connect(false);
}
break;
}
}
}
if (findSsid)
break;
}
if (!findSsid && m_pConnecter)
m_pConnecter->Connect(false);
大概过程是,首先枚举主机的网卡,然后对每个网卡进行获取网络列表,直到找到指定的ssid网络。
上面值得注意的是,调用函数WlanSetProfile时,主机就开始连接网络,调用WlanConnect对于此代码来说只是为了注册回调函数,通知我什么时候网络连接完成。
试想,如果你要开发界面应用,连接过程绝对是不能再界面线程完成的,不然界面会出现几面的卡顿,那么只能在子线程了,通过回调函数通知界面更新。
注册回调函数:
ret = WlanRegisterNotification(wlanHandle, WLAN_NOTIFICATION_SOURCE_ACM,
true, this->INotificationCallBack, this, NULL, NULL);
回调函数如下:
void __stdcall INotificationCallBack(
PWLAN_NOTIFICATION_DATA data,
PVOID context)
{
// 连接过程,该函数被回调多次,最后一次是连接成功则是成功
bool success = false;
WlanConnecter *connecter = static_cast<WlanConnecter*>(context);
if (data->NotificationSource == WLAN_NOTIFICATION_SOURCE_ACM)
{
if (data->NotificationCode == wlan_notification_acm_connection_complete)
{
PWLAN_CONNECTION_NOTIFICATION_DATA pData =
static_cast<PWLAN_CONNECTION_NOTIFICATION_DATA>(data->pData);
if (pData->wlanReasonCode == WLAN_REASON_CODE_SUCCESS)
{
success = true;
}
if (connecter && connecter->m_pConnecter)
connecter->m_pConnecter->Connect(success);
}
}
}
connecter 是需要此服务类的基类:
class IWlanConnecter
{
public:
virtual void Connect(bool success) = 0;
};