65.9K
CodeProject 正在变化。 阅读更多。
Home

使用连接管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (8投票s)

2010 年 7 月 30 日

CPOL

5分钟阅读

viewsIcon

67238

downloadIcon

5072

如何有效地使用连接管理器 API 连接到任意网络。

Connection Manager is connecting

目录

引言

大约每个月,MSDN 移动开发论坛中都会有人问一个类似的问题:“我的移动应用程序应该使用哪个网络适配器?”答案是连接管理器。在本文中,我们将探讨使用连接管理器 API 与任意网络建立通信的各种方法。附带的示例应用程序将列出所有连接管理器条目,并演示如何连接到其中任何一个。

什么是连接管理器?

典型的 Windows Mobile 设备具有许多不同的连接选项,如 WiFi、蜂窝网络、蓝牙、IrDa 等……微软在 Pocket PC 开发的早期就意识到,这将产生一系列变量,任何希望与外部世界通信的应用程序都必须处理这些变量。

  1. 它们中的每一个都以不同的速度通信。
  2. 在任何给定时间,只有一部分可用。如果您在高速公路上,WiFi 可能无法工作。
  3. 它们可能需要付费。某些蜂窝数据计划按兆字节收费。
  4. 有些可能与不同的网络通信。您与打印机的蓝牙连接无法让您访问 Internet。

连接管理器旨在透明地管理所有这些变量,以便应用程序开发人员可以专注于他们想要发送的内容,而不是发送的方式。

您甚至可以通过查看标题栏来了解连接管理器的状态。这些屏幕截图显示了 Windows Mobile 6.1 设备连接到 WAP 网络的各个阶段。

连接管理器已断开连接

Connection Manager is disconnected

连接管理器正在连接到 GPRS WAP 网络

Connection Manager is connecting

连接管理器已连接到 EDGE GPRS WAP 网络

Connection Manager is connected

建立连接

有三种方法可以定义您希望通过连接管理器建立的网络连接类型:

  1. 连接到特定网络 - 您知道您想使用的网络(例如,蜂窝 WAP 网络)。
  2. 连接到特定 URL - 您知道要通信的主机的 URL(例如,https://codeproject.org.cn)。
  3. 连接到特定接口 - 您知道要使用的网络适配器(例如,您的 WiFi 适配器)。

连接到特定网络

这是最简单的情况,因为我们可以使用四种预定义的元网络之一。

  1. IID_DestNetInternet - Internet!(需要完全限定的域名)。
  2. IID_DestNetCorp - 与 Internet 相同,但还支持代理服务器、SOCKS、VPN 和 WINS。
  3. IID_DestNetWAP - 您的基本蜂窝数据网络。
  4. 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 映射过程的完整说明,请参阅 MSDN 上的使用连接管理器 URL 映射CM_Mappings 配置服务提供程序

让我们回顾一下之前的示例,并修改它以支持 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 接口进行通信。有两种方法可以实现这一点:

  1. 按接口名称连接 - 如果您想让用户选择连接接口,这是一种首选方法,因为“我的 AT&T 网络”比看似随机的十六进制字符字符串(即 GUID)更容易让用户理解。
  2. 按接口 GUID 连接 - 此方法涉及的代码量稍少,因此如果您的应用程序以编程方式执行网络选择,则可能更受欢迎。

按接口名称连接

接口名称由 CONNMGR_CONNECTION_DETAILED_STATUSszAdapterName 参数给出。但是,对于某些接口,该参数可能为 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_STATUSdwDestNet 参数给出。由于它已经是 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 )
{
    // ...
}

延伸阅读

本文仅介绍了连接管理器的皮毛。如果您对其他主题感兴趣,我推荐以下文章:

© . All rights reserved.