使用连接管理器
如何有效地使用连接管理器 API 连接到任意网络。
目录
引言
大约每个月,MSDN 移动开发论坛中都会有人问一个类似的问题:“我的移动应用程序应该使用哪个网络适配器?”答案是连接管理器。在本文中,我们将探讨使用连接管理器 API 与任意网络建立通信的各种方法。附带的示例应用程序将列出所有连接管理器条目,并演示如何连接到其中任何一个。
什么是连接管理器?
典型的 Windows Mobile 设备具有许多不同的连接选项,如 WiFi、蜂窝网络、蓝牙、IrDa 等……微软在 Pocket PC 开发的早期就意识到,这将产生一系列变量,任何希望与外部世界通信的应用程序都必须处理这些变量。
- 它们中的每一个都以不同的速度通信。
- 在任何给定时间,只有一部分可用。如果您在高速公路上,WiFi 可能无法工作。
- 它们可能需要付费。某些蜂窝数据计划按兆字节收费。
- 有些可能与不同的网络通信。您与打印机的蓝牙连接无法让您访问 Internet。
连接管理器旨在透明地管理所有这些变量,以便应用程序开发人员可以专注于他们想要发送的内容,而不是发送的方式。
您甚至可以通过查看标题栏来了解连接管理器的状态。这些屏幕截图显示了 Windows Mobile 6.1 设备连接到 WAP 网络的各个阶段。
建立连接
有三种方法可以定义您希望通过连接管理器建立的网络连接类型:
- 连接到特定网络 - 您知道您想使用的网络(例如,蜂窝 WAP 网络)。
- 连接到特定 URL - 您知道要通信的主机的 URL(例如,https://codeproject.org.cn)。
- 连接到特定接口 - 您知道要使用的网络适配器(例如,您的 WiFi 适配器)。
连接到特定网络
这是最简单的情况,因为我们可以使用四种预定义的元网络之一。
IID_DestNetInternet
- Internet!(需要完全限定的域名)。IID_DestNetCorp
- 与 Internet 相同,但还支持代理服务器、SOCKS、VPN 和 WINS。IID_DestNetWAP
- 您的基本蜂窝数据网络。IID_DestNetSecureWAP
- 需要身份验证(PAP、CHAP 等...)的蜂窝数据网络。
注意:使用预定义的 GUID 时,请务必在包含 connmgr.h 之前包含 InitGuid.h,否则会遇到链接器错误。
error LNK2001: unresolved external symbol IID_DestNetInternet
我们有两种方式可以建立连接:ConnMgrEstablishConnectionSync()
和 ConnMgrEstablishConnection()
。同步函数将阻塞直到连接建立或超时。异步版本将在连接完成后向指定的 HWND
发送消息。为简单起见,我们将在本示例中使用 ConnMgrEstablishConnectionSync()
。
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
// specify the connection destination GUID
info.guidDestNet = IID_DestNetInternet;
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
连接到特定 URL
这是一个相当常见的情况:您知道要连接的主机,并且不在乎如何到达。为此,我们将使用 ConnMgrMapURL()
来要求连接计划程序计算到给定 URL 的最佳路由。使用默认的连接管理器设置(即,没有配置特定路由),以下 URL 模式将引导您到指定的元网络目标:
模式 | 目标 | 示例 |
---|---|---|
*://*.*/* | IID_DestNetInternet |
https://codeproject.org.cn |
*://*/* | IID_DestNetCorp |
http://codeproject |
wsp://*/* | IID_DestNetWAP |
wap://codeproject |
wsps://*/* | IID_DestNetSecureWAP |
wsps://codeproject |
让我们回顾一下之前的示例,并修改它以支持 URL 映射。
const wchar_t* url = L"https://codeproject.org.cn";
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
// use the connection planner to map our URL to a destination network
if( S_OK == ::ConnMgrMapURL( url, &info.guidDestNet, NULL ) )
{
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
}
使用特定接口连接
当您或您的用户知道他们想使用的接口时,此方法非常有用。例如,即使有桌面直通接口可用,您也肯定希望通过 WiFi 接口进行通信。有两种方法可以实现这一点:
- 按接口名称连接 - 如果您想让用户选择连接接口,这是一种首选方法,因为“我的 AT&T 网络”比看似随机的十六进制字符字符串(即 GUID)更容易让用户理解。
- 按接口 GUID 连接 - 此方法涉及的代码量稍少,因此如果您的应用程序以编程方式执行网络选择,则可能更受欢迎。
按接口名称连接
接口名称由 CONNMGR_CONNECTION_DETAILED_STATUS
的 szAdapterName
参数给出。但是,对于某些接口,该参数可能为 NULL
。在这种情况下,请使用 szDescription
参数。有关检索此结构的技巧,请快进到读取所有连接的状态。
一旦知道了要连接的接口名称,就可以使用 ConnMgrMapConRef()
API 将该名称映射到 GUID,如下所示:
// Get the interface you want to connect with. This can
// be a programmatic choice or a user selection.
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetDesiredInterface();
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
// Get the destination meta-network GUID for the adapter with the given name. If the
// adapter doesn't have a name, the description usually works. If the adapter is a proxy,
// be sure to set the correct reference type.
if( S_OK == ::ConnMgrMapConRef(
( CM_CONNTYPE_PROXY == status->dwType ) ? ConRefType_PROXY : ConRefType_NAP,
( NULL != status->szAdapterName ) ? status->szAdapterName : status->szDescription,
&info.guidDestNet ) )
{
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
}
按接口 GUID 连接
特定接口的目标网络由 CONNMGR_CONNECTION_DETAILED_STATUS
的 dwDestNet
参数给出。由于它已经是 GUID 格式,因此我们无需使用映射函数之一。我们将直接使用它。
// Get the interface you want to connect with.
// This can be a programmatic choice or a user selection.
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetDesiredInterface();
CONNMGR_CONNECTIONINFO info = { 0 };
info.cbSize = sizeof( CONNMGR_CONNECTIONINFO );
info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
// allow connection manager to use any proxy it needs to make the connection
info.dwFlags = CONNMGR_FLAG_PROXY_HTTP |
CONNMGR_FLAG_PROXY_SOCKS4 |
CONNMGR_FLAG_PROXY_SOCKS5 |
CONNMGR_FLAG_PROXY_WAP;
info.guidDestNet = status->dwDestNet;
DWORD status = 0;
HANDLE connection = NULL;
// Establish the connection. hr will either be E_FAIL or S_OK. Extended error
// information is stored in the status variable.
HRESULT hr = ::ConnMgrEstablishConnectionSync( &info,
&connection,
30000,
&status );
if( S_OK == hr )
{
// use the connection wisely, then release it when you are finished
::ConnMgrReleaseConnection( connection, 1 );
}
读取所有连接的状态
状态存储在 CONNMGR_CONNECTION_DETAILED_STATUS
结构的链表中,该链表可以通过 ConnMgrQueryDetailedStatus()
函数检索。我有一个我喜欢的用于此目的的构造,我将分享。它提供了强大的异常保证,即它不会泄漏内存或在失败时改变程序状态。请注意,最后,而不是将内部缓冲区完全复制到提供的缓冲区,我们只进行交换,这效率更高。
/// Populate the given buffer with the current status of all adapters registered
/// with connection manager.
/// Returns NULL on failure.
const CONNMGR_CONNECTION_DETAILED_STATUS* GetAdaptersStatus( std::vector< BYTE >* buffer )
{
std::vector< BYTE > int_buffer( sizeof( CONNMGR_CONNECTION_DETAILED_STATUS ) );
DWORD buffer_size = int_buffer.size();
CONNMGR_CONNECTION_DETAILED_STATUS* status =
reinterpret_cast< CONNMGR_CONNECTION_DETAILED_STATUS* >( & int_buffer.front() );
HRESULT hr = ::ConnMgrQueryDetailedStatus( status, &buffer_size );
if( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == hr )
{
int_buffer.resize( buffer_size );
status =
reinterpret_cast< CONNMGR_CONNECTION_DETAILED_STATUS* >( & int_buffer.front() );
hr = ::ConnMgrQueryDetailedStatus( status, &buffer_size );
}
if( S_OK == hr )
{
buffer->swap( int_buffer );
return status;
}
return NULL;
}
// retrieve the current status.
std::vector< BYTE > buffer;
const CONNMGR_CONNECTION_DETAILED_STATUS* status = GetAdaptersStatus( &buffer );
// iterate over the CONNMGR_CONNECTION_DETAILED_STATUS linked list
const CONNMGR_CONNECTION_DETAILED_STATUS* cur = status;
for( cur; NULL != cur; cur = cur->pNext )
{
// ...
}
延伸阅读
本文仅介绍了连接管理器的皮毛。如果您对其他主题感兴趣,我推荐以下文章: