现在的位置: 首页 > 综合 > 正文

通过Windows Mobile连接管理器建立网络连接

2013年03月14日 ⁄ 综合 ⁄ 共 12183字 ⁄ 字号 评论关闭

EstablishNetworkWithConnMgr.rar

  原文为Jim Wilson 的 Establishing Network Connectivity with the Windows Mobile Connection Manager

 

 概要 Summary

    本文主要讲述在托管程序中怎样使用连接管理器建立和释放网络连接。本文的重点是关于使用连接管理器建立和断开连接的概念,而不是如何封装连接管理器API。文章的目的在于涵盖概念使能够适用于任何连接管理器托管API。 

 

 适用 Applies to

Windows Mobile 6 Professional

Windows Mobile 6 Standard

Windows Mobile 6 Classic

Windows Mobile 5.0 for Pocket PC Phone Edition

Windows Mobile 5.0 for Smartphone

Windows Mobile 5.0 for Pocket PC 

 

 索引

1. Introduction

2. Accessing Connection Manager from Managed Code

3. Establishing a Connection

4. Conclusion 

 

 介绍 Introduction

     现代Windows Mobile设备包含了许多的网络连接选项,比如Wi-Fi和大量的cellular radios。而且,众所周知,所有Windows Mobile设备能够通过ActiveSync连接桌面计算机访问网络。

    所有这些网络选项提供不同的数据速度,并且,在任何时候,0、1或更多的网络连接可能都是可用的。当一个应用程序需要建立一个网络连接时,Windows Mobile提供了一个公共解决方案 —— 那就是连接管理器,而不必要求应用程序自己遍历所有可用的连接然后选择最合适的连接。

    就象名字表达的一样,连接管理器负责管理设备上所有的网络连接。当应用程序需要建立一个网络连接时,只需要简单的告诉连接管理器需要哪种连接(比如Internet),连接管理器自己会识别哪些连接可用、选择最佳连接并按照需要建立连接。连接管理器甚至可以在有相同连接需求的多个应用程序中共享一个连接。应用程序完全从细节中抽象出来并且无需考虑任何细节地使用连接。

    连接管理器功能强大并提供了大量不同的连接相关的性能。本文主要讲述怎样使用连接管理器建立一个网络连接并且在不需要的时候释放掉。

    虽然本文的例子直接p/invoke连接管理Native API,但是本文主要讲述关于使用连接管理建立和断开连接的概念,而不是如何封装连接管理器API。文章的目的在于涵盖概念使能够适用于任何连接管理器托管API。

 

 通过托管代码访问连接管理器 Accessing Connection Manager from Managed Code  

    连接管理器是Windows Mobile的基础部分,但是当前只以Native API的形式暴露。好消息是大多数API很简单并且能够通过.NET Compact Framework简单地访问。你需要定义一些枚举和结构,但大多数不必去定义,过程非常简单。

注意

    Microsoft Visual Studio 2008下.NET Compact Framework 3.5提供了连接管理器API的管理版本实现。即使你打算使用托管版本API,仍然鼓励你读完本文,因为无论是托管API还是P/Invoke本地API,他们的概念意义是一样的。

【李森 - listen附:Connection Manager wrapper 提及了:他本人向Jim Wilson 通过Email问及了Microsoft Visual Studio 2008下.NET Compact Framework 3.5 连接管理器的封装,Jim Wilson回答说微软放弃了添加连接管理器的封装,而Jim Wilson本人也多次要求微软方面删去本段,但是一直没有修改】

 

   函数Connection Manager Functions

    Connection Manager API由11个函数组成,但是只需要使用其中的5个就能完成建立和释放网络连接的工作。在许多情况下,你的程序可能只要其中的2个函数就可以了。表1显示了这5个函数

函数    

 描述

ConnMgrMapURL

取回指定URL的网络标识(Internet or Work) 。

ConnMgrEstablishConnection

为指定的网络标识选择和建立合适的连接。该方法直接返回,并不等待连接完整。使用ConnMgrConnectionStatus去判断网络状态。 

ConnMgrEstablishConnectionSync

为指定的网络标识选择和建立合适的连接。该方法直到尝试连接完成了才返回。 

ConnMgrReleaseConnection 

释放指定连接,可能会关闭连接(并不保证一定关闭)。 

ConnMgrConnectionStatus 

取回指定连接的状态。 

  表1 Connection Manager functions used to establish and release a network connection

 

    要从 .NET Compact Framework程序中访问这5个函数,需要P/Invoke,如下: 

  1. [DllImport("CellCore.dll")]
  2. static extern int ConnMgrMapURL(string url, ref Guid networkGuid, int passZero);
  3. [DllImport("CellCore.dll")]
  4. static extern int ConnMgrEstablishConnection(ConnMgrConnectionInfo connectionInfo, ref IntPtr connectionHandle);
  5. [DllImport("CellCore.dll")]
  6. static extern int ConnMgrEstablishConnectionSync(ConnMgrConnectionInfo connectionInfo, ref IntPtr connectionHandle, uint dwTimeout, ref ConnMgrStatus dwStatus);
  7. [DllImport("CellCore.dll")]
  8. static extern int ConnMgrReleaseConnection(IntPtr connectionHandle, int cache);
  9. [DllImport("CellCore.dll")]
  10. static extern int ConnMgrConnectionStatus(IntPtr connectionHandle, ref ConnMgrStatus status);

 

   枚举和结构 Connection Manager Enumerations and Structures 

    当使用连接管理器建立连接时,必须指定连接的特性。本地代码中需要使用CONNMGR_CONNECTIONINFO和几组 #define 宏来指定连接的特性。为了使结构和.NET的特性相容,可以用托管的类来实现结构并命名为,比如ConnMgrConnectionInfo。与其象.NET一样使用常量来定义宏,不如使用枚举来显式地定义每组相关的宏。

注意

  如果想查看所有连接管理器native声明,可参见%Program Files%\Windows Mobile 6 SDK\PocketPC\Include\Armv4i 下的connmgr.h头文件

 

    有几个结构依赖于宏,因此首先要定义宏对应的枚举。

 

    第一个枚举对应CONNMGR_PARAM_* 宏集。这些值标识了结构中可用的标准字段,这些字段指定了请求连接中想要的特性。一个连接请求可以指定多个条件,因此,枚举声明应该包含Flags属性。通过表示Flags属性,可以对成员进行位或运算。 

  1. [Flags]
  2. public enum ConnMgrParam : int
  3. {
  4.     GuidDestNet = 0x1,
  5.     MaxCost = 0x2,
  6.     MinRcvBw = 0x4,
  7.     MaxConnLatency = 0x8
  8. }

    

    每个枚举变量就像结构中对应的字段一样拥有相同的名字。下面对于结构定义的讨论将满足每个字段的意义。

  

    下一个枚举对应CONNMGR_FLAG_PROXY_*宏。这些值指定代理服务器的类型,连接管理器使用该类型建立连接。就象ConnMgrParam枚举一样,该声明包含Flags属性。 

  1. [Flags]
  2. public enum ConnMgrProxy : int
  3. {
  4.     NoProxy = 0x0,
  5.     Http = 0x1,
  6.     Wap = 0x2,
  7.     Socks4 = 0x4,
  8.     Socks5 = 0x8
  9. }

 

    下面需要考虑的就是连接请求的优先级。连接管理器负责设备上的所有连接,并尝试服务尽量多连接请求。为了辅助连接管理器决定每个请求的顺序和重要性,每个请求必须指定优先级。连接管理器支持几个不同优先级水平,尽管如此,程序一般只使用几个值。下面的枚举声明显示了通常使用的值: 

  1. public enum ConnMgrPriority
  2. {
  3.     UserInteractive = 0x8000,
  4.     HighPriorityBackground = 0x0200,
  5.     LowPriorityBackground = 0x0008
  6. }

 

 表2提供了每个优先级的描述:

优先级

描述 

UserInteractive  

主动请求连接,用户接口等待连接。该优先级高于大多数其他优先级。 

HighPriorityBackground 

该请求为高优先级,但是应用程序在后台运行,因此并不影响用户接口。

LowPriorityBackground  

该请求仅当更高优先级的程序已经使用了请求路径后才连接。使用该优先级,应用程序可能共享一个已经存在的连接而并不是建立一个新的连接。

  表2 Common Connection priorities

     连接管理器支持了更多的枚举声明,connmgr.h中的CONNMGR_PRIORITY_*宏提供了完整的列表。 在表2中没有提及的优先级值,参考Connection Manager Priority Constants

 

    最后一个枚举是连接状态值。大多数状态值名称无需说明便能理解。对于每个状态值的描述,参见Connection Manager Status Constants或者参见connmgr.h中的CONNMGR_STATUS_*宏。如下:

  1. public enum ConnMgrStatus
  2. {
  3.     Unknown = 0x00,
  4.     Connected = 0x10,
  5.     Suspended = 0x11,
  6.     Disconnected = 0x20,
  7.     ConnectionFailed = 0x21,
  8.     ConnectionCanceled = 0x22,
  9.     ConnectionDisabled = 0x23,
  10.     NoPathToDestination = 0x24,
  11.     WaitingForPath = 0x25,
  12.     WaitingForPhone = 0x26,
  13.     PhoneOff = 0x27,
  14.     ExclusiveConflict = 0x28,
  15.     NoResources = 0x29,
  16.     ConnectionLinkFailed = 0x2a,
  17.     AuthenticationFailed = 0x2b,
  18.     NoPathWithProperty = 0x2c,
  19.     WaitingConnection = 0x40,
  20.     WaitingForResource = 0x41,
  21.     WaitingForNetwork = 0x42,
  22.     WaitingDisconnection = 0x80,
  23.     WaitingConnectionAbort = 0x81
  24. }

 

    所有的枚举定义后,下面要做的就是理解连接请求结构体。可以使用结构或管理类来组织Native结构,一般,类比结构体更流畅,因为类允许提供默认构造函数和成员初始化。上面已经提过了,ConnMgrConnectionInfo管理类代表CONNMGR_CONNECTIONINFO,如下: 

  1. [StructLayout(LayoutKind.Sequential)]
  2. class ConnMgrConnectionInfo
  3. {
  4.     Int32 cbSize;                          // DWORD
  5.     public ConnMgrParam dwParams = 0;      // DWORD
  6.     public ConnMgrProxy dwFlags = 0;       // DWORD
  7.     public ConnMgrPriority dwPriority = 0; // DWORD
  8.     public Int32 bExclusive = 0;           // BOOL
  9.     public Int32 bDisabled = 0;            // BOOL
  10.     public Guid guidDestNet = Guid.Empty;  // GUID
  11.     public IntPtr hWnd = IntPtr.Zero;      // HWND
  12.     public UInt32 uMsg = 0;                // UINT
  13.     public Int32 lParam = 0;               // LPARAM
  14.     public UInt32 ulMaxCost = 0;           // ULONG
  15.     public UInt32 ulMinRcvBw = 0;          // ULONG
  16.     public UInt32 ulMaxConnLatency = 0;    // ULONG
  17. } ;

 

    表3描述了各个字段的意义 


字段  

描述 

cbSize 

类的大小,按照字节计算 

dwParams  

连接管理器参数常量枚举值。 

dwFlags  

连接请求的代理需求 

dwPriority  

连接请求的优先级 

bExclusive 

是否独占连接请求,非0值代表连接不能被其他程序共享 

bDisabled 

指定连接是否断开。非0值代表连接管理器决定连接是否进行,但并不真正执行连接。当连接管理器需要建立连接时,它会将连接状态设置为ConnMgrStatus.ConnectionDisabled。 

guidDestNet  

目标GUID。如果该字段可用,那么dwParams字段必须包含ConnMgrParam.GuidDestNet值。 

hWnd 

接受状态改变消息的窗口句柄,通常为MessageWindow派生类句柄。 

uMsg 

当发送状态改变消息时,发送该消息到hWnd字段代表的窗口。 

lParam 

发送给hWnd字段代表的窗口的状态改变消息中的值,该字段当且仅当hWnd字段被设置了一个可用窗口句柄时才可用。在消息中使用该参数包含应用程序指定的数据。

ulMaxCost  

可接收的最大连接。如果dwParams字段包含 ConnMgrParam.MaxCost 属性,此字段才可用(大多数程序取消该字段)。 

ulMinRcvBw  

可接收的最小带宽。如果dwParams字段包含 ConnMgrParam.MinRcvBw 属性,此字段才可用(大多数程序取消该字段)。 

ulMaxConnLatency  

最大等待时间。如果dwParams字段包含 ConnMgrParam.MaxConnLatency 属性,此字段才可用(大多数程序取消该字段)。

 表3 ConnMgrConnectionInfo class members

 

    为了简化ConnMgrConnectionInfo类的使用,该类定义了一组构造函数负责一些通用情况。第一个是默认构造函数,如下: 

  1. public ConnMgrConnectionInfo()
  2. {
  3.     cbSize = Marshal.SizeOf(typeof(ConnMgrConnectionInfo));
  4. }

    在给Connection Manager functions传递ConnMgrConnectionInfo类参数时,需要指定ConnMgrConnectionInfo类的大小,因此构造函数自动存储cbSize作为类的大小。 

 

    虽然连接管理器类有许多成员,应用程序通常只需要设置目标GUID、优先级,如果需要,还要设置代理。如果包含目标GUDI,那么dwParams字段必须要设置ConnMgrParam.GuidDestNet值。如下:

  1. public ConnMgrConnectionInfo(Guid destination, ConnMgrPriority priority, ConnMgrProxy proxy)
  2.     : this()
  3. {
  4.     guidDestNet = destination;
  5.     dwParams = ConnMgrParam.GuidDestNet;
  6.     dwPriority = priority;
  7.     dwFlags = proxy;
  8. }

 

    方便起见,该类包含了2个其他的构造函数。第一个接受目标GUID和优先级,不需要代理。第二个参数接受目标GUID,并设置优先级为ConnMgrPriority.UserInteractive,该优先级由程序用户接口等待。 

  1. public ConnMgrConnectionInfo(Guid destination, ConnMgrPriority priority)
  2.     : this(destination, priority, ConnMgrProxy.NoProxy) { }
  3.  
  4. public ConnMgrConnectionInfo(Guid destination)
  5.     : this(destination, ConnMgrPriority.UserInteractive) { }

 

 建立连接 Establishing a Connection

    使用Native方法,枚举和结构就可以通过连接管理器建立一个连接了。好消息是困难的工作已经做完了,使用连接管理器是非常容易的事情。建立实际的连接包括两步:确定网络目标标识和发送连接请求。 

 

   确定目标网络标识 Determining a Network Destination Identifier

    建立连接的第一步是标识你的程序是连接到Internet 还是单位网络(Work network)。每个网络目标有一个指定的GUID.Internet的GUID是436EF144-B4FB-4863-A041-8F905A62C572,单位网络(Work network)的GUID是A1182988-0D73-439e-87AD-2A5B369F808B。

    虽然我们都知道每个网络目标的GUID,但大多数程序并不直接使用这些GUID.相反,能够功过ConnMgrMapURL函数确定合适的网络GUID.

    连接管理器压缩了逻辑来确定url请求哪个网络。因此,与其硬编码一个网络GUID,不如简单的传递一个目标url给ConnMgrMapURL函数来返回一个合适的GUID。 

注意

[说明,原文periods在此处翻译为:终结符。比如http:\\www.baidu.com,那么".com"就是periods]
    连接管理器根据目标url中是否包含终结符决定请求Internet还是单位网络(Work network)。如果包含终结符,那么请求连接Internet;如果不包含则链接到单位网络(Work network)。如果你的公司在内部网络地址中使用终结符,那么你就必须把那些地址添加到工作URL异常列表中(Work URL exception list)。进入“Start - setting - connections”页面, 选择Connections图标,接着选择Advanced页面,最后选择Exceptions按钮,就可以添加那些URL到工作URL异常列表中了。

 

    下面的代码演示了使用ConnMgrMapURL去确定不同URL的目标GUID。

  1. string url1 = "http://msdn2.microsoft.com/en-us/netframework/bb495180.aspx";
  2. string url2 = "http://hedgydev02/test/default.aspx";
  3. Guid destination1 = Guid.Empty;
  4. Guid destination2 = Guid.Empty;
  5.  
  6. ConnMgrMapURL(url1, ref destination1, 0);
  7. ConnMgrMapURL(url2, ref destination2, 0);
  8.  
  9. // Write to debug window
  10. Debug.WriteLine(url1);
  11. Debug.WriteLine(destination1.ToString());
  12. Debug.WriteLine("");
  13. Debug.WriteLine(url2);
  14. Debug.WriteLine(destination2.ToString());

 

    在上面例子中,有两个URL,第一个为Microsoft's MSDN网址,显然是在Internet上。另一个URL则指向内部网络上的某个电脑,因此是在单位网络(Work network)上。图一显示在运行上述代码后,Visual Studio输出窗口上显示的信息。就像看到的那样,Microsoft URL 返回Internet GUID (436EF144-B4FB-4863-A041-8F905A62C572),而指向内部网的URL则返回Work GUID(A1182988-0D73-439e-87AD-2A5B369F808B)。 

 

 

图一 The destination network identifiers for an Internet URL and a Work URL 

 

    多亏ConnMgrMapURL函数,这也许是你最后一次见到这些GUID了。以后,你只需让ConnMgrMapURL函数为你生成GUID即可。 

 

   建立连接 Making the Connection 

    在这里,只需使用合适的连接标准并且调用合适的连接管理器函数,你就可以创建一个ConnMgrConnectionInfo类实例了。

   

    当请求连接时,你只需调用ConnMgrEstablishConnectionSync函数,该函数将保持阻塞直到连接过程完成;或者使用ConnMgrEstablishConnection函数,该函数立即返回,调用线程无需等待连接过程完成。如果你的应用程序在后台运行,或者你在后台建立连接,可以使用ConnMgrEstablishConnectionSync函数,因为当函数返回时你就能够知道是否成功地建立了连接。下面的代码演示了如何使用ConnMgrEstablishConnectionSync函数建立一个连接: 

  1. IntPtr _connectionHandle = IntPtr.Zero; // Class-level field
  2. const int _syncConnectTimeout = 60000; // 60 seconds
  3. void DoConnect(string url)
  4. {
  5.     Guid networkGuid = Guid.Empty;
  6.     ConnMgrStatus status = ConnMgrStatus.Unknown;
  7.     ConnMgrMapURL(url, ref networkGuid, 0);
  8.     ConnMgrConnectionInfo info = new ConnMgrConnectionInfo(networkGuid, ConnMgrPriority.HighPriorityBackground);
  9.     ConnMgrEstablishConnectionSync(info, ref _connectionHandle, _syncConnectTimeOut, ref status);
  10.  
  11.     if (status == ConnMgrStatus.Connected)
  12.         Debug.WriteLine("Connect Succeeded");
  13.     else
  14.         Debug.WriteLine("Connect failed: " + status.ToString());
  15. }

 

    注意,前面的代码给ConnMgrEstablishConnectionSync函数传递了一个延时值。你应该提供一个足够大的延时值,因为,在建立连接时,尤其在一个很差的网络时,有时候要花上几十秒时间。如果函数因为时间到了而返回,相比于连接错误,此时的状态值为ConnMgrStatus.WaitingConnection。当你取回ConnMgrStatus.WaitingConnection状态值时,那么如果你提供了足够的延时值,连接就有可能成功。因此,你应该在尝试建立连接时给与足够的延时时间。

 

    为了测试前面的连接模式,我们使用Windows Mobile 6 Professional Device Emulator 和 Cellular Emulator。Cellular Emulator模拟cellular radio,给Device Emulator提供GPRS连接。开始前,你必须连接Device Emulator到Cellular Emulator,具体参照 Cellular Emulator section 里的Developer's Guide to the Arm 。

 

    你需要为模拟器定义GRPS连接,这和使用真实设备连接到服务网络的步骤一样。按照下面的步骤定义GPRS连接:

        1. 在设备模拟器中 “Start - setting”

        2. 在“setting”页面选择“Connections” 页面

        3. 点击“Connections”图标

        4. 然后,选择上面“My ISP”下的“Add a new modem connection”

        5. 将连接命名为GPRS Internet。 该名字对连接行为并不起任何作用。

        6. 从“Select a modem”下拉列表中选择“Cellular (GPRS) ”

        7. 选择“Next”

        8. 保持“Access point name”为空,选择Next

        9. 保持本页面所有字段为空(User name, Password, Domain),选择“Finish”

 

    现在就定义好了Internet GPRS连接。同样,定义一个单位网络(Work network)GPRS连接,和上面的一样,只需在第4步选择“My Work Network”下“Add a new modem connection”,接着在第5步将连接命名为“GPRS Work”即可。

注意

    之所以能将访问点(Access point)和登录信息设置为空,是因为我们通过Cellular Emulator连接GPRS。如果在真实设备上,你就需要将这些字段设置上信息了。

 

    运行之前的代码后,你就会在几秒后看到Cellular Emulator上“Data Channels”下面的列表有一行上显示有连接活动了。

 

图二 The Cellular Emulator with an active GPRS connection 

 

    Cellular Emulator显示连接以后,ConnMgrEstablishConnectionSync函数就会立即返回了。你应该每次都检查返回状态以确定连接是否真的成功。一个成功的连接返回ConnMgrStatus.Connected状态。

 

注意

  要关闭Cellular Emulator的连接,在“Network”下点击“Disconnect GPRS”按钮。

    因为ConnMgrEstablishConnectionSync函数在连接进程完全完成以前一直在阻断进程,所以该连接并不适合在面向用户的程序主线程运行,否则将造成程序界面假死现象。为了避免这种情况,就要使用ConnMgrEstablishConnection函数。ConnMgrEstablishConnection 函数并不等待连接完成就直接返回,它只是初始化连接。调用ConnMgrEstablishConnection函数和调用ConnMgrEstablishConnectionSync函数的操作非常相似,如下: 

  1. Guid networkGuid = Guid.Empty;
  2. ConnMgrMapURL(url, ref networkGuid, 0);
  3. ConnMgrConnectionInfo info = new ConnMgrConnectionInfo(networkGuid);
  4. ConnMgrEstablishConnection(info, ref _connectionHandle);

 

    如上代码,ConnMgrEstablishConnection函数只是初始化连接然后在连接管理器真正建立连接前就直接返回了。为了检测连接是否真的完成,应该使用SystemState类和ConnMgrConnectionStatus函数协同使用来监测。 

 

    下面的代码显示了当连接数发生改变时,SystemState类如何通知应用程序: 

  1. SystemState _connectionsCount;
  2. private void Form1_Load(object sender,

抱歉!评论已关闭.