通过互联网访问你的 Pocket PC
适用于 Compact Framework 的 DynDNS 更新程序。
它做什么?
DynDNS 是众多可以将域名与 IP 地址关联的服务之一。域名只是 IP 地址的一个用户友好的别名。在 ISP 分配的 IP 地址(称为 WAN IP)并非总是相同(动态而非静态)的情况下,如果不了解最新的分配地址,就无法通过互联网找到设备。DynDNS 客户端是一种识别 WAN IP 更改并将预定域名与新 IP 重新关联的软件。然后可以使用该域名通过互联网找到设备。由于 3G/GPRS 连接始终具有动态 IP(至少在澳大利亚是这样),因此需要 DynDNS 类型服务才能通过互联网访问设备。
*注意 – 许多 3G/GPRS 连接也有所谓的“私有 IP”。您需要找到一个允许您的 SIM 卡拥有“公共 IP”的提供商。在澳大利亚,Optus 无需修改即可使用,而 Telstra 如果您愿意花费几个小时在电话上寻找了解您需求的人,则会提供该服务。
用途
- 使用 Pocket PC 作为网关来控制系统(安全/灌溉/照明等)。
- 使用支持 GPS 的 Pocket PC 随时跟踪车辆位置。
- 与“VNC”配合使用,以访问员工 PDA 进行远程协助。
- 还有更多…
背景
我决定发布这篇文章有几个原因。当我开发此代码时,我找不到任何关于 Pocket PC 的 DynDNS 更新程序的参考资料。Code Project 在过去几年中对我帮助巨大,我渴望回馈一些东西。
实现最终目标涉及到克服一些我未曾预料到的障碍。我是一名开发与控制系统接口的软件工程师,过去几年一直专注于 Pocket PC 的使用,不仅将其作为远程访问控制器的设备,而且还将其作为分布式控制系统的“大脑”。
Pocket PC 作为控制/记录设备物超所值。尽管它不提供板载 I/O(有许多 Wi-Fi I/O 设备可用),但仅需大约 500 澳元,您就可以获得一个具有以下功能的设备:
- 实时时钟
- 电池备份
- 用于用户交互的本地 GUI
- Wi-Fi 和蓝牙连接
- 3G/GPRS 连接
- 短信警报
- 专为低功耗设计
- 使用 .NET CF 的丰富编程环境
要求
要使用 CompactDDNS,您需要:
- 一个 DynDNS 帐户和至少一个已创建的主机
- (最多 5 个主机免费)
- 一台带有 WM5 或 WM6 的 Pocket PC 手机
- (可能适用于 PPC2003)
- 一张支持 3G/GPRS 并带有“公共 IP”的 SIM 卡
- Visual Studio 2005 和相关的 Pocket PC SDK
特点
除了 DynDNS 更新程序之外,CompactDDNS 还可以编辑您的 3G/GPRS 连接的相应注册表项,以使其永久保持连接。
最后,我包含了 EXE 文件,但没有包含我用于 Pocket PC 的内部 VNC 服务器的源代码。这个 VNC 服务器运行良好,但尚未实现身份验证。您可以选择从 CompactDDNS 启动 VNC 服务器。我很高兴 Code Project 社区在非商业应用程序中使用此 VNC 服务器。
代码
在为 Pocket PC 开发专业应用程序时,我大量使用了 OpenNetCF 框架,并建议任何认真使用 .NET CF 进行开发的人也这样做。
为了发布这个项目,我重新编写了代码,使其不使用 OpenNetCF 框架。例如,要查找 SIM 卡的 WAN IP,我通常使用 OpenNetCF.Net 程序集中提供的 `Adaptors` 类。但是为了绕过这个要求,我使用了“WhatsMyIP”网络服务。
如果您有权访问 OpenNetCF 框架,我已包含一个备用的“modMain”,其中包含通知图标的创建。启用此功能将使您能够隐藏然后显示 CompactDDNS 应用程序。要使用此功能,请添加对 OpenNetCF.Windows.Forms 程序集的引用,并将主窗体的“`ControlBox`”属性设置为 `true`(使您能够隐藏窗体)。
基本的更新程序代码首先从 XML 文件加载配置(包括上次更新信息),然后 - 如果更新程序已启用 - 它使用“WhatsMyIP”Web 服务检索 WAN IP。
然后将检索到的 IP 与设置文件中的上次更新 IP 进行比较,并且只有当 IP 发生更改时,它才使用新的 WAN IP 更新 DynDNS 服务。更新后,应用程序会定期循环遍历设备上可用的 IP 地址,以确保上次更新的 IP 仍然可用。如果上次更新的 IP 不再可用,更新程序会再次尝试使用“WhatsMyIP”Web 服务获取新的 WAN IP(如果已连接),依此类推。
尽管我用于执行 DynDNS 更新的代码位于“PhoneLib”项目中并以 C# 编写,但我已为 VB 生成了等效代码,并且可以在“*modGlobals*”文件中找到注释掉的代码。
这是执行 DynDNS 更新的函数
public static string UpdateDDNS(string Host, string Ip, string UserID,
string Password, string UserAgent)
{
string ResponseText = "Failed";
System.Net.HttpWebRequest Request = null;
System.Net.NetworkCredential myCred =
new System.Net.NetworkCredential(UserID,
Password);
//This sets mappings to ensure the HttpRequest
//uses the internet connection to update
SetDDNS_Prov(UserID, Password);
string url = "http://" + UserID + ":"+ Password +
"@members.dyndns.org/nic/update?hostname=" + Host + "&myip=" +
Ip + "&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG HTTP/1.0:8245";
Debug.WriteLine("url =" + url);
try
{
// make a Web request
Request = (System.Net.HttpWebRequest)
System.Net.HttpWebRequest.Create(url);
//"Open Control-Compact Watchdog-v1.0";
Request.UserAgent = UserAgent;
Request.Proxy = System.Net.GlobalProxySelection.GetEmptyWebProxy();
Request.Method = "GET";
Request.Credentials = myCred;
System.Net.HttpWebResponse HttpWResponse = (
System.Net.HttpWebResponse) Request.GetResponse();
if (HttpWResponse != null)
{
System.IO.Stream stm = HttpWResponse.GetResponseStream();
System.IO.StreamReader sr = new System.IO.StreamReader(stm);
ResponseText = sr.ReadToEnd();
stm.Close();
sr.Close();
Request = null;
HttpWResponse = null;
stm = null;
sr = null;
}
}
catch (Exception ex)
{
Debug.WriteLine("Update Failed - " + ex.Message);
}
return ResponseText;
}
这是用于映射提供的函数
//Uses CM_Mappings Provisioning to force the connection manager
//to use the internet and not the local network to update DDNS
public static string SetDDNS_Prov(string UserID , string Password )
{
System.Xml.XmlDocument d= new System.Xml.XmlDocument();
//My Way of making strings with lots of quotes more legible
//by avoiding control characters for quotes(Works for VB too)
String provString = "<wap-provisioningdoc>";
provString += "<characteristic type='CM_Mappings'>";
provString += "<characteristic type='900'>"; // This needs to be less than
// 1000 (first default mapping)
provString += "<parm name='Pattern' value='*://" + UserID + ":" + Password +
//"@members.dyndns.org/*'/>";
provString += "<parm name='Network'
value='{436EF144-B4FB-4863-A041-8F905A62C572}'/>";
provString += "</characteristic>";
provString += "</characteristic>";
provString += "</wap-provisioningdoc>";
provString = provString.Replace("'", Convert.ToChar(34).ToString());
Debug.WriteLine("provString = " + provString);
d.LoadXml(provString);
try
{
System.Xml.XmlDocument d2 =
Microsoft.WindowsMobile.Configuration.ConfigurationManager.ProcessConfiguration(
d, true);
return d2.OuterXml;
}
catch (Exception ex)
{
Debug.WriteLine("Error caught at SetDDNS_Prov - " + ex.Message);
}
return "failed";
}
以及 VB 中的相同功能
Public Function UpdateDDNS(ByRef Host As String, ByRef Ip As String, _
ByVal UserID As String, ByVal Password As String) As String
Dim myCred As New Net.NetworkCredential(UserID, Password)
Dim url As String = "http://" & UserID & ":" & _
Password & "@members.dyndns.org/nic/update" & _
"?hostname=" + Host + "&myip=" + Ip + "&wildcard" & _
"=NOCHG&mx=NOCHG&backmx=NOCHG HTTP/1.0:8245"
Dim Request As Net.HttpWebRequest
Dim ResponseText As String = "Failed - Could not connect"
Try
'This sets mappings to ensure the HttpRequest
'uses the internet connection to update
SetDDNS_Prov(UserID, Password)
Request = CType(System.Net.HttpWebRequest.Create(url), _
System.Net.HttpWebRequest)
Request.UserAgent = "Open Control-Compact Watchdog-v1.0"
Request.Proxy = System.Net.GlobalProxySelection.GetEmptyWebProxy()
Request.Method = "GET"
Request.Credentials = myCred
Dim HttpWResponse As Net.HttpWebResponse = Nothing
Try
HttpWResponse = CType(Request.GetResponse(), _
System.Net.HttpWebResponse)
Catch e As Exception
Debug.WriteLine("Inner Exception Caught" & _
" at UpdateDDNS" & e.Message)
End Try
If Not HttpWResponse Is Nothing Then
Dim strm As IO.Stream = HttpWResponse.GetResponseStream()
Dim sr As IO.StreamReader = New IO.StreamReader(strm)
ResponseText = sr.ReadToEnd()
strm.Close()
sr.Close()
Request = Nothing
HttpWResponse = Nothing
strm = Nothing
sr = Nothing
End If
Catch ex As Exception
OnEx(ex)
End Try
Return ResponseText
End Function
'Forces connection manager to use the internet for 'ddns'
Public Sub SetDDNS_Prov(ByVal UserID As String, ByVal Password As String)
Dim d As System.Xml.XmlDocument = New System.Xml.XmlDocument()
Dim provString As String = "<wap-provisioningdoc>"
provString += "<characteristic type='CM_Mappings'>"
provString += "<characteristic type='900'>"
provString += "<parm name='Pattern' value='*://" & UserID & ":" &
Password & "@members.dyndns.org/*'/>"
provString += "<parm name='Network'
value='{436EF144-B4FB-4863-A041-8F905A62C572}'/>"
provString += "</characteristic>"
provString += "</characteristic>"
provString += "</wap-provisioningdoc>"
provString = provString.Replace("'", Chr(34))
Debug.WriteLine("provString = " & provString)
Try
d.LoadXml(provString)
Dim d2 As System.Xml.XmlDocument = _
Microsoft.WindowsMobile.Configuration._
ConfigurationManager.ProcessConfiguration(d, True)
Debug.WriteLine("d2 = " & d2.InnerXml)
Catch ex As Exception
OnEx(ex)
End Try
遇到的问题
除了必须使用 `Net.HttpWebRequest` 和 `Net.HttpWebResponse` 来实现 Web 服务请求之外,用于执行 DynDNS 更新的基本代码几乎在第一次尝试时就奏效了。
然后,毫无理由地,大约一周后它停止工作了。我又花了 24 小时才找到问题的原因,然后又花了两天时间才找到一个合适的解决方案。
我发现因为我是通过 Wi-Fi 调试的,所以我的 Pocket PC 实际上有两个互联网连接:一个通过 3G/GPRS,另一个通过我的 ADSL 路由器。因为我使用 OpenNetCF 库来报告我的 SIM 卡的 WAN IP,所以我假定 `HttpWebRequest` 也正在采用这条路线。
但是,我发现 `HttpWebRequest` 一直在使用我的 ADSL 连接通过 Wi-Fi 进行 DDNS 更新,而它停止工作的那天,我的 ADSL 调制解调器是关闭的。
发现问题原因后,我着手寻找解决方案。我知道 Pocket PC 有一个“连接管理器”,它会尝试确定用于任何网络请求的最佳连接。
在我看来,如果“连接管理器”通过 Wi-Fi 找到互联网连接,那么这对于数据传输来说可能更便宜,因此是一个明智的选择。不合逻辑的是,如果通过 Wi-Fi 没有互联网连接可用,为什么请求没有通过 3G/GPRS 连接进行。
经过大量 Google 搜索后,我发现“连接管理器”使用映射模板来匹配请求的 URL,以确定最适合使用的连接。由于我从未真正弄清原因(因为我找到了解决方案),连接管理器总是希望将我的“工作网络”用于 DDNS 更新 URL 的格式。
经过更多的谷歌搜索,我发现通过“WAP 配置档案”,我可以添加优先级更高的额外映射,其中包括 DDS 更新 URL 的模板。创建这些额外映射后,一切都正常工作。
我不想在这里详细介绍 WAP 配置,但值得注意的是,它在为特定任务定制 Pocket PC 方面非常有用。
我花了很多时间修改我的代码,使其在没有 OpenNetCF 的情况下也能工作,所以这篇文章就到此为止。如果这篇文章引起了很大的兴趣,我将努力写一篇后续文章,更详细地介绍代码(我也会给我的代码添加注释)。
希望您觉得这篇文章有用。
历史
- 09/01/09 - 发布版本 1.0.0。
- 10/01/09 - 将 `doDDNS_Upate` 中调用的 '`UpdateDDNS`' 函数的 `UserAgent` 参数更改为 "Code Project-Compact DDNS-v1.0"。我不小心将其保留为我的产品名称。我应该在文章中提到用户代理。如果您上传了早期版本,能否将其更改为上述字符串,或者使用您自己的格式为 "Company-Product-Version"?我将在下一篇文章中包含 DynDNS 代码的更多详细信息。