wap开发

2001 年 7 月 12 日

如果您的企业已经依赖于使用多层的企业 Java 应用,那么您也许比想象中更接近无线科技的未来。通过描述一个样本应用,Aashish Patil 向您展示了如何用最少的人力物力把您现有的企业 Java 基础构件连接到无线网络中去。使用现有的 EJB、修改过的 Servlet 以及新的 WML 和 WMLScript 页面会使这个过程产生一个飞跃。如果您的企业已经依赖于使用多层的企业 Java 应用,那么您也许比想象中更接近无线科技的未来。通过描述一个样本应用,Aashish Patil 向您展示了如何用最少的人力物力把您现有的企业 Java 基础构件连接到无线网络中去。使用现有的 EJB、修改过的 Servlet 以及新的 WML 和 WMLScript 页面会使这个过程产生一个飞跃。

无线应用协议(Wireless Application Protocol,WAP)可以提高一个企业现有 Web 体系结构的含金量。如果您已经使用了企业 Java 应用,您可以容易地将它们与 WAP 服务集成,这样可以为移动工作群体带来有用的数据和功能。在这篇文章里,我会涉及到使用与 WAP 相关的 J2EE 的基本要素,然后创建一个 WAP/企业 Java 样本应用,以展示您如何把自己的 EJB 连接到无线网络中去。

背景:J2EE 和 WAP

在阅读本文前,您应该对 Java 2 平台,Enterprise Edition(J2EE)体系结构有个基本的了解。您可以通过下面的 参考资料章节找到关于 J2EE 的更多信息的链接。作为一个回顾,下面是一张为台式客户机设计的典型 J2EE 应用的示意图。


图 1 ― J2EE 应用结构
J2EE 应用结构
 

在图 1 中,包含 JavaServer Page(JSP)和 Servlet 的那一层负责生成动态 HTML 页面。而在 WAP 应用中,这一层将生成动态的无线标记语言(Wireless Markup Language,简称 WML)页面。因此,为了转换一个标准的 J2EE 应用使之为移动设备所使用,您将不得不编写新的 JSP,并且在某些情况下,还要编写新的 Servlet。企业 JavaBean(EJB)保持不变,因 为它们与数据表现无关。

什么是 WML?

正如 Web 浏览器显示 HTML 编码的数据一样,支持无线标记语言(WAP)的设备显示 WML 编码的数据;另外,正如 Web 开发人员使用 JavaScript 把脚本功能嵌入到 Web 页面一样,设备开发人员使用 WMLScript 把同样的功能嵌入到 WML 页面中。WML 是 XML 的一个子集,而对于精通 HTML 或其它标记语言的人来说它看起来很眼熟。WML 有一个独一无二的特征需要牢记:它像 一盒卡片;一个单一的 HTML 文档显示成一个单一的 Web 文档,而一个单一的 WML 文档可以包含很多卡片。WAP 设备的屏幕一次只能显示一张卡片。关于一些 WML 和 WMLScript 的链接可以参阅下面的 参考资料章节。

有些人认为 Servlet 无需更改,或者说:只要把 Servlet 的输出简单地重定向到生成动态 WML 页面的 JSP 上就已经足够了。然而,Servlet 不能区别从台式机和从 WAP 设备发来的请求;既然 WAP 应用可能无法实现基于 Web 的体系结构的所有功能,所以在这方面并没有混淆的地方,这一点很重要。也正由于此,开发人员通常为 WAP 应用设计新的 Servlet。然而在大多数情况下,这些 Servlet 与那些在基于 Web 的体系结构上提供类似功能的 Servlet 非常相似。

在图 1 中没有出现但对 WAP 应用又很重要的另一个组件是 WAP 网关。这个组件负责 WAP 栈和 Internet 栈之间的相互转换。

图 2 是图 1 的改进版,显示了使用 WAP 设备作为客户端的 J2EE 应用的结构:


图 2 ― WAP/J2EE 应用的结构
WAP/J2EE 应用的结构
 

按照图示,所有自 WAP 客户端到 Web 服务器的请求必须通过 WAP 网关发送。尽管 WAP 网关也可以作为放置 WML/WMLScript 页面的 WAP 服务器,但使用 Web 服务器来放置这些页面更为方便。

有很多 WAP 网关的部署方法。对于多数 WAP 应用来说,网关或由 ISP 部署,或由提供这个应用的公司来部署。后者更为安全,我们以后会解释;然而,如果用户要求在他们的 WAP 设备上进行多用途的网络访问,一个内部的 WAP 网关会很不方便。大多数非 ISP 不希望他们的网关被用来访问他们自己站点以外的其他站点;因此,为了访问其他站点,用户将不得不使用 ISP 网关。但对于被 WAP 客户端使用的每一个网关来说,用户都必须定义一个不同的连接,正如 Windows 98 的拨号网络一样 ― 而且在每个设备上,这样连接的数目通常是有限的。这就增加了用户的不便性,并且在访问一个站点时造成 WAP 设备中的连接阻塞。





 
回页首


 

WAP 应用设计的考虑事项

当使用 WAP 时,一个习惯为台式客户机编写 J2EE 应用的开发人员会遇到一些新的挑战。以下是在构建 WAP 应用时您也许会碰到的一些问题。

我可以在屏幕上显示几行信息?
事实上,对显示多少行没有特别限制,只要不超过面板的最大尺寸就行(随设备的不同而不同)。然而,为了避免太多滚屏,每屏(即卡片)5 至 7 行最佳。

我应该考虑哪些安全问题?
一些电话不支持使用 POST 方法发送表单数据。因此,用户名和密码必须通过 GET 方法发送。在 WAP 网关上,如果日志功能被激活并且请求已被记录,管理员就有能看到用户名和密码。如果网关是由 ISP 或其它第三方提供的,这个问题就会特别突出。

即使一个安全的连接也不能完全消除安全隐患。那些发送到 WAP 网关的数据使用 WTLS(Wireless Transport Layer Security)加密,它使用与标准 TLS 相同的算法。然而,发送到 WAP 网关的数据是二进制的编码格式(对 WAP),所以这些加密后的数据必须用 TLS 解密和再加密以适用于因特网。经过一段时间以后,敏感数据在 WAP 网关上以明文的形式出现。黑客则会在适当的时刻,将内存中的信息转储出来,进而成功地访问这些敏感数据。

按照注释,解决该问题的一种办法是在自己公司(而不是在 ISP)设一个 WAP 网关。在这种情况下,一个可信的人可以操作网关,并且可以关闭日志功能。

您也可以用 WMLScript 来编写自定义的加密算法,以对客户端的用户名和密码进行加密。这只有在使用简单的算法时才有可能实现;在支持 DES 类的算法上,WMLScript 不够强大。

我怎样保持 Session?
WAP 客户端不支持 Cookie。这样,当用户在您的站点的不同页面之间穿梭时,为了在服务器端保留关于客户端的信息,在向服务器发送每个请求的同时,一个 Session ID 必须被当作参数传递。Session ID 的参数名根据 Servlet 引擎的不同而不同。

有时,缺省的 Session ID 长度很大幅度地增加了每个请求的长度。结果导致客户端或 WAP 网关可能将此请求看作一个无效的 URL 而拒绝。这样有必要缩短 Session ID 的长度。请查看一下您正在使用的 Servlet 引擎的说明文档中关于 Session ID 参数名的部分。如果您碰到过无效 URL 的错误,这个说明文档也应提供有关缩短 Session ID 值长度的指南。





 
回页首


 

构造样本应用

XYZ Ltd. 是一家生产 PDA,可佩戴的计算机,及其它普及计算设备的公司。公司的销售人员拜访客户,提供 XYZ 产品的现场演示;某些演示要求销售人员必须跑很远的路去客户那里。那么在路上,他们是怎么收到客户列表和其它重要数据的呢?

为此使用电子邮件会需要体积较大且昂贵的便携式电脑或无休止的 Internet caf?s 的搜索;在客户端使用传真机则更不切实际。取而代之的是 XYZ 的销售人员会通过支持 WAP 功能的设备接收数据,例如手机或 PDA。使用移动设备,销售人员能在拜访客户时向公司提供及时的反馈。公司就能马上安排给客户及时发货并维护目前的销售统计信息。

我们的应用有两个主要目标。首先,我们流动的销售人员应该能使用它在 WAP 设备上查看客户列表。第二,如果一个客户希望买货,那么销售人员应能使用设备来下订单。此外,任何 WAP 应用的一个重要目标应该是减少用户必要的按键数目。由于受手持设备的用户界面限制,用户需要输入的数据量应控制在最少。

这是一张显示我们系统的体系结构的流程示意图


图 3 ― 应用流程示意图
应用流程示意图
 

用户首先必须登录以访问系统;然后他们能浏览客户列表和每个客户的详细信息。如果他们希望为某一特定的客户下订单,那么系统会提供他们一个产品列表,他们可以从中为该客户选择一个特定的产品。

在本文余下的大多数内容中,我们会讨论实现该应用的 Servlet 和 JSP 代码,并会考察 JSP 和 Servlet 一起工作的方式。关于每个 JavaServer Page 的讨论还配有图解,显示了 JSP 在设备屏幕上的输出。

清单 1, Login.jsp 接受用户名和密码,并把它们作为参数来调用 LoginServlet 。对这个和其它所有的 JSP 来说,MIME 类型都应被设置成 text/vnd.wap.wml 类型。在传递请求的同时,上面的 Login.jsp 还传递了一个叫 SessionID 的参数。它必须与每个传送到服务器的请求一起传递。参数名 SessionID 是一个占位符;请参考应用服务器的说明文档,找到适用于您特定的应用服务器的正确的参数名。Java 方法 HttpServletResponse.encodeURL(String URL) 自动添加 Session ID;在我们的应用里,这已经被广泛地使用在 Servelet 中。


登录系统
 

验证空白的输入域时会出现问题。在 input 标记里有一个属性,它让您使输入域不为空:

<input name="name" type="text" emptyok="false"/>

 

一个手机用户必须访问各个独立的对话框屏幕去输入数据。问题出现了,因为用户宁愿选择直接访问下一盒或下一张卡片而不愿通过对话框屏幕去输入数据。一个用户面对如图 4 所示的屏幕时也许会遗漏密码并揿下 NEXT。

一个开发人员可以通过使用 WMLScript 的验证来避免这个问题的发生(通过使用 onclick 事件)。然而,直到输入一个值到输入框以后,您传递到 WMLScript 函数的代表输入域值的那个变量才开始被初始化。因此,若无密码键入,传递到该函数的是未初始化的变量和脚本错误结果。这个问题的解决方法是在服务器端验证所有的输入域。

清单 2, LoginServlet 是我们问题的解决方案:它可以认证销售人员,并把他记录在系统中。它也可以在服务器端为销售人员创建一个 Session。代码块上的注释指明了在哪里这些操作会被执行。一旦成功登录,设备显示如清单 3 所示的主菜单( MainMenu.jsp )。

如图 5 所示,该文件将两个链接显示在设备屏幕上。其中第二个终止了当前的 Session;第一个指向当前的客户列表。在当前版本的程序流程中,销售人员必须在开始任何销售交易之前先从列表中选择一个客户;有关销售产品的列表只能在后继的屏幕上显示(后面将会讨论到细节)。也有其它可能的程序流:举例来说,用来直接将用户送到产品列表的链接可以被加到主菜单中。但是,您不应该在任一菜单中提供太多链接,否则支持 WAP 的设备的小屏幕会因此而变得混乱不堪。


Login.jsp
 

在图 5 的主菜单中揿下 View Clients 将调用清单 4 中的 ClientViewServlet ,它抽取销售人员将要拜访的客户列表。代码上的注释说明了 Servlet 怎样从客户端上找到该信息。接着 Servlet 将列表放到 Session 对象中并调用 ClientList.jsp 。(这里和下一段中提到的 Session 对象是来自于 Java servlet 包中的 HttpSession 类。)

清单 5, ClientList.jsp 抽取由 ClientViewServlet 放置在 Session 中的客户列表;它显示了客户的姓名,但不是完整的详细信息(请参见图 6)。当选择一个用户时,销售人员则被定向到 ClientDetails.jsp 。


Login.jsp
 

注意:显示客户列表的任务由三个独立的部分完成 ― ClientViewServlet 、 ClientList.jsp 和 ClientDetails.jsp 。这样设计的原因是什么呢?

  1. 大多数 J2EE 架构的权威人士建议 JSP 不应该直接访问 EJB;而应使用诸如 Servlet 的中间件来进行与 EJB 的交互。 ClientViewServlet 访问 EJB 并获得客户列表。
  2. 这个应用本可以如此设计,这样所有的用户信息都会包含在一个单一的 WML 文档中。在这个体系结构中,客户列表包含在 WML 盒中的一张卡片上,而单个客户的详细信息会包含在同一盒中的不同卡片上。不过该单一文档可能包含太多数据,以至于对一个低带宽的 WAP 设备来说不能立刻下载。如果客户数目过于庞大,所生成的数据总量很容易超过 WML 卡片盒所允许的最大容量。(最大容量随设备不同而有所区别;如 Nokia 7110 的最大编译卡片盒容量为 1.3 KB)。因此我们使用两个 JSP: ClientList.jsp ― 显示客户列表,还有 ClientDetails.jsp ― 显示单个客户的详细信息。

清单 6, ClientDetails.jsp 接受客户数组的索引号作为参数,其中索引号在 Session 中出现。接下来它抽取所选客户的详细信息并显示。如果销售人员希望为该客户下订单,他只要揿下 Items 按钮。这会调用清单 7, ItemListServlet ,并且显示该订单的可选产品。


Login.jsp
 

清单 7, ItemListServlet 抽取销售人员可以销售的产品列表,并将列表置于 Session 中。然后它调用清单 8, ItemList.jsp

清单 8, ItemList.jsp 从 session 中抽取产品列表并显示产品名称。销售人员可选择一个产品去订货并揿下 Order 按钮去调用 PlaceOrder.jsp 。数组中所选产品的索引被作为参数送到清单 9, PlaceOrder.jsp


Login.jsp
 

PlaceOrderServlet 从 Session 中获得销售人员、客户及产品的 ID。接着通过创建新的 Order Entity EJB 可以产生一个新的订单。成功的下单显示了订单的 ID 和下订单的时间。

在这一版本的应用中,销售人员在完成交易后的唯一选择便是返回主菜单(请参见图 9)。您也可修改代码以便使用户返回到客户或产品列表。


Login.jsp
 

如果注意观察,您会发觉销售人员只输入两次数据:登录时和为客户输入购买产品数量时。





 
回页首


 

关于代码

附带文件包含本文所有的 JSP 和 Servlet 代码,也包含必需的 EJB 代码。EJB 的 jar 文件和部署描述符也一起包含在内。所有屏幕截图均来自 Nokia WAP 模拟器 2.0 版。





 
回页首


 

结论

就像前面提到的那样,WAP 应用提供了非常好的增值服务。一个孤立的 WAP 应用是不可取的。然而,这样一个应用无需花费很多财力人力就可以方便地集成到一个现有的 Web 应用体系结构中去。您所需要的唯一新硬件是一台机器,以及用于 WAP 网关的软件;如果您使用的是自己 ISP 的网关,那么这项开销也可省去了。

现有的 HTML 页面需要被转换成 WML。然而,WML 不像 HTML 那么复杂,因为它不支持 HTML 的许多功能。因此,这并不是一件费时的工作。

WAP 也支持无线 BitMap(WBMP)格式的图片。然而,使用 WAP 设备的用户在连接时间上花费了不少钱,他们更感兴趣的是直接有效的信息而非奢华的界面。除非图片本身能传递信息,否则提供快速的信息比占用带宽和时间来传输图片会更好。

最后注意事项:尽管模拟器可以提供测试 WAP 应用的良好环境,但只有当它配合已部署好的 WAP 网关,运行在所有可能的目标 WAP 设备上时,WAP 应用才算作真正意义上的被完全测试过了。所有动态生成的页面在网关上被编译。因此,有必要知道您的网关支持哪些版本的 WAP。如果网关编译器只使用 WML 1.1,那么用 WML 1.2 编写的页面是毫无用处的。



 

参考资料



 

关于作者

 

Aashish Patil 不久前刚获得印度 Mumbai 的 Thadomal Shahani 工程技术学院的计算机工程系学士学位。在 Tata Consultancy Services,他作为实习生完成了一个关于支持 WAP 的股票交易的项目;本文是该项目的一个直接部分。今年秋季,他将赴 Southern California 大学攻读计算机科学系的硕士学位。可通过 ash01@vsnl.net 联系 Aashish。



 

WAP经验总结

包括WAP1.2和WAP2.0,包括移动和联通,对各款手机对WML和XHTML支持的一个总结

Nokia 3310 对do的提交不会显示确定,就是对软按键支持有问题;
——————————————-
NEC N800
对input输入框有format等多余属性时,则不能输入字符;
字符集:我实际测试非常奇怪,GET的居然UTF-8和GB2312都可以;
支持页面内CSS ,支持背景图;
支持XTHML,WAP2.0;
对图片链接选中后很明显,就是难看些,土黄色的外框;
支持滚动字
支持背景音乐
支持表格
——————————————-
三星SCH-X859(CDMA)对WAP2.0支持很好的,对link href=”images/style.css” mce_href=”images/style.css” rel=”stylesheet” type=”text/css” 的写法支持,支持背景图片,支持背景音乐等。对图片链接选定后有一个不太明显的框但是能感觉到选中了;
这款手机的*键和#键是手机固化了的向上向下,对于联通的规定没法执行了;
——————————————–
Nokia 6681 (WAP2.0)对link href=”images/style.css” mce_href=”images/style.css” rel=”stylesheet” type=”text/css” 写法不支持,对图片链接选择没有任何反应,看不出是选中了还是没选中,得点进去才知道。
Nokia6670 和Nokia6681显示效果一样!6670支持的编码是UTF-8(传输未指定也是UTF-8,不是8859_1);
浏览器:Series60 版本2.6(显示的效果真不错~)
Nokia7610
不支持背景音乐()
支持滚动文字
——————————————–
Moto E680 (WAP2.0)不支持背景图的,无论加CSS还是不加CSS。这个浏览器V2.2对页面的显示效果不是很好,连梦网都显示的很烂~而且对链接颜色未选中是蓝色选中是红色,很难看~对图片链接周围加一圈小点,选中很不明显。
——————————————–
索爱 S700c 不支持背景图片无论那种方式写~ 显示字体大些~
——————————————–

关于特殊字符:
Nokia 3100 对 ※(&#x203b;) 字符显示 □

索爱K700C: 字符显示 □
——————————————–
关于测试:
◇最好的测试手机:NEC-N800 为什么说最好呢,因为这个手机可以直接查看地址,直接刷新地址;而且用来浏览wap页面看起来也比较舒服,而且兼容性能比较好;
◇最不好的测试手机:NOKIA 3100 为什么说最不好呢?因为这个手机,不能直接查看地址,必须保存书签才能看,兼容性差,缓存又小,经常出些莫名其妙的问题;屏幕也小;
——————————————–
关于UA(手机号)问题:
正常的话都拿到UA的,手机号不是一定能拿到的;
2006-04-19 更新:
广东:拿不到手机号,拿不到UA;
上海:拿不到手机号,能拿到UA;
北京:拿不到手机号,拿不到UA;

就算你在上海用北京的卡访问WAP,走的还是上海的网关,必须拿北京的卡在北京访问,才会有效果;我试过用广东的卡在上海访问wap还是能拿到UA的。
——————————————–
关于session问题:
◇确实有些手机是不支持session的,这个现象是越来越少了,但是有可能是有的网关不支持session,这个问题可以通过程序URL后带session id来解决;
◇如果必须使用session则页面中的链接url需要用URLEncode保证该页面的通用性;
◇使用session 会造成消耗大量服务器资源;
——————————————–
关于重定向问题:
◇用WML的ontime重定向,存在几个timer的name相同的页面相互跳转,手机会有cache,而同一个timer只工作一次,也就是说,跳一次就再也不跳了。解决办法就是起不同的name ,或者干脆去掉这个name属性;

◇移动,发现采取(response.sendRedirect)重定向到计费页的时候,会丢失一些移动MISC平台带的参数,导致弹不出点播计费页来,尤其是直接跳转到计费地址。
——————————————–
关于手机编码:
◇大部分手机支持UTF-8编码的,但是也有部分手机支持GB2312的编码的;
——————————————–
关于CSS:
◇并不是所有手机都支持CSS的,有的支持link的嵌入,比较多的支持直接写在页面里(移动推荐),还有一点非常重要,就是CSS里面一定都要是小写,我试过大写不起作用对某些手机(不要相信Opera).
——————————————–
关于图片链接:

◇wap1.2和wap2.0都支持图片链接的;
图片周围最好加一圈白边,这样选中效果比较明显;
——————————————–
关于移动:

移动全网:

2006-02-06

1 直接访问计费地址:不会弹出移动计费确认页,不会计费;
2 直接访问反向订购地址(wap.monternet.com) 不会弹出订购确认页,不发送订购关系;
3 直接访问反向订购地址(wap.monternet.com),SPURL为计费地址 不会弹出订购确认页,弹出移动计费确认页,计费,第一次点播发送订购关系;
4 直接访问反向订购地址(wap.monternet.com),SPURL为其他地址(除计费地址) 不会弹出订购确认页,不发送订购关系;然后接着访问计费地址 弹出移动计费确认页,计费,第一次点播发送订购关系;
5 有订购关系后,直接访问计费地址 不会弹出移动计费确认页,不会计费;
6 有订购关系后,通过我的梦网书签访问,访问计费地址,弹出计费确认页,计费;
7 有订购关系后,通过梦网频道访问,直接登陆是一样的。
8 有订购关系后,取消订购,成功!
9 通过梦网频道登陆效果和直接登陆是一样的。
10一个业务无论你是先订包月后订按次,还是先订按次后订包月,在我的梦网(收藏夹)显示的顺序都是 按次 包月。

只有订购过的用户,移动才会引导到MISC平台里来,否则直接导向SP的链接;

移动上线测试:

◇有专门的测试入口,测试手机号;
◇测试业务代码有8位的有10位的,上线后都是8位的;
◇在业务里不要有直接重定向(程序)到计费地址的写法,否则会丢失移动参数导致不能扣费,建议必须跳转的地方就采用wml的timer跳,xhtml mp用

<meta http-equiv="refresh" content="0;URL=http://www.xxx.com/" />
采用refresh重定向如果程序里设置了背景,那么将会出现一个带背景的空白屏幕一小会,可在跳转的时候将背景去掉;采取timer跳转看起来比较正常;

移动MISC返回错误:

◇[对不起,因服务提供商900651通讯故障,服务暂时无法完成。]大多是SP服务器/程序的问题,有一次我SPURL写错了结果订购的时候总出现这个错误;
◇[对不起,服务器繁忙。请您返回梦网首页。]。如果是订购的时候出现,通常是provision的处理时间过长;
◇[服务暂时不可用,请稍后再试。] 这个不清楚,但我碰到过;
◇[鉴权失败。]有一次上线测试没用指定的测试手机号访问上线测试业务出现该错误;
◇[您暂时不能订购该业务。] 哪是您卡里没钱了,不过也有可能是个假象,有时候出现这个我以为是手机卡没钱了(有些允许透支的),结果等了一会又正常了。
◇[对不起,请求参数错误,服务暂时无法完成。]这个一般在反向订购的时候带的参数不对可能出现;

广东移动:
新上线业务先免费一段时间,但是申报的时候会有计费地址等;
河南移动:
新上线的业务全部免费,三个月后按使用用户量再申报计费;
——————————————–
关于WML:
关于WML:
◇如果一个页面看样子没什么错误,可以正常显示,就是在页面的底部 将所有的wml都打印出来,然后最下面还奇怪的出现一些字符,比如/00/00/00 …什么,这个百分之百是 多了一个〈/p〉标记
——————————————–
关于铃声:
◇MIDI是一种控制手机里面音源的数据。相当于乐谱的音符,本身没有声音。必需依靠手机里面的音色才能演奏。我们必须编写关于每首歌的每个音符。输入各种乐器的MIDI数据来控制手机上的音色。目前手机还没有人声音源。只有AMR、MP3等波形格式才能发出人声。10首同一歌曲的音符是同样的,只是音色上的区别,在一部手机里面只能发出一种声音。
◇ 一些手机对不支持的铃声格式表现不同,比如对于wma文件,Nokia3100显示未知格式,NEC N800则显示一小句乱码,一些高档手机则显示很长的乱码。
——————————————–
关于开发:
◇java.net.URLEncoder.encode(SPURL);里面的&必须换成&,否包含则encode SPURL的链接在手机上点通常是没有反应;
◇POST字符必须注意将字符转换成UTF-8处理,否则有些GB2312的字符传过来直接处理不了(表现在查询结果为空);
——————————————–
关于WAP PUSH:
◇WapPush的访问地址如果完全相同,那么某种手机就只能够接收到第一条。
——————————————–
关于传说:
◇WAP的推送协议中定义了服务指示(SI:Service Indication)和服务加载(SL:Service Load)两项服务,其中SI方式用户收到的push可以根据用户意愿进行处理(立即或以后);SL则用户收到push就立即上网不用用户干涉;SL是不受欢迎的一种push,现在很多手机都不支持它,包括大部份的nokia手机;
◇现在许多公司都是通过改变sms的包头和内容,将一条普通sms变成push消息,来发送WAP PUSH;
◇捆绑,据说,当用户走到弹出确认页面后,他不想点确定,但是通过脚本(wmls),能自动的点确定订购,而且脚本还是存在客户端的,无凭无据的。
◇订购包月后还按次计费;
◇退定后还继续收费;
◇模拟上行,就是利用短信中心的平台,修改源号码为某一手机用户的号码,这是任意的,并且可以模拟1860或指定的号码。但是有一问题:就是利用这短信中心模拟的源号码只能向所有的移动用户发送消息;
◇用一些用户很感兴趣的语言将用户骗取订购,订购后则完全边了样;通常用于WAP PUSH群发;也可以杜撰业务的资费,订购后发现资费大涨;

——————————————–
关于浏览器,平台,OS,SVG:

Nokia Series30(Nokia OS) 96*65 WAP/XHTML MIDP java MMS
Nokia Series40(Nokia OS) 128*128* WAP/XHTML MIDP java MMS
Nokia Series60(Symbian OS) 176*208 WAP/XHTML MIDP java MMS
Nokia Series80(Symbian OS) 640*200 WAP/HTML Personal java MMS
Openwave SDK 6.2.2
Openwave V7
Moto v2.2
Access NF3系列
Pollex WAP Browser

诺基亚40系列是指:7600,7200,7250,7210,6100,6220,6230,6610,6650,6820,6800,6810,3100,3200,3300,5100。
诺基亚60系列是指:3650,3660,6600,7650,N-Gage。

wap2.0手机
NOKIA(诺基亚) 3100,3220,6600,6260,7610
MOTO E365,V872,C650,V180,E680,V878,V3,A768,V80,V220,A780
SONY ERICSSON P908,P910c,K506c,S700c,T238,T618,T628,T290
QT557,QT735,QT756
NEC N700,N820,N830,N710,N718,N720,N728,N110
三菱 M350,M750,M330
Panasonic X200,A500
SUMSUNG E108,E338
SIEMENS CF62,SX1
多普达 565,696,818

支持SVG的手机
SVG-enabled phones have hit the street, and this is only a beginning! Here is an updated list of phones that you can buy in your shop and come fully equipped with a compliant SVG Tiny 1.1 implementation with pictures of the main models further down:

Motorola: C975, C980, E770V, E1000, i870, V3X, V975, V980, V1050
NEC: 802
Nokia: 3250, 6265, 6280, 7710, E60, E61, E70, N70, N71, N80, N90, N91, N92
Panasonic: MX6, MX7, SA6, SA7, VS3, VS7
Sagem: my-X8, my-V76, my-V85
Samsung: D600, E350, Z300, Z500, ZV10, ZV30
Sanyo: S750
Sharp: V501SH, V601SH, V602SH, V603SH, V604SH, V703SH, V703SHf, 802, 902, V903SH
Siemens: C65, C70, C75, CF65, CFX65, CL75, CX65, CX70, CX70 Emoty, CX75, M65, M75, S65, S75, SF65, SL65, SL75, SK65, SP65
Sony Ericsson: D750, F500, K300, K500, K508, K600, K608, K700, K750, P990, S600, S700, S710, V600, V800, W550, W600, W800, W810, W900, Z500, Z520, Z800
Toshiba: TS 803, TS 921, V902T, V903T

128*128相素(S40系列 Nokia Series 40 Nokia N3100, N3108, N3200, N3300, N3510i, N3530, N5100, N5140, N6100, N6108, N6220, N6610, N6800, N6820, N7200, N7210, N7250,N6230)
120*130相素(Sharp Gx10 Gx12 Motorola V750)
120*147相素(Sharp Xera GZ100)
176*204相素(Motorola V300, V303, V400, V500 , V600,V80,E398)
176*208相素(S60 系列:是指Nokia Series 60 Nokia N7650 N3650, N3660,N6600, N6620, N7610)
176*220相素(S60 SonyEricssonK700 Z1010 Siemens SX1)
176*200相素(3G系列:是指 LG 8110 NEC 313 616 Motorola A835 176*206相素)
240*260相素(Sharp Gx20 Gx22 Gx30 Gx32)
240*260相素(vodafone P4 P5 P6 W日本手机 FOMA240*265)

1:42 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
sms实现wap push的方法和格式(转载)
 
wap push格式
00     ’SMSC Len。用手机上设置短信中心号码
51     ’submit type
00     ’SMS_TP_Message_Reference type
0B    ’对方电话的长度
A1     ’Number type
13175639296F6       ’电话号码:13573629696
00     ’SMS_TP_PID
F5     ’SMS DCS
A744         ’SMS available date
0B05040B8423F00003030101         ’Wap Push Header 1
29060603AE81EA8DCA   'Wap Push header 2
02
05  '-//WAPFORUM//DTD SI 1.0//EN
6A  'UTF-8
00
45  '<si>
C6  '<indication
08  '<action=signal-high>
0C  'href="http://
03  '字符串开始
687474703A2F2F3231382E35392E3133382E35343A32303030  'URL:   http://218.59.138.54:2000
00  'URL 字符串结束
01  '>
03  '内容描述字符串开始
'这里就是显示给用户的内容,用utf-8编码。
9A6C5EF6671D       '内容描述:马延朝
00  '内容描述字符串结束
01  '</indication>"
01  '</si>
可以只能显示很少的汉字,请高手指点怎么分包发送更多的汉字?

 

一下方法是经过验证的。

TrxID  =一个随机数
    fullStr = "0605040B8423F0" & TrxID & "0601AE"
    fullStr = fullStr & "02056A0045C6080C03"
    fullStr = fullStr & StrToHex(TheURL, 2)
    fullStr = fullStr & "001103" & "0102"
    fullStr = fullStr & "40494400080AC307" & Format(Now, "yyyymmddhhmmss")
    fullStr = fullStr & "10C304" & "20990101" & "0103"
    fullStr = fullStr & StrToHex(MServiceName, 2) & "000101"


分段发送的

例如  URL:wap.gd.monternet.com/?userType=B&serviceID=04020028 提示信息:神秘激情地带,江湖儿女情长神秘激情地带,江湖儿女情长神秘激情地带,江湖儿女情长
第一包:
0B05040B8423F0000355020155060403AE81EA02056A0045C60C037761702E67642E6D6F6E74657
26E65742E636F6D2F3F75736572547970653D42267365727669636549443D303430323030323800
070103E7A59EE7A798E6BF80E68385E59CB0E5B8A62CE6B19FE6B996E584BFE5A5B3E68385E995BF
E7A59EE7A798E6BF80E68385E59CB0E5B8A62C 
第二包:
0B05040B8423F00003550202E6B19FE6B996E584BFE5A5B3E68385E995BFE7A59EE7A798E6BF80E6
8385E59CB0E5B8A62CE6B19FE6B996E584BFE5A5B3E68385E995BF000101 ,解释可参考WDP WSP,我就不具体说了


0B是头的总长度
05040B8423F0是固定的,表示接下来是一个WAP PUSH
分包的关键是0003550201,对应GSM 03.40里9.2.3.24.1,00表示是Concatenated Short Messages,03是长度,55是reference number,楼主在这儿固定编码会有问题的,如果同时下发两条这样的多包短信给同一个手机,手机就区分不开了,02表示分成2个短信发送,01是当前包的序号。


一个扩展包wappush包是这么构成的
WDP + WSP + SI/SL

如果长度超常(短信一个包的Content不要超过140)

就要分解成

WDP1 + (WSP+SI/SL)的part 1
WDP2 + (WSP+SI/SL)的part 2
...
WDPN + (WSP+SI/SL)的part N

 

如单包
WDP: 06 05 04 0B 84 23 F0
WSP: ...
SI/SL:...

双包是
第1包
WDP: 0B 05 04 0B 84 23 F0 00 03 01 02 01
Part1: ...

// 00 - UDH IE Tag
// 03 - UDH SAR IE Length
// 01 - Refrence
// 02 - Total Packet
// 01 - Current Packet

第2包
WDP: 0B 05 04 0B 84 23 F0 00 03 01 02 02
Part2:...


WDP参考相关文档。

 

=====================================================
首先,构造一个Push消息体:

02
05  '-//WAPFORUM//DTD SI 1.0//EN
6A  'UTF-8
00
45  '<si>
C6  '<indication
08  '<action=signal-high>
0C  'href="http://
03  '字符串开始
这里就是url从"http://"以后的那部分的每个字符的ASCII码
00  '字符串结束
0A  'created=
C3  '时间
07  '7个字节,也可以是04,下面就只需要年月日就可以了
20 03 01 01 00 00 00 '年,月,日,时,分,秒,格式如何一看就明白吧。
10  'si_expires=
C3  '时间
07  '跟上面一样
20 04 01 01 00 00 00
01  '>
03  '字符串开始
这里就是显示给用户的内容,用utf-8编码。
utf-8编码,英文字符直接用ascii码;中文如果unicode是(二进制)abcdefgh ijklmnop,
那么utf-8就会变成1110abcd 10efghij 10klmnop
00  '字符串结束
01  '</indication>"
01  '</si>

有了Push消息体之后,需要在前面增加一个Push PDU
81  'transaction id (connectionless WSP)
06  'pdu type (06=push)
06  'Headers len
03 AE 81 EA    'content type: application/vnd.wap.sic; charset=utf-8
8D    'content-length
XX  '这里就是Push消息体的长度。如果消息体长度小于128,那么就要加上128。例如是93个字节,那么需要填入DD
'至于大于127怎么处理,按照协议好像应该是这样,例如原来的二进制abcdefgh,那么就要弄成两个字节:
'1000000a 1bcdefgh,但是尝试还没成功

在然后,还要在前面增加一个UDH
06 'User Data Header Length (6 bytes)
05 'UDH Item Element id (Port Numbers)
04 'UDH IE length (4 bytes)
0B 84   'destination port number
23 F0   'origin port number

如果所有这些加起来大于140个字节,那么就需要修改UDH头,分成两条短消息串联。但是没有尝试成功。

发送的时候,udhi=1,pid=0,dcs=4
Nokia 3650/7650肯定OK,motorola t720肯定ok,siemens 3118,3618肯定不行,其他的还没尝试。

同样的技术可以用来发送mms通知、fundown的铃声图片。

需要解决的问题:长于127字节/两条短信的时候该怎么办。

 

另转载 Wap push over sms 实践  


通过短信进行点对点的WAP PUSH,本质上来说应该是发送一个wap页面(WBXML)到接收方,里面含有一段文字和一个url链接。

在发送端采用PDU模式发送,编码是UCS2。
 
整个发送的包大部分内容都是固定的,只需要对几个地方根据希望发送的内容进行一下替换即可。
 
我参考的例子是这样的,只用说明中红色的部分是需要变化的:
0051000BA13108086406F600F5A7850B05040B8423F0000303010129060603AE81EA8DCA02056A00
45C6080C033231312e3133362e3135332e33302f776170707573682f70757368496e6465782e6a737
03f7075736849643d3035303531313134313630353231000103E8AFB7E782B9E587BBE4BBA5E4B88B
E993BEE68EA5E88EB7E58F96E5BDA9E4BFA1E58685E5AEB9000101

00 SMSC 地址信息的长度 00表示用手机上设置短信中心号码,PDU 串的“SMSC 址格式”段和“SMSC 地址”段将省去
51 基本参数(TP-MTI/VFP) 不要求发送回复
00 消息基准值(TP-MR)
0B 对方电话的长度
A1 目标地址格式 A1表示为国内格式
3108086406F6 目标地址,补‘F’凑成偶数位后奇偶位互换 (因为手机号码是11位,需要补一个F,再将奇偶位互换,比如12345678901需要变成2143658709F1)
00 协议标识(TP-PID) 是普通GSM 类型,点到点方式
F5 用户信息编码方式 (TP-DCS)
A7 有效期(TP-VP)
85 用户信息长度(TP-UDL) (此处是从下面的0B开始所有的字节数/2对应的十六进制数)
0B WAP PUSH头部的总长度
05040B8423F0表示接下来是一个WAP PUSH
00 表示是Concatenated Short Messages
03 长度
03 reference number
01 表示分成1个短信发送
01 当前包的序号
29060603AE81EA8DCA WSP
02 标记位
05 -//WAPFORUM//DTD SI 1.0//EN
6A UTF-8
00 标记开始
45
C6
08
0C href=/"http://
03 字符串开始
3231312e3133362e3135332e33302f776170707573682f70757368496e6465782e6a73703f7075736
849643d3035303531313134313630353231 URL (编码过的URL,不带http://)
00 URL 字符串结束
01 >
03 内容描述字符串开始
E8AFB7E782B9E587BBE4BBA5E4B88BE993BEE68EA5E88EB7E58F96E5BDA9E4BFA1E58685E5AEB9 内容描述字符串 (编码过的文字内容)
00 内容描述字符串结束
01
01
 
 
AT指令发送时:
at+cmgf=0 //设置发送为二进制模式
OK
at+cmgs=93 //十进制的数字,值为 (上面生成的一大串的字节数/2)-1,还没搞清为啥要减一
> 0051000BA1。。。。。。。(就是那一大串了) Ctrl+Z
+CMGS: 14
OK
 
发送成功

0:42 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
9月16日
WAP开发(三)
38. 为什么META标签不工作?

浏览器不支持默认的meta标签,例如:

<meta http-equiv="refresh" content="1;http://somewhere.com/"> 

虽然有少量网关支持非常有限的META标记。但是测试显示,如果使用了它们,网关就会出问题。例如某网关不支持普通的HTTP Cache控制,如果要实现Cache控制只好使用特殊的META标记。显然从其他
网关来的用户就可能不支持这个META。注意:不要使用META tags。肯定有其他的方式来完成你的想法。

最常使用的META是:

<meta http-equiv="refresh" content="1;http://somewhere.com/"> 

这个告诉浏览器重新装入指定的WML页面。WML中已经包含了一个<ontimer>。

39. 为什么服务器接收不到用户发送的参数?

用户输入的参数或者其他参数可以像在HTML中一样通过提交方式发送到服务器。在HTML中这个是<FORM>,POST或者GET。
首先知道要知道POST和GET的区别。对于POST浏览器将生成一个数据包将变量名和它们的内容捆绑在一起,并发送到服务器。对于GET,它其实是一个URL请求,变量名和内容都包含在URL中。
对于WAP环境,要求是非常严格的,必须要根据协议来操作。虽然以下的URL

"/cgi-bin/somescript?username=john&telephone=123-123-1234&occupation=banana+bender"

可以在HTML环境中工作,但是在WAP环境中无法工作。以上的部分编码将使得保护的变量内容被误解。特殊的空格(在 banana 和 bender )被转成 “+”。 URL就根本没有空格。
以上的URL在WAP中无法工作的主要原因是用来分割每个变量和变量内容的 & (与号)没有转义。正确的格式应该是:

"/cgi-bin/somescript?username=john&telephone=123-123-1234&occupation=banana+bender"

在这里 & 被名字实体所替换。为了解释更清楚些,请看下面的代码:

<card id="input" title="Gimme some data">
 

<input type="text" name="username" format="M*m"/>


 

<input type="text" name="occupation" format="M*m"/>



<anchor>Send this
<go href="/cgi-bin/somescript?username=$(username)
&occupation=$(occupation)"/>
</anchor>

注意这不是真正的WAP协议,专门的字符应该转义,否则将得到不可预料的结果。

40. 为什么在HTTP中的Referer看不见?

当HTML浏览器从一个URL到另外一个URL的时候,它默认地会发送一个叫做 Referer的 HTTP头给服务器,告诉它在浏览这个页面之前的那个页面。这是一个非常有用的特点,在WAP环境中同样也有。
但是既然这个信息来自用户代理(浏览器)、WAP设备,通常为了节约带宽和时间,就被省略了。
为了使用 Referer ,应该使用新的URL标签例如: <a>,<go>等等,并且加入参数:sendreferer。

<go href="/somedir/somedeck.wml" sendreferer="true"/> 

这样就会把参考的URL发送到服务器。

41. 如果没有找到URL,有可能重新将用户引导到另外一个WML卡片或者页面吗?

是的。但这是服务器端的特点,与客户端没有关系。

42. 为什么普通的HTTP 302重新导向不好使?

这的确是一个事实。核心的问题是在服务端的脚本语言,而不是在服务端语言和服务器之间。
所谓的302 Found HTTP反应是指服务器告诉用户代理,它所需要的资源在另外的地方可以找到。302反应可能包括一个人们可理解的信息,如果在这种情况下“ Content-type: ”就被设置了
。笔者所测试过的服务器,即使没有内容也都加了“Content-type:”。默认的 “Content-type:” 是text/html。当然有些网关不喜欢这个类型。
以下的例子已经经过测试,在Apache和Microsoft Internet Information Server都可以工作。如果使用其他的Web Server,或者其他的脚本语言,需要能转换这些简
单的脚本。关键的工作是十分简单的,除非需要,不用告诉服务器整个HTTP头。大多数Web Server将自动完成这个HTTP头,使得用户代理可以理解。
所有的代码例子可以在线测试。如果它们能够工作,用户将被重新引导到http://wap.colorline.no/clientinfo.html ,在那儿将产生一个WML页面来显示所有的HTTP头。
PHP 代码测试可以在 http://wap.colorline.no/wap-faq/apps/302test.php3中找到。

<?
    header("Location: http://wap.colorline.no/clientinfo.html");
    header("Content-type: text/vnd.wap.wml";
?> 

Perl测试代码可以在http://wap.colorline.no/cgi-bin/302test.pl中找到。 

print "Location: http://wap.colorline.no/clientinfo.html/n";
print "Content-type: text/vnd.wap.wml/n"; 

ASP测试代码可以在 http://www.colorline.no/302test.asp中找到。 (注意不同的URL): 

<%
    Response.Redirect = "http://wap.colorline.no/clientinfo.html";
    Response.ContentType = "text/vnd.wap.wml";
    Response.Flush
    Response.End
%>

43. 可能在WML中实现ASP Session吗?

不可能。可以把信息存储在临时变量中模拟Session。Session是保存在用户PC上的“cookies”中。目前的WAP设备不支持“cookies”。不过下一代的设备和WML可能支持“cookie
s”。

44. WAP支持Session吗?

在HTML中,一个十分普遍的“处理”用户的方法就是为每个用户分配一个“session”。这个有时候是通过指定一个独一无二的cookies来实现的。然而WAP的资源非常有限,因此session的处理必
须以不同的方式来处理。
Alex Kriegel 提供了一个安装在 WAPlinks的Custom Session Object包。这个zip文件中包含了VB类的文件和编译过的Dll文件,还有相关的文档。这些可以在http
://www.waplinks.com/customsessionobject.zip下载。
另外一种方法是使用 PHPlib ,它是使用 PHP 编写的。
Tarique (tarique@nagpur.dot.net.in) 提供了如何使用PHPlib来验证和处理每个WAP用户。有相关的文件和注释可以到下面地址下载:

http://wap.colorline.no/wap-faq/archive/phplib_wml.zip
45. 可以在WAP中使用Cookies吗?

在理论上是可以的,但不是所有的WAP设备都支持。另一个方法来管理会话是使用隐藏的fields(包含会话标识,无论是POST或者GET方式)。

46. WAP支持Cookies吗?

普通的HTTP Cookies是作为WAP的扩展来实现的。无论你以前听到什么,Cookies的支持将越来越好。实际上Phone.com的 UP.Link网关已经支持这个功能有一段时间了。
可以使用以下的脚本语言检测Cookie-support,:
http://wap.colorline.no/wap-faq/apps/cookietest.php3
脚本在http://wap.colorline.no/demos.html也可以得到。
当第一次看见卡片的时候,记数器应该为0。所有的Cache都被关闭。卡片通过在URL中随机地加入变量来强制自己加载(笔者不推荐这种强制加载办法)。当点击增加计数连接,页面将重新加载,卡片就再次出现,并
且记数器变成1。
在脚本中,Cookie的名字被称做 TestCookie,它有很长的生命期,因此可以隔好几天再来查看记数器,它将是上一次的数值。这要求你能使用与上一次访问所使用的WAP环境一样,否则将把你的数值清0

如果记数装置一直都是0,那么cookie 就没有能传送到你的Web Server。这个脚本也能表示Cookie是否被传送。
另外,这个脚本同样还显示HTTP头中的HTTP_VIA 和 HTTP_USER_AGENT 。这些能够得到所使用的网关和模式。一些网关使用HTTP_VIS标识自己,而另外一些使用HTTP_USER_
AGENT,还有一些则让程序无法知道。
下面是它的PHP代码。一个标准的 PHP setcookie() 函数只有在这种脚本语言中才会出现。函数只是简单地设置cookie,并且PHP变量 $HTTP_COOKIE_VARS 用来读取coo
kie。

<?
if(isset($HTTP_COOKIE_VARS["TestCookie"]))
{// Check if TestCookie is set
      $cookieset = "set";

// Read the Cookie
      $cookieid = $HTTP_COOKIE_VARS["TestCookie"];
  // and increase its value
      $cookieid++;
     }
    else {
  // cookie was not set
      $cookieset = "not set";
  // start counter at zero
      $cookieid = 0;
    }
  // apply the Cookie to the HTTP header
 setcookie("TestCookie",$cookieid);
  // set the content type for WML
    header("Content-type: text/vnd.wap.wml");

// disable ALL caching
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Cache-Control: no-cache, must-revalidate");          
    header("Pragma: no-cache");                                  
    echo("<?xml version=/"1.0/"?>/n");
 echo("<!DOCTYPE wml PUBLIC /"-//WAPFORUM//DTD WML 1.1//EN/"
 /"http://www.wapforum.org/DTD/wml_1.1.xml/">/n/n");
 echo("<!-- This application attempts to test the capabilities of a WAP gateway to support     cookie
s -->/n");
    echo("<!-- App by Espen.Lyngaas@colorline.no (c) 2000 -->/n");
  // Generate random value for reload forcing
    $random = mt_rand(100000,999999);
 ?>
 
  <head>

// Even more cache disabling
<meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/> 
  </head>
  <card id="init" title="CookieTest">
 


   Cookie "TestCookie" was <?echo($cookieset)?>.
   Value is currently "<?echo($cookieid)?>"
 


 

<anchor>
Increase value
<go method="get" href="<?echo($PHP_SELF)?>?random=<?echo($random)?>"/>
</anchor>


Gateway: 
  <?
    if(isset($HTTP_VIA))
{ // Is there something in the HTTP_VIA variable?
      echo($HTTP_VIA);
    }
    else {
      if(isset($HTTP_USER_AGENT))
{ // Is there something in the HTTP_USER_AGENT variable?
        echo($HTTP_USER_AGENT);
      }
      else {
// Absolutely no identifier was found
        echo("Unknown");
      }
    }
  ?>
 


 
 
47. 如何使用WAP设备发送E-Mail?

在HTML中有一个默认的E-Mail机制:“ mailto:” 。但在WML中不好使,因此E-Mails必须通过WML表单来解决。例如:


     <card id="edit" title="Email Editor">
     

From: <input type="text" name="from" format="*M"/>


     

To: <input type="text" name="to" format="*M"/>


     

Subject: <input type="text" name="subject" format="*M"/>


     

Message body: <input type="text" name="body" format="*M"/>


     


        <anchor>Send this mail
          <go method="post" href="http://some.host/mailhandler"?action=send/">
            <postfield name="from" value="$(from)"/>
            <postfield name="to" value="$(to)"/>
            <postfield name="subject" value="$(subject)"/>
            <postfield name="body" value="$(body)"/>
          </go>
       </anchor>
     


  
 

在代码中的http://some.host/mailhandler是一个CGI程序,它是服务端的脚本程序,将提交的表单转换成E-Mail格式并发送出去。
如果想使用一个类似于发信的过程,就需要编辑变量名。另外发送的数据是有限的,长信息可能需要打断。
为了演示它是如何工作的,下面的 PHP 脚本可以用来处理Mail。它将格式化WML页面,告诉用户是否发出信件。在真实的应用中,应该加入检测,例如:E-Mail的合法格式。

<?
// Tell the client that this is a WML deck
    header("Content-type: text/vnd.wap.wml");
    echo("<?xml version=/"1.0/"?>/n");
    echo("<!DOCTYPE wml PUBLIC /"-//WAPFORUM//DTD WML 1.1//EN/"
/"http://www.wapforum.org/DTD/wml_1.1.xml/">/n");
// The name of your mail server
    $mailer = "wap.colorline.no";
// Format the from field
    $from = $from." (WAP user at ".$mailer.")";

// Add the from field and some character handling to the extra headers
  $extraheaders = $from."/nContent-Type: text/plain;
charset=iso-8859-1/nContent-Transfer-Encoding: 8bit";

// Start sending out the WML deck
    echo("/n");
    if(mail($to,$subject,$body,$extraheaders))
{// Use PHP's internal mail functionality
// Mail was successfully sent
      echo("<card id=/"sent/" title=/"Mail sent/">/n");
      echo("

Mail was sent successfully

/n");
      echo("/n");
    }
    else {
// The mail could not be sent
      echo("<card id=/"notsent/" title=/"Mail failed/">/n"); 
      echo("

Unable to send mail

/n");
      echo("/n");
    }
    echo("/n");
?>

因为安全性的原因,以上的代码没有演示程序。

48. 可以在模拟器上操作本地的页面,却没有办法访问Internet上的,有什么问题吗?

大多数模拟器和工具都可以浏览Internet、Intranet和本机的页面。但是在访问一些大公司页面的时候,必须通过代理服务器来取得进入Internet的权限。如果必须通过这个代理服务器来取得HTM
L页面,那么你的WAP模拟器也会取得权限来访问Internet。
注意到有些模拟器不支持代理服务器,但是大多数是支持的。在模拟器里面设置这些是很简单的。用户所做的只需要将代理主机的名字、IP地址和端口号输入就可以了。如果没有找到,你可以在 systems/netw
ork 管理器里面设置这些,也可以检查 Netscape/IE的设置。
在某些情况下,代理服务器使用 userid 和 password 来取得进入Internet的权限。有些模拟器支持用户代理服务器,用户应该能告诉模拟器相关的代理配置。
在极少的情况下,使用代理服务器(如Microsoft Proxy Server,)的用户会遇到更多的问题。这个代理服务器只接受一种验证方式(userid/password)。这种验证被称做 NTLM
,并且是某种 MS 的验证方式。几乎很少有模拟器支持这种方式。因此最好是避免使用它,或者让管理员使用“Basic Authentication”方式以避免更多的麻烦。

49. 什么是PUSHing,它是如何工作的?

PUSH被加入到WAP 1.2,而且只在WAP 1.2中才存在。简单地来说,PUSH提供了另外一种从服务器向用户发送数据的方式。PULLing是从客户端请求信息,然后接收它;PUSH意味着服务器可以
向用户发送数据,而不需要用户来请求。
内容或者应用服务器无法向用户代理直接发送数据,必须使用一种叫做Push Proxy 的网关。PPG 是基于Internet的Push Initiator (内容或者应用服务器) 与移动用户之间的。在
Internet一边,使用Push Access Protocol,在移动网络一边使用Push Over-the-Air Protocol。
当前只有 WAP 1.2 开发平台支持 PUSH, 例如 Nokia Toolkit 2.0。 Nokia Toolkit 2.0 only 内部支持PUSHing,意味着用户可以从工具包的界面将消
息推送到模拟器。如果想试着到一个外部的Push Proxy Gateway, 工具包就崩溃了。从readme文件中知道,PUSHing 还没有经过完整的测试。

50. WAP模拟器说text/html不支持,但是用户的MIME设置是正确的,为什么?

当使用服务端的脚本语言,例如ASP、PHP或者Perl,来生成WML输出,或者从HTTP服务器提供WML页面的时候。记住HTTP一般默认的显示是HTML,其MIME类型是text/html。
如果HTTP服务器或者服务器脚本有错误,错误的信息将使用HTML显示,因此微型浏览器是不能显示错误信息的。
一个开发工具/模拟器可以让用户看到从HTTP服务器过来的代码。例如,在Nokia SDK中,这个功能被称做View Source。通过看代码可以知道HTTP服务器到底发送了些什么内容。也可以使用普通
的浏览器来查看任何HTML格式的错误信息。

51. 在哪儿有Visio移动电话的模板库?

目前唯一知道的就是它包含在 Nokia 7110 中。

52. 有没有其他有用的WML内容服务列表?

这里至少有一个。
对于Unix用户, http://pwot.co.uk/wml/中有Thomas Neill (ponder@pwot.co.uk)提供的WML工具,包括WML二进制编译和反编译。
Angus 和 Zygo WAP(angus@z-y-g-o.com)已经开发出了一个Perl工具包。它还在为管道式的WML编译器工作。

53. XML到XSL的转换可以应用到WML和WAP吗?

既然WML实际上是XML,并且XSL将WML转换成其他不同的XML文档,那么问题的答案是显然的:XSL也可以应用到WML。可以参考Luca Passani的文章《WebTechniques》。这个文
章在网络上的地址是:
http://www.webtechniques.com/archives/2000/03/passani/
它推荐看一下叫做《在 Apache下Cocoon计划的实现》这篇文章。“Cocoon 是一个依赖于新的W3C技术(例如DOM,XML,和XSL)框架。Cocoon计划在于改变Wen信息创建,生成和提
供的方式。文档内容、风格和逻辑经常因为个人或者工作组的不同而不同。 Cocoon目标在于将这三层分离,允许三层次之间进行独立的设计,创建和管理,减少相互之间的影响,增加工作的可复用性以及缩短上市的时间
。Web内容的产生大多数是基于HTML的,但是HTML并不能将三者分离开来,混合着各种格式标签,程序逻辑等等。而Cocoon计划将要改变这种情况,允许内容,逻辑和风格相互分离。使用XML来保存,但是使
用XSL来将它们混合。”
基本上来说,Cocoon将解读HTTP头,判断使用的是什么浏览器,然后使用不同的风格来选择正确的页面,使用XSL进行混合。

54. 想让用户只要简单地按下一个按钮就能够转跳到其他卡片而不是通过选择URL,这个可能吗?

不,不可能。

55. 如何避免一个行的中断以便可以在一行中输入多个链接?

在Nokia 7110中,不可能做到这一点,每个链接都占据自己的一行。

1:42 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
WAP开发(二)
27. 怎样防止从Cache中读取WML页面?

当WML页面下载到WAP设备后,它将保存在WAP设备内存中一段时间,直到这个时间过期。在这之后,页面将从服务器下载,而不是从WAP设备的缓存读取。这个过程被称做Cache。
但是有些时候不想让页面从缓存中读取,而是从服务器端读取。一个典型的例子就是当服务器的内容不断在更新的时候,通过在HTTP头中加入一定的cache信息,来告诉WAP设备该页面将不存储在缓存中。
可以在服务器端生成HTTP头,或者使用PHP、ASP、Perl或者其他服务端开发语言。这一行不能被包括在页面里,既然是HTTP的信息头,就不是WML元素。
对于静态页面,或许没有使用服务器端脚本语言,许多浏览器支持META标签来控制浏览器的Cache。看本部分的最后的例子。
将下面代码加入到HTTP头中,页面将马上过期:

Expires: Mon, 26 Jul 1997 05:00:00 GMT
Last-Modified: DD. month YYYY HH:MM:SS GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
第一行告诉微型浏览器,页面已经过期一段时间了。第二行告诉浏览器页面最后一次修改的时间。DD应该换成当天的日期,month YY HH MM SS等等类推。第三行和第四行有同样的效果。告诉浏览器页面不被
Cache(第三行适用于 HTTP 1.1,第四行适用于HTTP 1.0)。
下面的是PHP的一个例子:

<?
// set the correct MIME type
     header("Content-type: text/vnd.wap.wml");
// expires in the past
     header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
// Last modified, right now
     header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); 
// Prevent caching, HTTP/1.1
     header("Cache-Control: no-cache, must-revalidate");
// Prevent caching, HTTP/1.0
     header("Pragma: no-cache");
   ?>

下面是使用WebClasses(VB)的例子。使用"Response.Expires=-1",防止Cache。

 Private Sub WebClass_Start()
      'Set correct MIME type
      Response.ContentType = "text/vnd.wap.wml"
     
      'Make sure no caching
      Response.Expires = -1
      Response.AddHeader "Pragma", "no-cache"
      Response.AddHeader "Cache-Control", "no-cache, must-revalidate"
   
      'Use basicwml(my own) as template
      Set NextItem = basicwml
  End Sub 

这里有一个ASP的例子,同样使用“Response.Expires=-1”防止Cache。

<%
    Response.ContentType = "text/vnd.wap.wml"
    Response.Expires = -1
    Response.AddHeader "Pragma", "no-cache"
    Response.AddHeader "Cache-Control", "no-cache, must-revalidate"
%> 

最后是使用META的例子:

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
 
    <head>
      <meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
    </head>
    <card id="alwaysexpire">
     

This deck will never be stored in the cache


   
 

下面的页面是在经过86400秒(24 hours)后过期。

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
 
    <head>
      <meta forua="true" http-equiv="Cache-Control" content="max-age=86400"/>
    </head>
    <card id="expire1day">
     

This card will live in the cache for a day


   
 

有些浏览器例如:UP.Simulator如果可以通过“返回”达到另外一个卡片,那么它将不会重新装载卡片。为了强制这个更新动作,用户必须在META标签中使用must-revalidate 参数。

<meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/>

28. 如何防止变量被保存在Cache中?

变量保存在Cache中,这样变量还可以再利用。例如当用户返回到上一个输入卡片,他不需要重新输入,只需要改变需要改变的地方。但是在某些情况下这会造成一些问题。例如以WAP聊天系统,有些变量用了一遍又一
遍,但是需要不同的内容。有些浏览器,例如:Nokia 7110,就会存在类似的在该清除的时候无法清除的问题。
在WML中,<card>标签有一个参数叫做newcontext。
当newcontext="true" 时清除所有的变量。但是这样也清除了所有导航的历史记录,这意味着back按钮不再工作。
为了清除变量,可以告诉浏览器将变量设为空:

<setvar name="one_variable" value=""/>
<setvar name="another_variable" value=""/>

但是,不是每个时候都有效果。在某些情况下必须使用一个难以想象的方法来清空变量。就是使用 onenterforward 事件。

<onevent type="onenterforward">
   <refresh>
     <setvar name="one_variable" value=""/>
     <setvar name="another_variable" value=""/>
   </refresh>
</onevent>
29. 怎么能够知道请求是从WML浏览器来的还是HTML浏览器来的?

既然要利用已经存在的为HTML浏览器编写的代码,就需要知道请求是从HTML浏览器还是从WML浏览器过来的。同样地,如果想重新引导的HTML浏览器直接到相应的HTML文档上,WML浏览器到WML页面上
,以下的PHP代码就可以做到这些。

<?
// Because this script sends out HTTP header information,
// the first characters in the file must be the <? PHP tag.

// relative URL to your HTML file
   $htmlredirect = "/html/my_htmlpage.html";
// ABSOLUTE URL to your WML file 
   $wmlredirect = "http://wap.mysite.com/wml/my_wmldeck.wml";

   if(strpos(strtoupper($HTTP_ACCEPT),"VND.WAP.WML") > 0)
{// Check whether the browser/gateway says it accepts WML.
     $br = "WML";
   }
   else {
     $browser=substr(trim($HTTP_USER_AGENT),0,4);
     if($browser=="Noki" || // Nokia phones and emulators
        $browser=="Eric" || // Ericsson WAP phones and emulators
        $browser=="WapI" || // Ericsson WapIDE 2.0
        $browser=="MC21" || // Ericsson MC218
        $browser=="AUR " || // Ericsson R320
        $browser=="R380" || // Ericsson R380
        $browser=="UP.B" || // UP.Browser
        $browser=="WinW" || // WinWAP browser
        $browser=="UPG1" || // UP.SDK 4.0
        $browser=="upsi" || // another kind of UP.Browser ??
        $browser=="QWAP" || // unknown QWAPPER browser
        $browser=="Jigs" || // unknown JigSaw browser
        $browser=="Java" || // unknown Java based browser
        $browser=="Alca" || // unknown Alcatel-BE3 browser (UP based?)
        $browser=="MITS" || // unknown Mitsubishi browser
        $browser=="MOT-" || // unknown browser (UP based?)
        $browser=="My S" || // unknown Ericsson devkit browser ?
$browser=="WAPJ" || // Virtual WAPJAG www.wapjag.de
$browser=="fetc" || // fetchpage.cgi Perl script from www.wapcab.de
$browser=="ALAV" || // yet another unknown UP based browser ?
        $browser=="Wapa") // another unknown browser (Web based "Wapalyzer"?)
        {
        $br = "WML";
     }
     else {
       $br = "HTML";
     }
   }

   if($br == "WML") {
// Force the browser to load the WML file instead
    header("302 Moved Temporarily");
    header("Location: ".$wmlredirect);
    exit;
   }
   else {
// Force the browser to load the HTML file instead
    header("302 Moved Temporarily");
    header("Location: ".$htmlredirect);
    exit;
   }
  ?> 

这个判断是在服务端完成的, PHP代码将首先查看网关是否接收text/vnd.wap.vml MIME类型。如果不是,将检测前面的字符,查看是否为WML浏览器。如果不符合,那么就假设为HTML浏览器
。如果有新的WML浏览器,那么ID字符串也要增加。
这个代码基于Robert Whitinger(robert@wapsight.com)的代码,使用了Don Amaro(donamaro.concepcion@nl.unisys.com)提供的列表

注意:由于只需要四个字符串就可以辨别,因此例如:"WapIDE-SDK/2.0;(R320s(Arial))" 可以使用“WapI”来代替是可行的做法,也是足够的。
同样的功能也可以通过ASP来解决。先判断请求的是“/index.wml” 或者 “/index.html” 和所需要的MIME类型。另外以下的脚本辨别的方式和上面不一样。另外还需要网关告诉服务器它能
接收 的text/vnd.wap.wml MIME类型。该例子如下所示:

<%
Response.Buffer = TRUE
  Dim IsWap
  httpAccept = LCase(Request.ServerVariables("HTTP_ACCEPT"))
  if Instr(httpAccept,"wap") then
  IsWap=1
  Else Response.Redirect "/index.html" : Response.Flush : Response.End
End if
%>
<%Response.ContentType = "text/vnd.wap.wml"%><?xml version="1.0"?>
<%Response.Flush%>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
 
  <card id="redirect">
  <onevent type="onenterforward">
  <go href="/index.wml"/>
  </onevent>
 


  <a href="/index.wml">enter
 


 
 
  <%Response.Flush:Response.End%>

30. 如何判断访问者是来自哪个浏览器或者移动电话?

可以通过检查HTTP_USER_AGENT标签来判断。例如试着使用Microsoft Internet Explorer访问一个站点的时候,HTTP_USER_AGENT将返回:Mozilla/4.
0 (compatible;MSIE 5.0; Windows 98);在同样的情况下使用Nokia 7110访问这个站点,HTTP_USER_AGENT就会是:Nokia7110/1.0(04.73
)。据此可以判断用户代理是什么类型的。

31. 可以得到用户代理的电话号码吗?

不可以,除非网关支持这个特点,WAP没有办法知道用户的电话号码。

32. 可以通过WML使得可以用WAP设备进行拨号吗?

WAP的电话功能可以使用Wireless Telephony Application Interface(WTAI)。

例如:

WMLScript: WTAPublic.MakeCall("9287787"); 

但是第一代的WAP设备不支持这个功能。

33. 能够从WAP设备中读取数据吗,例如:电话号码?

这里有一些通过HTTP的信息,但是十分有限。既然只有网关发送过来少量的信息,像WAP设备的号码可能无法读取。同时,在某些国家这还涉及到个人隐私的问题。
基本上丢弃的内容就是WAP网关传送给HTTP服务器的内容。这不同于WAP网关到网关。Phone.com的UP.Link网关是一个最好的例子。因为它在HTTP头中返回一个字符串叫做 UP_X_SUBN
O,里面包含了电话号码。Ericsson网关将传送一个辨别设备用的字符串,但是在明文中没有电话号码。
每次WAP设备向HTTP服务器请求一个URL,WAP网关就会将信息传送给HTTP服务器。
以下的PHP脚本显示了从网关过来的所有HTTP头的信息。可以使用WML浏览器进行测试。(http://wap.colorline.no/clientinfo.html)。其他的例子也可以在下面的UT
L中找到:http://wap.colorline.no/demos.html
第一个部分是取得所有的标准HTTP头信息。第二个部分是提取一个内容。

<?
  header("Content-type: text/vnd.wap.wml");
  echo("<?xml version=/"1.0/"?>/n");
echo("<!DOCTYPE wml PUBLIC /"-//WAPFORUM//DTD WML 1.1//EN/"
/"http://www.wapforum.org/DTD/wml_1.1.xml/">/n/n");
  echo("<!—Code written in Microsoft NOTEPAD.EXE à /n");
?>


  <card id="init" title="Client Info">
   


      <?
        // First part – standard HTTP stuff
        $headers = getallheaders();
        while (list($header, $value) = each($headers)) {
          echo strtoupper($header). ": ". $value. "<br/>/n";       
        }
        // Second part

// IP address of the client side
        echo("REMOTE_ADDR: ".$REMOTE_ADDR. "<br/>/n");
// Port at the client side
        echo("REMOTE_PORT: ".$REMOTE_PORT. "<br/>/n");
// Name of authenticated user
        echo("REMOTE_USER: ".$REMOTE_USER. "<br/>/n");
// Gateway Interface type
        echo("GATEWAY_INTERFACE: ".$GATEWAY_INTERFACE. "<br/>/n");
// Protocol used by the server
        echo("SERVER_PROTOCOL: ".$SERVER_PROTOCOL. "<br/>/n");
// Request Method
        echo("REQUEST_METHOD: ".$REQUEST_METHOD. "<br/>/n");
// Connection type
        echo("HTTP_CONNECTION: ".$HTTP_CONNECTION. "<br/>/n");
// Host it connected via (proxy)
        echo("HTTP_VIA: ".$HTTP_VIA. "<br/>/n");
      ?>
   


 
 
Henrik Gemal ( gemal@dk.net)也有一个在线的基于WML的工具BrowserSpy,来显示更多关于HTTP头的信息、服务器环境和用户的浏览器等等。有关这个工具的详细情况可以浏览h
ttp://wap.gemal.dk/
Werner Forkel 提交了一个Perl的脚本,可以显示电话号码(如果有)。可以在以下位置测试: http://wap.colorline.no/wap-faq/apps/subnotest.w
ml,同样也收集在: http://wap.colorline.no/demos.html.
这些程序只适合某个网关。如果要测试其他的网关,可能就显示不出电话号码。因此电话号码不能作为ID号来处理。至少因为不是一个全球的标准。 34. 有没有办法连接到电话号码?

在某些情况下,当在显示了一连串的号码之后,需要中断功能连接到一个电话号码上并拨号。例如:执行一个 dial:12345678 就非常像 mailto: 标签。
越来越多的浏览器都支持这个功能,但还不是所有。Phone.com, Mitsubishi 和 Ericsson 已经在浏览器中集成了这个基于Wireless Telephony Interface
specifications (WTAI)的功能。 WTAI将允许以下的URL将关闭连接并且拨号:

  <go href="wtai://wp/mc;+4712345678">Make a call to +47-12345678</go> 

Nokia 7110 已经有个功能叫做“Use Number”。它可以通过WML卡片查找一个类似于电话号码的列表,然后用户可以选择进行呼叫。注意用户必须分离这些数字以便它能正常工作。

35. 使用GET或者POST方式能传送多少字符?

使用GET或者POST方式所能传送的字符数目,不同的设备有不同的限制。一个GET通过UTL传送变量,能传送的数据总量比使用POST方式所能传送的数据要小。例如,Nokia 7110似乎对每个GET 
限制在512个字节左右,但是POST最大可以达到一个编译后卡片的大小(约1300字节)。UP.SDK 4.0将GET请求限制在970左右,最大可以达到一个编译后卡片的大小。
显然,卡片有时候保存了要发送给服务器的参数的内容,既然编译后的卡片大小有限制,那么肯定要影响到整个所能传输的数据。
在POST和GET之间没有太多的区别。比如这个没有很好地使用GET的例子。

<input type="text" name="var1" format="*N"/>
  


     <anchor>Send it
     <go href="somescript.cgi?variable=$(var1)" method="get"/>
     </anchor>
  

下面仍然是一个使用GET的请求,但是使用了<postfield>来传送参数。这个代码就漂亮多了。既然可以定义为GET,同样也很容易转成POST。

<input type="text" name="var1" format="*N"/>
  


     <anchor>Send it
     <go href="somescript.cgi" method="get">
       <postfield name="variable" value="$(var1)"/>
     </go>
     </anchor>
  

直接改为POST:

<input type="text" name="var1" format="*N"/>
  


     <anchor>Send it
     <go href="somescript.cgi" method="post">
       <postfield name="variable" value="$(var1)"/>
     </go>
     </anchor>
  

最好是做测试找到到底能传输多少数据。这里有个测试程序: http://wap.colorline.no/wap-faq/apps/putsize.php3

这个程序也可以在下面的URL中找到:http://wap.colorline.no/demos.html
该程序将产生一个卡片包含一个变量,里面包含了一定数量的字符X。用户可以选择传输是使用GET还是POST。在传输之后,脚本将要显示接收到的字符个数。
脚本生成一个页面来测试使用GET或者POST方式到底能发送多少个字符:

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">

  <head>
  <meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
  <meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/>
  </head>
  <card>
  <do type="prev" label="Back">
  <go href="putsize.php3"/>
  </do>
 


  <anchor>GET data
  <go method="get" href="putsize.php3">
  <postfield name=/"a/"
value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
  </go>
  </anchor>
 


 

36. 如何同HTML站点一样POST/CGI,返回表单数据到服务器?

如果使用:

<go method="post" href="mycgi.cgi"> 

并且使用:

<postfield name="fieldname" value="$NameOfInputField"/> 

就可以POST数据给CGI程序了。

37. POST无法工作是怎么回事?

有很多说POST参数将会丢失的流言,特别是在Nokia 7110。就笔者所知,还没有哪个Nokia 7110有这样的问题。这个问题主要是存在于网关或接收方。
测试显示Nokia SDK 2.18,当使用内建网关的时候,使用POST会出现问题。甚至当method 设置成“POST”的时候,服务器那边还是将POST请求作为 GET。
当使用POST的URL时 ,Nokia SDK 将会崩溃。在某些情况下URL的最后的字符将被删除。
POST Test页面将简单的POST的两个变量叫做“var1”和“var2”来显示整个变量的内容和HTTP头的内容。如果不能看到正确的变量内容,肯定有问题。检查HTTP头中的application
/x-www-form-urlencoded(注意!需要在变量中输入一些内容)。
这个方法解决了Nokia SDK 2.18的问题,可以把它配置到任意的公共网关来测试。笔者推荐使用 wapHQ 网关。
在其他的情况下,POST确实不工作,问题可能是HTTP头在服务器端解释的时候有问题。脚本语言,例如:ASP、Java或是CGI等等都是通过查看在HTTP头中的 application/x-www-f
orm-urlencoded 完全匹配的字符串。在某些情况下字符串可能有附加的数据,例如:charset="utf8" 。既然服务器端不是精确的匹配,它就不会查看HTTP头,因此POST就变量丢失了。

注意这不是浏览器的问题,在HTTP头加入字符集描述,将造成脚本语言方面的错误。
为了检测有关网关或浏览器的问题,仍使用上面的POST Test页面来测试。同样查看application/x-www-form-urlencoded 的输出,检查有没有附加的字符在结尾部分,如果有,
那么这就是服务器端的问题。
解决这个问题的方案很复杂,它随用户使用的脚本描述语言不同而不同,而且需要操作原代码。简单地说,解决方案就是需要人工读取HTTP头,不要使用脚本语言已经写好的读取方式。
这里有一个用ASP编写的解决方法。它显示了如何在POST中抓取数据。用户需要从二进制数据中发现需要的变量。

Dim lngToalByteCount
Dim vntRequestData

  lngTotalByteCount = Request.TotalBytes
  vntRequestData = Request.BinaryRead(lngTotalByteCount)

全部的代码,就应该像下面的代码:

<%@ Language=VBScript %>
<%
  Dim Temp, i, sPost, sWMLDeck

  'Converts the binary data to a string.
  For i = 1 To Request.TotalBytes
    Temp = Request.BinaryRead(1)
    sPost = sPost & Chr(AscB(Temp))
  Next

  'Parses out the values of the POSTED variables (in this
  'example myvar1 and myvar2).
  Dim sVar1, sVar2
  sVar1 = getVar("myvar1", sPost)
  sVar2 = getVar("myvar2", sPost)

  'Writes the WML Deck displaying the POSTED Variables
  sWMLDeck = "<?xml version=""1.0""?>" & vbCrLf
  sWMLDeck = sWMLDeck & "<!DOCTYPE wml PUBLIC ""-//WAPFORUM//DTD WML 1.1//EN"" "
  sWMLDeck = sWMLDeck & """http://www.wapforum.org/DTD/wml_1.1.xml"">" & vbCrLf
  sWMLDeck = sWMLDeck & vbCrLf & "" & vbCrLf & vbTab
  sWMLDeck = sWMLDeck & "<card id=""main"" title=""POST TEST"">" & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & "

" & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & vbTab & "myVar1: " & sVar1 & "<br/>" & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & vbTab & "myVar2: " & sVar2 & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & "

" & vbCrLf & vbTab
  sWMLDeck = sWMLDeck & "" & vbCrLf & ">/wml>"

  Response.ContentType = "text/vnd.wap.wml"
  Response.Write(sWMLDeck)

  'Quick function for picking out the values of the POSTed variables.
  'sKey is the variable name, sRaw is the POST string.
  Private Function getVar(sKey, sRaw)
  Dim sRetVal
If InStr(sRaw, sKey) Then
sRetVal = Mid(sRaw, InStr(sRaw, sKey) + Len(sKey) + 1)
If InStr(sRetVal, "&") Then
sRetVal = Mid(sRetVal, 1, InStr(sRetVal, "&") - 1)
End If
End If
getVar = sRetVal
  End Function
%>

35. 使用GET或者POST方式能传送多少字符?

使用GET或者POST方式所能传送的字符数目,不同的设备有不同的限制。一个GET通过UTL传送变量,能传送的数据总量比使用POST方式所能传送的数据要小。例如,Nokia 7110似乎对每个GET 
限制在512个字节左右,但是POST最大可以达到一个编译后卡片的大小(约1300字节)。UP.SDK 4.0将GET请求限制在970左右,最大可以达到一个编译后卡片的大小。
显然,卡片有时候保存了要发送给服务器的参数的内容,既然编译后的卡片大小有限制,那么肯定要影响到整个所能传输的数据。
在POST和GET之间没有太多的区别。比如这个没有很好地使用GET的例子。

<input type="text" name="var1" format="*N"/>
  


     <anchor>Send it
     <go href="somescript.cgi?variable=$(var1)" method="get"/>
     </anchor>
  

下面仍然是一个使用GET的请求,但是使用了<postfield>来传送参数。这个代码就漂亮多了。既然可以定义为GET,同样也很容易转成POST。

<input type="text" name="var1" format="*N"/>
  


     <anchor>Send it
     <go href="somescript.cgi" method="get">
       <postfield name="variable" value="$(var1)"/>
     </go>
     </anchor>
  

直接改为POST:

<input type="text" name="var1" format="*N"/>
  


     <anchor>Send it
     <go href="somescript.cgi" method="post">
       <postfield name="variable" value="$(var1)"/>
     </go>
     </anchor>
  

最好是做测试找到到底能传输多少数据。这里有个测试程序: http://wap.colorline.no/wap-faq/apps/putsize.php3

这个程序也可以在下面的URL中找到:http://wap.colorline.no/demos.html
该程序将产生一个卡片包含一个变量,里面包含了一定数量的字符X。用户可以选择传输是使用GET还是POST。在传输之后,脚本将要显示接收到的字符个数。
脚本生成一个页面来测试使用GET或者POST方式到底能发送多少个字符:

<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">

  <head>
  <meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
  <meta forua="true" http-equiv="Cache-Control" content="must-revalidate"/>
  </head>
  <card>
  <do type="prev" label="Back">
  <go href="putsize.php3"/>
  </do>
 


  <anchor>GET data
  <go method="get" href="putsize.php3">
  <postfield name=/"a/"
value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
  </go>
  </anchor>
 


 

36. 如何同HTML站点一样POST/CGI,返回表单数据到服务器?

如果使用:

<go method="post" href="mycgi.cgi"> 

并且使用:

<postfield name="fieldname" value="$NameOfInputField"/> 

就可以POST数据给CGI程序了。

37. POST无法工作是怎么回事?

有很多说POST参数将会丢失的流言,特别是在Nokia 7110。就笔者所知,还没有哪个Nokia 7110有这样的问题。这个问题主要是存在于网关或接收方。
测试显示Nokia SDK 2.18,当使用内建网关的时候,使用POST会出现问题。甚至当method 设置成“POST”的时候,服务器那边还是将POST请求作为 GET。
当使用POST的URL时 ,Nokia SDK 将会崩溃。在某些情况下URL的最后的字符将被删除。
POST Test页面将简单的POST的两个变量叫做“var1”和“var2”来显示整个变量的内容和HTTP头的内容。如果不能看到正确的变量内容,肯定有问题。检查HTTP头中的application
/x-www-form-urlencoded(注意!需要在变量中输入一些内容)。
这个方法解决了Nokia SDK 2.18的问题,可以把它配置到任意的公共网关来测试。笔者推荐使用 wapHQ 网关。
在其他的情况下,POST确实不工作,问题可能是HTTP头在服务器端解释的时候有问题。脚本语言,例如:ASP、Java或是CGI等等都是通过查看在HTTP头中的 application/x-www-f
orm-urlencoded 完全匹配的字符串。在某些情况下字符串可能有附加的数据,例如:charset="utf8" 。既然服务器端不是精确的匹配,它就不会查看HTTP头,因此POST就变量丢失了。

注意这不是浏览器的问题,在HTTP头加入字符集描述,将造成脚本语言方面的错误。
为了检测有关网关或浏览器的问题,仍使用上面的POST Test页面来测试。同样查看application/x-www-form-urlencoded 的输出,检查有没有附加的字符在结尾部分,如果有,
那么这就是服务器端的问题。
解决这个问题的方案很复杂,它随用户使用的脚本描述语言不同而不同,而且需要操作原代码。简单地说,解决方案就是需要人工读取HTTP头,不要使用脚本语言已经写好的读取方式。
这里有一个用ASP编写的解决方法。它显示了如何在POST中抓取数据。用户需要从二进制数据中发现需要的变量。

Dim lngToalByteCount
Dim vntRequestData

  lngTotalByteCount = Request.TotalBytes
  vntRequestData = Request.BinaryRead(lngTotalByteCount)

全部的代码,就应该像下面的代码:

<%@ Language=VBScript %>
<%
  Dim Temp, i, sPost, sWMLDeck

  'Converts the binary data to a string.
  For i = 1 To Request.TotalBytes
    Temp = Request.BinaryRead(1)
    sPost = sPost & Chr(AscB(Temp))
  Next

  'Parses out the values of the POSTED variables (in this
  'example myvar1 and myvar2).
  Dim sVar1, sVar2
  sVar1 = getVar("myvar1", sPost)
  sVar2 = getVar("myvar2", sPost)

  'Writes the WML Deck displaying the POSTED Variables
  sWMLDeck = "<?xml version=""1.0""?>" & vbCrLf
  sWMLDeck = sWMLDeck & "<!DOCTYPE wml PUBLIC ""-//WAPFORUM//DTD WML 1.1//EN"" "
  sWMLDeck = sWMLDeck & """http://www.wapforum.org/DTD/wml_1.1.xml"">" & vbCrLf
  sWMLDeck = sWMLDeck & vbCrLf & "" & vbCrLf & vbTab
  sWMLDeck = sWMLDeck & "<card id=""main"" title=""POST TEST"">" & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & "

" & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & vbTab & "myVar1: " & sVar1 & "<br/>" & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & vbTab & "myVar2: " & sVar2 & vbCrLf
  sWMLDeck = sWMLDeck & vbTab & vbTab & "

" & vbCrLf & vbTab
  sWMLDeck = sWMLDeck & "" & vbCrLf & ">/wml>"

  Response.ContentType = "text/vnd.wap.wml"
  Response.Write(sWMLDeck)

  'Quick function for picking out the values of the POSTed variables.
  'sKey is the variable name, sRaw is the POST string.
  Private Function getVar(sKey, sRaw)
  Dim sRetVal
If InStr(sRaw, sKey) Then
sRetVal = Mid(sRaw, InStr(sRaw, sKey) + Len(sKey) + 1)
If InStr(sRetVal, "&") Then
sRetVal = Mid(sRetVal, 1, InStr(sRetVal, "&") - 1)
End If
End If
getVar = sRetVal
  End Function
%>

1:41 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
WAP开发(一)
1. 开发WAP软件需要哪些工具?

为了开发WAP应用程序,需要一个WAP网关(注意:这里的网关可能是指支持WML的服务器。可以通过配置WWW服务器达到这个目的)和WAP工具包。工具包应当包括模拟器和能让开发者浏览WML网页。WML页
面的开发和HTML页面的开发一样,可以使用Notepad或者其他文本编辑器来进行编辑。

2. 有哪些公司现在提供这样的开发环境?

Nokia、Ericsson、UpPhone和Motorola都提供免费的WAP网关和工具包。
Nokia:Nokia Toolkit和Nokia WAP Server。
Ericsson:Ericsson R320和WapIDE。
UpPhone:UpPhone SDK。
Motorola:Motorola ADK。

3. 开发WAP应用一定要有WAP手机吗?

不是,当开发WAP应用的时候,不一定需要WAP手机。模拟器可以帮助开发者解决大部分的问题。但是如果是开发商业网站,特别是想知道各种移动电话在显示WML页面上的差别的时候,最好是配备一个。目前各种手机
对WML标记的支持和中文的支持状况大不相同,因此WAP手机还是必要的。

4. 开发者需要一个WAP网关吗?

不是很必要。如果只想进行简单的WAP内容服务,可以使用现有的Web服务器(只需要修改MIME类型)。移动电话会通过坐落在本地的网关连接到你的服务器上。
但是在网关上驻留开发者的程序有很多好处。既然开发者的程序是网关的一个部分,开发者就可以知道呼叫号码、身份、位置等等。

5. 可以看到WML的源代码么?

如果开发者使用SDK浏览的时候将能够看到WML的代码。如果只有一个HTML浏览器,可以访问“Fetch Page”服务(http://www.webcab.de)来取得代码。这个可以显示在Inter
net上的任何WML页面中。

6. 可能在WML中加入applets吗?

不能。
7. 可以使用HTML开发工具来开发WAP应用吗?

在大多数情况下开发工具是使用基于PC的浏览器。HTML、JavaScript和Java对于WAP开发来说都没有用处。但是,越来越多的开发工具在加入对WML的支持。
Allair的Cold Fusion 4.5 和 HomeSite已经有WML支持,虽然Allair也许需要清除一些BUG。另外PHP和ASP在Coldfusion/HomeSite也能支持。
可以到 Marjolei Katsma的 HomeSite Help site 上得到更多的信息。

8. 可以通过WML页面来操作数据库吗?

可以,与创建HTML页面相同。任何相关的服务器端的技术都可以用来生成WML页面。

9. 可以使用CGI生成WML页面吗?

当然。可以用创建HTML同样的方法来创建WML。如果想书写一个CGI来创建WML,只要记住在页面的开头正确设置MIME类型。具体的形式根据所使用的语言不同而不同。例如在Perl中:

print ("Content-type:application/vnd.wap.wml /n/n/n"); 

注意至少要使用2个换行。

10. 如何使用Cold Fusion来生成页面?

使用Cold Fusion只需要加上:

<CFCONTENT type="text/vnd.wap.wml">

11. 如何使用PHP来书写动态的WML页面?

PHP(和大多数其他服务端脚本语言一样)可以被用来书写动态的WML内容。只需要将输出的标记限制在WML微型浏览器可接受的范围内。
注意PHP有很多内建的HTML功能,特别是错误功能,这些功能WML微型浏览器可能无法识别。
PHP同样可以在一个HTML文件中编写出既适合于HTML,也适合于WML的内容。PHP的源代码对于客户端来说是不可见的。因此可以针对HTML浏览器输出HTML页面,针对WML浏览器输出WML页面。
可以在开发PHP编写的WML页面的时候把以下代码加在开头:

<?

// header("Content-type: text/vnd.wap.wml");
  echo("<?xml version=/"1.0/"?>/n");
echo("<!DOCTYPE wml PUBLIC /"-//WAPFORUM//DTD WML 1.1//EN/"
//"http://www.wapforum.org/DTD/wml_1.1.xml/">/n/n");
?>

基于PC的浏览器将忽略这些无法理解的WML标记。但是如果想在WAP设备或者模拟器上测试的时候,只需要将"//"去掉,页面自动变成WML页面。

12. 使用PHP动态输出WML

这些例子生成一个非常有用的应用叫做:PizzaCalc。它将输入所有的pizza的帐单和人的数目,可以算出每个人的花费。
应用生成一个动态的页面叫做“calc”或者“input”。注意到所有的转义字符例如双引号。该页显示了一个简单的变量处理,和如何传递参数到另外的卡片:
使用WML浏览器就可以测试应用程序:
http://wap.colorline.no/wap-faq/apps/pizzacalc.html
或者输入:
http://wap.colorline.no/demos.html选择应用。

<?
header("Content-type: text/vnd.wap.wml");
echo("<?xml version=/"1.0/"?>/n");
echo("<!DOCTYPE wml PUBLIC /"-//WAPFORUM//DTD WML 1.1//EN/"
/"http://www.wapforum.org/DTD/wml_1.1.xml/">/n/n");
echo("<!--The application PizzaCalc was originally made by The Crusaders
www.crusaders.no on the Commodore Amiga -->/n");
echo("<!-- It was unfortunately not possible to emulate the crap interger handling of the
original program -->/n");
?>


<?
  if($action == "calc") {
    echo("<card id=/"result/" title=/"PizzaCalc/">/n");
    echo("<do type=/"prev/" label=/"Back/">/n");
    echo("<go href=/"pizzacalc.html#input/"/>/n");
    echo("</do>/n");
    echo("

/n");
    echo("The cost per eater will be ".$total / $eaters."<br/>/n");
  }
  else {
    echo("<card id=/"input/" title=/"PizzaCalc/">/n");
    echo("

/n");
echo("<anchor>Split Pizza bill
<go href=/"pizzacalc.html?total=/$(total)&eaters=/$(eaters)&action=calc/"/>
</anchor>/n");
    echo("<br/>/n");
    echo("Total cost: <input type=/"text/" name=/"total/" format=/"*N/"/>/n");
    echo("Eaters: <input type=/"text/" name=/"eaters/" format=/"*N/"/>/n");
  }
?>



13. 可以使用Java Servlet来生成WML页面吗?

当然。可以使用创建HTML同样的方法来创建WML。如果想书写一个CGI来创建WML,只要记住在页面的开头正确设置MIME类型:
response.setContentType("text/vnd.wap.wml");

14. 可以使用ASP、Perl等生成动态的应用吗?

是的。可以使用任何服务器端的脚本语言来生成WAP应用。
15. 如何使用ASP书写WML内容?

ASP(Active Server Pages)可以做到和PHP一样,也可以用来书写动态的WML。如果需要一些好的例子请参考Luca Passani's WAP and ASP articles。或
者查看Jean-Luc Praz's (jeanluc@corobori.com)。更多的ASP例子在:http://www.corobori.com/wap/
16. 在使用ASP动态输出WML页面的时候,已经设置了Content-type,但是浏览器返回的仍然是text/html,有什么问题吗?

如果在ASP脚本中有一个错误,那么诊断程序会发还一个HTML页面,请检查脚本。

17. 在使用ASP生成WML页面的时候出现了错误: <MIME type "text/html" is not supported>,会是什么问题?

这个问题是Web浏览器不知道WML的正确类型,修改ASP的第一行,加入:

<Response.ContentType = "text/vnd.wap.wml"> 

后就可以工作了。

18. 下面的代码有什么问题吗?

<%Response.ContentType = "text/vnd.WAP.WML"%>
<?xml version="1.0"?>

去掉<?xml version="1.0"?>之前的空格。XML解释器希望在这行中没有其他字符,甚至是空行。

19. ASP代码可以在模拟器上工作,在真正的浏览器上怎么不行?

在很多模拟器上没有像真正的WML浏览器那么严格。这些对于那些没有使用网关的模拟器(Nokia SDK/Toolkit)来说更是这样,有些就根本没有使用网关(WinWAP、WapMAN)。
一个真正的WML浏览器应该只读取二进制的数据(从WML编码得来的)WMLC,对于网关应该将文本WML转换/编译成WMLC。语法是非常严格的。ASP是为HTML浏览器设置的,但是HTML没有WML那么
严格。
这里在ASP生成动态页面的时候有一个微小的“bug”。它在WML浏览器上不允许有任何地方输出白行(例如:空格,回车,换行)。注意到有些网关可能会修正这些问题,但有的则不管(例如:CMG网关)。
下面是一个常见的ASP代码用来输出WML页面开头的MIME类型:

<%Response.ContentType = "text/vnd.wap.wml"%>
<?xml version="1.0"?>

问题就在ASP将会在 .wml"%> 和 <?xml vers 之间输出换行和回车。这两行就被分割了。这将打乱WML代码的内容。WML必须以“<”开头,而且第一行是<?xml version="1.0"?>。
就上面的WML页面回车/换行将会出现问题。
最简单的解决办法是:

<%Response.ContentType = "text/vnd.wap.wml"%><?xml version="1.0"?>

在XML定义正确的格式化以后,后面的部分WML对空格就没有那么严格的要求。

要注意的是有些网关在输出ASP的时候会有问题,因此在WML代码中最好使用 Response.Write 而不是<%=MyVar%>。
20. 如何使用Perl来生成WML内容?

和其他Server端程序一样。Perl也可以用来书写漂亮的WAP应用程序。
最常见的就是如何使用Perl输出正确的MIME类型,下面的例子说明了这一点:

print "Content-type: text/vnd.wap.wml/n/n";
print "<?xml version=/"1.0/"?>/n";
print "<!DOCTYPE wml PUBLIC /"-//WAPFORUM//DTD WML 1.1//EN/"
/"http://www.wapforum.org/DTD/wml_1.1.xml/">/n";
print "/n";
……

21. 应当如何下手书写WAP应用程序?

其实需要的只是Text编辑器。但是使用一个开发工具可以节约很多时间。
在这之前应该浏览一下WAP的权威站点:www.wapforum.com
在Nokia WAP 开发论坛中进行注册,并且下载Nokia WAP Developer Toolkit 。Toolkit中的PDF文件可以给出一定的WML和WMLScript指导。Nokia To
olkit需要JRE (Java Runtime Environment) v.1.2.2 或者更高版本。
虽然工具可以用来为WAP设备设计应用,但是不是为专门的移动电话。在WAP开发工具上所看到的并不代表用户在手机上所看到的。为了确定想看到的事情,最好需要一个WAP设备,例如移动电话,或者模拟器。
Nokia WAP SDK 2有一个7110的模拟器。模拟器是一个有效的检测方式,能检测程序中的bug。 Nokia SDK 同样还包括一个小的WAP server让开发者可以从本地或者HTTP服务
器上下载WML页面。
到 Phone.com 开发站点注册后,Phone.com 提供UP.browser。这是最流行的浏览器,特别是在美国,Phone.com 提供UP.SDK。 在注册之后就可以下载。
对于Ericsson R320 和 R380是最近的事情。应该注册并查看Ericsson's Developer's Zone 来得到开发工具。R380是一个非常好的模拟器,在 Symbian 不需
要注册就可以下载。Ericsson 没有公开的为R320的模拟器。
Motorola 有一个平台叫做 Mobile Internet eXchange 或者 MIX 。Mobile Application Development Kit 已经开发出一个开发平台,即为
WAP也为Motorola的 VoxML。在注册后,可以在下面的网址找到数据包。

http://www.motorola.com/MIMS/MSPG/cgi-bin/spn_madk.cgi. 

WAPmine 是一个独立的应用,叫做 WAPPage 是一个所见即所得的编辑工具。而且有一个XML树型控件来编辑WML标签。
如果在开发公共应用程序时,想在很多设备上测试你的程序,就像在不同的浏览器上测试HTML页面一样。注意在不同的WML浏览器上的差别,可能比在不同的HTML浏览器上的差别要大。

22. 如何编写和测试WML页面?

现在有很多SDK。AnywhereYouGo.com有WAP SDK和IDE列表,可以下载一个来用。任何文本编辑器都可以书写一个简单的WML页面,当然HTML编辑器也可以(特别是那些支持个人定义标签
的),例如:Allaire Homesite (http://www.allaire.com )。可以使用SDK来做简单的测试,但是对于大的项目可能要困难些。AnywhereYouGo.com已经建立
一套基于Web的工具来帮助WAP测试。
23. 哪儿可以在找到WML的测试工具?

首先确定WML代码是正确的,然后再使用WML测试工具。
有一个非常好的测试工具在Zygo Communications(http://wap.z-y-g-o.com/tools/),测试工具是用Perl写的。里面还有其他的工具可供下载。

24. 如何操作WML页面?

操作WML页面或者卡片,最简单的办法是通过现有的网关。大多数移动电话提供者将功能都放在主页上,在上面可以通过WAP设备操作。网关的链接一般叫做“Go to URL”。当选择以后,WAP设备将通过网关
操作指定的普通IP或者URL。在这种情况下,网关读取从WAP设备发送给网关的WML内容,就像PC浏览器读取内容的过程一样。
有些营运商选择不让他们的用户操作其他的站点。这个就像Internet Service Provider只允许用户操作ISP自己的站点。像这样的做法是不明智的,这样会发现自己的用户去其他地方了。
如果要坚持这种方法,可以通过ISP拨号或者使用一个公共的网关来取得其他的WAP资源。

25. 有没有一个友好的方式来管理WML内容?

还没有。虽然Oracale正在开发数据库驱动的文档服务,被称为Panama,可以支持WAP分发。

26. 如何防止用户代理cache页面?

如果用户使用ASP,应该加入一行<%Response.expires=-1%> ,这个将阻止Cache。

1:39 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
基于短信网关WAP推送的实现
WAP PUSH既有信息发送主动、及时的特点,又有可随时随地接收信息的优势,因而具有良好的应用前景。该文简单介绍了WAP PUSH系统框架、推送协议和推送方式,重点介绍了通过短信网关实现WAP推送的实现方法。
  1 WAP推送技术简介

  1.1 什么是WAP推送

  WAP推送(PUSH)技术是一种建立在客户服务器上的机制,就是由服务器主动将信息发往客户端的技术。同传统的拉(PULL)技术相比,最主要的区别在于推送(PUSH)技术是由服务器主动向客户机发送信息,而拉(PULL)技术则是由客户机主动请求信息。PUSH技术的优势在于信息的主动性和及时性。

  PUSH技术在Internet中没能取得大的成功,原因是多方面的。主要原因在于固定网中计算机等固定设备为用户提供了足够的资源和能力去查找信息所以用户通常将它作为一个浏览信息的窗口,而不是被动的信息接收者。同时固定网用户对于信息准确性的要求远甚于对其及时性的要求,因此PUSH技术未能得到广泛的应用。

  而在移动网中,由于存在着网络带宽、移动终端能力以及自费标准高昂等诸多限制,使得用户查找信息受到了一定的限制,如果将重要的信息主动及时地推送到用户的移动设备上无疑会大大方便用户。移动通信的优点是移动设备能够随时随地接收信息因此PUSH技术在移动网中可以大展拳脚,WAP PUSH正是PUSH技术和移动通信两者扬长避短相结合的产物。WAP PUSH是在移动网络中应用的PUSH技术,它结合了一般PUSH技术和移动网络的特点。它的系统框架、使用协议和服务方式与固定网上的PUSH技术有很大不同。

  1.2 WAP PUSH系统框架

  WAP PUSH框架主要包括推送发起者(PI:PUSH Initiator)、推送代理网关(PPG:PUSH ProxyGateway)和推送客户(PC:PUSH Client)三个功能部分。PI位于Internet中,而PC在WAP领域,PI和WAP客户端所使用的协议是不同的,需要在中间建立一个协议转换网关即PPG。PPG通过推送访问协议(PAP:PUSH AccessProtocol)与PI通信,通过推送空间传输协议(PUSH OTA:PUSH over-the-Air)完成向客户推送信息的数据传输任务。

  PPG完成推送体系结构中的大部分工作,包括从Internet到移动网的访问接入,以及与其有关的认证、安全、客户端控制等所有工作。PPG所提供的主要服务包括:1)PI的标识、鉴权和访问控制;2)对推送内容进行语法分析,并依据数据类型定义(DTD)检错纠错;3)客户寻址与信息传输;4)PAP与PUSH OTA间的协议转换;5)为提高无线信道中的传输效率,对信息进行压缩、编译等处理。

  另外,PPG还可以通过别名机制实现组播和广播,即将某些特定的地址别名映射到组播或广播的操作中,具体方案可以由系统实现者决定。不同的客户端,其能力是不同的,PPG还要负责响应PI的客户能力查询请求,以便于PI针对不同的客户端构造合适的内容格式。

  1.3 推送协议

  PAP是PI与PPG间的通信协议,它使用可扩展标记语言(XML)作为消息的描述语言,通过简单的请求响应机制完成数据的传输。PAP可以在多种通信协议(包括超文本传输协议(HTTP)、简单邮件传输协议(SMTP)等)之上实现。

  而PUSH OTA是运行于无线会话协议(WSP)之上的一个较为简单的协议层,负责从PPG到客户代理的数据传输。PUSH OTA可使用面向连接的会话和无连接会话两种WSP层服务,对于使用连接会话的推送,需要在PPG和客户端间预先有一个激活的会话上下文;对于无连接的推送,则通过预留的端口来完成通信。

  1.4 推送服务方式

  WAP的推送协议中定义了服务指示(SI:Service Indication)和服务加载(SL:Service Load)两项服务,以给用户和网络运营者更多的选择。服务指示是将新信息的指示和相关的通用资源标识符(URI)推送给用户,由用户选择是立即处理信息还是以后处理。服务加载是将一项服务的URI推送给用户,然后客户端自动地使用PULL技术根据该URI启动服务。两种服务的区别在于用户是否介入推送信息的处理过程。SL对推送信息的处理对用户来说是透明的,而SI则在指示用户的同时,请用户对随后的处理做出选择。
PUSH可以将某一站点或某一业务的链接通过短信发送到支持WAP PUSH功能的手机上,这样用户只需要阅读这条短信,打开短信中的链接,就可以直接访问业务了。因此,WAP PUSH实现了短信和WAP业务的结合,节省了用户寻找业务的时间,方便用户直接找到并使用自己喜欢的业务。

  2 短消息网关简介

  短消息网关(ISMG)是处于短消息中心(SMSC)和业务提供商(SP)之间的设备,它为这两个实体的数据交换提供安全、快捷的通道。网关与短消息中心之间使用SMPP协议(Short Message Peer to Peer,短消息点对点协议), 与SP之间使用CMPP协议(China Mobile Peer to Peer,中国移动点对点协议),因此短消息网关需要完成协议的转换、计费、路由、安全和网络管理等功能。具体说来, SMPP通信代理系统主要实现网关和GSM网中短消息中心(SMSC)的连接,确保准确接收和发送数据,实现高效、可靠的数据传输。为了达到规范要求的不超过0.001%的数据丢包率,SMPP通信代理需要支持流量控制。CMPP通信代理系统主要是实现和SP服务提供商的连接,与SMPP通信代理系统不同的是,由于协议的影响,CMPP通信代理是服务器端,需等待SP的连接,而SMPP通信代理是客户端,需要主动连接SMSC。短消息网关处理系统是网关中最复杂的处理进程,它完成的任务包括:向GNS(汇接网关) 查询路由,维护路由表,进行协议转换和数据分发。防火墙系统主要为网关系统提供安全保障,它包括IP包过滤和身份验证。短信网关计费系统主要形成各种计费话单,为计费提供依据。业务管理系统主要完成对业务进行统计报告,生成报表,为运营者对用户数据的添加、修改、删除以及对网关系统的监控、查询、操作和维护提供接口和界面。

  3 基于短信网关发送WAP PUSH

  WAP PUSH的发送有两种途径,一个是通过PPG网关,另外一个是通过SMPP协议。其中SMPP是一个基本协议,在中国主要有三个由其派生的协议:中国移动的CMPP协议,中国联通的SGIP(在CDMA上是ETIP),以及小灵通的SMGP。通过中国移动的PPG网关发送WAP PUSH有着开发周期长,调测流程较复杂等不足,而使用CMPP协议即基于短信网关来进行WAP PUSH发送灵活性比较高,相对比较简单。

  3.1 WAP PUSH发送的实现模式

  可通过计算机串口上连接GSM MODEM,用它向手机发送WAP PUSH。这种方法发WAP PUSH又分三种模式:BLOCK 模式、TEXT 模式和PDU 模式。BLOCK 模式现在用的很少了, TEXT 模式则只能发送ASCII 码,它不能发送中文的UNICODE码,而PDU 模式开发起来则较为复杂,它需要编写专门的函数来将文本转换为PDU 格式,但PDU 模式被所有手机支持,可以使用任何字符集,它也是手机默认的编码方式,所以选用PDU模式发送WAP PUSH.

  3.2 PDU 模式
 
  用PDU 模式发送 WAP PUSH可以使用三种编码: 7-bit 编码、8-bit 编码和UCS2 编码。7-bit 编码用于发送普通的ASCII 字符,8-bit 编码通常用于发送数据消息,UCS2 编码用于发送Unicode 字符。由于要实现中文WAP PUSH的发送,所以选择用UCS2 编码,即中文Unicode 码。
⑴ UCS2 编码原理 所谓UCS2 编码,是将单个的字符(1-2 个字节)按ISO/IEC10646 的规定,转变为16 位 的Unicode 宽字符。即将单个的字符转换为由四位的‘0’-‘9’、‘A’-‘F’的数字和字 母组成的字符串。待发送的消息以UCS2 码的形式进行发送。
⑵ 通过UCS2 编码我们得到中文Unicode 码,接着就可以进行发送PDU 串的编制了。从表面上看,PDU 串是ASCII 码串,同样由‘0’-‘9’、‘A’-‘F’这些数字和字母组成。它们是8 位字节的十六进制数,或者BCD 码十进制数。PDU 串除了包含所发送的消息本身外,还包含很多其它参数信息,如服务中心号码、目标号码和编码方式等

  例如

0051000BA13108086406F600F5A7850B05040B8423F_
0000303010129060603AE81EA8DCA02056A0045C6080C033231312e_
3133362e3135332e33302f776170707573682f70757368496e6465782e_
6a73703f7075736849643d3035303531313134313630353231000103E8A_
FB7E782B9E587BBE4BBA5E4B88BE993BEE68EA5E88EB7E58F96E5BDA9E4BFA1E58685
E5AEB9000101_

  为一串可以成功发送的WAP PUSH,其中包括了汉字描述和WAP页面地址。具体分析如下

  00 SMSC 地址信息的长度 00表示用手机上设置短信中心号码,PDU 串的“SMSC 址格式”段和“SMSC 地址”段将省去

  51 基本参数(TP-MTI/VFP) 不要求发送回复

  00 消息基准值(TP-MR)

  0B 对方电话的长度

  A1 目标地址格式 A1表示为国内格式

  3108086406F6 目标地址,补‘F’凑成偶数位后奇偶位互换

  00 协议标识(TP-PID) 是普通GSM 类型,点到点方式

  F5 用户信息编码方式 (TP-DCS)

  A7 有效期(TP-VP)

  85 用户信息长度(TP-UDL)

  0B WAP PUSH头部的总长度

  05040B8423F0表示接下来是一个WAP PUSH

  00 表示是Concatenated Short Messages

  03 长度

  03 reference number

  01 表示分成1个短信发送

  01 当前包的序号

  29060603AE81EA8DCA WSP

  02 标记位

  05 -//WAPFORUM//DTD SI 1.0//EN

  6A UTF-8

  00 标记开始

  45 <si>

  C6 <indication

  08 <action=signal-high>

  0C href="http://

  03 字符串开始

3231312e3133362e3135332e33302f776170707573682f
70757368496e6465782e6a73703f7075736849643d3035303531313134313630353231 URL

  00 URL 字符串结束

  01 >

  03 内容描述字符串开始

  E8AFB7E782B9E587BBE4BBA5E4B88BE993BEE68EA_
  5E88EB7E58F96E5BDA9E4BFA1E58685E5AEB9 内容描述字符串

  00 内容描述字符串结束

  01 </si>

  01 </indication>

  由以上分析可以看出,WAP PUSH可以被当作一种特殊的短信来发送,WAP PUSH包发送的内容实际上跟通过PPG网关发送的XML原理相同,但是经过了压缩。压缩之后的格式称为WBXML,这种格式将一些标记用代码来表示。然而WBXML的缩略标记分为两部分,一部分是所有类型的XML都通用的,另一部分是不同类型的XML有着不同的解释。
用户接收到此类信息时,在客户端手机支持WAP的情况下,可以直接访问到信息中加载的WAP网站地址,这样服务器也达到了推广业务方便用户使用的目的。由于在UCS2 编码方式下,可发送短消息的最大字符数是140字节,即WAP PUSH中的推送URL与描述文字的总字符数为140,因此描述文字的字数限制与推送的URL长度有关。

  4 结束语

  WAP PUSH技术结合了PUSH技术的优势和移动通信服务的特性,具有良好的应用前景。但是WAP PUSH技术仍然存在着一些亟待解决的问题,如信息的鉴权与认证、信息的准确性、如何避免垃圾信息等。如何解决好这些问题将是WAP PUSH技术成功的关键。另外,随着GPRS技术和3G无线通信技术的发展,无线信道的带宽将逐步增大,WAP PUSH也将能进一步推送多媒体信息,有着更宽广的应用前景

0:12 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
9月15日
什么是WAP PUSH?
 
所谓推(PUSH)技术是一种基于客户服务器机制、由服务器主动将信息发往客户端的技术,其传送的信息通常是用户事先预订的。同传统的拉(PULL)技术相比,最主要的区别在于前者是由服务器主动向客户机发送信息,而后者则是由客户机主动请求信息。PUSH技术的优势在于信息的主动性和及时性,而弱势则是信息的准确性较差。 

    PUSH技术在Internet中没能取得大的成功,原因是多方面的。在固定网中计算机等固定设备为用户提供了足够的资源和能力去查找信息所以用户通常将它作为一个浏览信息的窗口,而不是被动的信息接收者。固定网用户对于信息准确性的要求远甚于对其及时性的要求。 

    而在移动网中,由于存在着网络带宽、移动设备能力以及高昂的资费标准等诸多限制,用户无法像在固定网中一样方便地查找信息,如果将重要的信息主动及时地推送到用户的移动设备上无疑会大大方便用户。移动通信的优点是移动设备能够随时随地接收信息因此PUSH技术可以在移动网中大显身手,WAP  PUSH正是PUSH技术和移动通信两者扬长避短相结合的产物。 

    WAP  PUSH是在移动网络中应用的PUSH技术它既具有一般PUSH技术的特点又拥有移动网络的特点。它的系统框架、使用协议和服务方式与固定网上的PUSH技术有很大不同。 

         WAP  PUSH技术结合了PUSH技术的优势和移动通信服务的特性,具有良好的应用前景。将PUSH技术应用于移动通信领域可以产生许多电信增值业务这包括移动中收发电子邮件,随时获得股价信息、天气预报、新闻以及位置相关服务等。所有这些服务的共同特点在于用户对信息的及时性要求比较高,用户希望能够通过手机、PDA等移动设备随时随地地得到该种服务。但是,WAP  PUSH技术仍然存在着一些亟待解决的问题,如信息的鉴权与认证、信息的准确性、如何避免垃圾信息等。如何解决好这些问题将是WAP  PUSH技术成功的关键。另外,随着GPRS技术和3G无线通信技术的发展,无线信道的带宽将逐步增大,WAP  PUSH也将能进一步推送多媒体信息。
其实WAP Push没有那么神秘- -
                                      


只要写一个XML文件,然后用WBXML编码就可以了。下面是一个Sample

<si>

  <indication href="http://wap.fractalist.cn/" action="signal-high">

    挑战脉动,挑战你我

  </indication

</si>

这个XML的DTD定义在http://www.openmobilealliance.org/tech/dtd/si.dtd

WMXML定义在http://www.w3.org/1999/06/NOTE-wbxml-19990624/

编码后作为二进制短消息发送给用户,记得讲tp_udhi设为1就可以了

WBXML编码的代码如下:

#include <map>
#include <vector>
#include
#include <time.h>
#pragma warning(disable : 4267)
#include <StringHelper.h>
#pragma warning(default : 4267)
#include <boost/cstdint.hpp>

#include <sstream>
#include

using boost::uint8_t;
using std::cout;
using std::endl;
using std::setw;
using std::hex;
using std::setfill;

/// <summary>
/// Series of well known constants and static uint8_t values used when encoding
/// a document to WBXML
/// </summary>
class WBXML
{
public:
 static const uint8_t CHAR_NULL = 0x00;

 static const uint8_t VERSION_1_1 = 0x01;
 static const uint8_t VERSION_1_2 = 0x02;

 static const uint8_t CHARSET_UTF_8 = 0x6A;

 static const uint8_t TAGTOKEN_END = 0x01;
 static const uint8_t TOKEN_INLINE_STRING_FOLLOWS = 0x03;
 static const uint8_t TOKEN_OPAQUEDATA_FOLLOWS = 0xC3;

 static uint8_t setTagTokenIndications(uint8_t token, bool hasAttributes, bool hasContent)
 {
  if (hasAttributes)
   token |= 0xC0;
  if (hasContent)
   token |= 0x40;

  return token;
 }
};

/// <summary>
/// Encapsulates the Service Indication WAP Push instruction.
/// Full documentation can be found at http://www.openmobilealliance.org/tech/affiliates/wap/wap-167-serviceind-20010731-a.pdf?doc=wap-167-serviceind-20010731-a.pdf
/// </summary>
class ServiceIndication
{
public:
 // Allowed values of the action attribute of the indication tag
 enum Action {NotSet = -1, signal_none = 0, signal_low, signal_medium, signal_high, Delete};
 // Well known DTD token
 static const uint8_t DOCUMENT_DTD_ServiceIndication = 0x05;   // ServiceIndication 1.0 Public Identifier

 // Tag Tokens
 static const uint8_t TAGTOKEN_si = 0x5;
 static const uint8_t TAGTOKEN_indication = 0x6;
 static const uint8_t TAGTOKEN_info = 0x7;
 static const uint8_t TAGTOKEN_item = 0x8;

 // Attribute Tokens
 static const uint8_t ATTRIBUTESTARTTOKEN_action[5];
 static const uint8_t ATTRIBUTESTARTTOKEN_created = 0xA;
 static const uint8_t ATTRIBUTESTARTTOKEN_href = 0xB;
 static const uint8_t ATTRIBUTESTARTTOKEN_href_http = 0xC;   // http://
 static const uint8_t ATTRIBUTESTARTTOKEN_href_http_www = 0xD; // http://www.
 static const uint8_t ATTRIBUTESTARTTOKEN_href_https = 0xE;   // https://
 static const uint8_t ATTRIBUTESTARTTOKEN_href_https_www = 0xE; // https://www.
 static const uint8_t ATTRIBUTESTARTTOKEN_si_expires = 0x10;
 static const uint8_t ATTRIBUTESTARTTOKEN_si_id = 0x11;
 static const uint8_t ATTRIBUTESTARTTOKEN_class = 0x12;

 // Attribute Value Tokens
 static const uint8_t ATTRIBUTEVALUETOKEN_com = 0x85;      // .com/
 static const uint8_t ATTRIBUTEVALUETOKEN_edu = 0x86;      // .edu/
 static const uint8_t ATTRIBUTEVALUETOKEN_net = 0x87;      // .net/
 static const uint8_t ATTRIBUTEVALUETOKEN_org = 0x88;      // .org/

private:
 static std::map<std::string, uint8_t> m_hrefStartTokens;
 static std::map<std::string, uint8_t> m_attributeValueTokens;
 static int initializeServiceIndication()
 {
  m_hrefStartTokens["https://www."] = ATTRIBUTESTARTTOKEN_href_https_www;
  m_hrefStartTokens["http://www."] = ATTRIBUTESTARTTOKEN_href_http_www;
  m_hrefStartTokens["https://"] = ATTRIBUTESTARTTOKEN_href_https;
  m_hrefStartTokens["http://"] = ATTRIBUTESTARTTOKEN_href_http;

  m_attributeValueTokens[".com/"] = ATTRIBUTEVALUETOKEN_com;
  m_attributeValueTokens[".edu/"] = ATTRIBUTEVALUETOKEN_edu;
  m_attributeValueTokens[".net/"] = ATTRIBUTEVALUETOKEN_net;
  m_attributeValueTokens[".org/"] = ATTRIBUTEVALUETOKEN_org;
  return 0;
 }

 std::string m_href;
 std::string m_text;
 time_t m_createdAt;
 time_t m_expiresAt;
 Action m_action;

public:
 ServiceIndication(const std::string & href, const std::string & text, Action action) :
   m_href(href), m_text(text), m_action(action)
 {
  static int __unused = initializeServiceIndication();
 }

 ServiceIndication(const std::string & href, const std::string & text, time_t createdAt, time_t expiresAt) :
  m_href(href), m_text(text), m_action(Action::NotSet), m_createdAt(createdAt), m_expiresAt(expiresAt)
 {
 }

 ServiceIndication(const std::string & href, const std::string & text, time_t createdAt, time_t expiresAt, Action action) :
  m_href(href), m_text(text), m_action(action), m_createdAt(createdAt), m_expiresAt(expiresAt)
 {
 }

 /// <summary>
 /// Generates a uint8_t array comprising the encoded Service Indication
 /// </summary>
 /// <returns>The encoded body</returns>
 //01 05 6a 00 45 c6 0c 03 77 61 702e6672616374616c6973742e636e0008 01034120574150205075736820
 //746f20746865204672616374616c6973742073697465000101

 //01 05 6a 00 45 c6 0b 03 68 74 74703a2f2f7761702e6672616374616c6973742e636e0008010341205741
 //50205075736820746f20746865204672616374616c6973742073697465000101

 std::vector<uint8_t> getWBXMLBytes() const
 {
  std::vector<uint8_t> vec;
  vec.push_back(WBXML::VERSION_1_1);
  vec.push_back(DOCUMENT_DTD_ServiceIndication);
  vec.push_back(WBXML::CHARSET_UTF_8);
  vec.push_back(WBXML::CHAR_NULL);

  // start xml doc
  vec.push_back(WBXML::SetTagTokenIndications(TAGTOKEN_si, false, true));
  vec.push_back(WBXML::SetTagTokenIndications(TAGTOKEN_indication, true , true));

  // href attribute
  // this attribute has some well known start tokens that
  // are contained within a static hashtable. Iterate through
  // the table and chose the token.
  size_t i = 0;
  uint8_t hrefTagToken = ATTRIBUTESTARTTOKEN_href;
  for(std::map<std::string, uint8_t>::const_iterator it = m_hrefStartTokens.begin(); it != m_hrefStartTokens.end(); ++it)
  {
   const std::string & startString = (*it).first;
   if(m_href.find(startString) == 0)
   {
    hrefTagToken = (*it).second;
    i = startString.length();
    break;
   }
  }
  vec.push_back(hrefTagToken);

  writeInlineString(vec, m_href.substr(i));
  /*
  * Date elements removed as does not seem to be supported
  * by all handsets, or I"m doing it incorrectly, or it"s a version 1.2
  * thing.

  // created attrbute
  stream.WriteByte(ATTRIBUTESTARTTOKEN_created);
  WriteDate(stream, this.CreatedAt);

  // si-expires attrbute
  stream.WriteByte(ATTRIBUTESTARTTOKEN_si_expires);
  WriteDate(stream, this.ExpiresAt);
  */
  // action attibute
  if (m_action != Action::NotSet)
   vec.push_back(getActionToken(m_action));

  // close indication element attributes
  vec.push_back(WBXML::TAGTOKEN_END);

  // text of indication element
  writeInlineString(vec, m_text);

  // close indication element
  vec.push_back(WBXML::TAGTOKEN_END);
  // close si element
  vec.push_back(WBXML::TAGTOKEN_END);

  return vec;
 }


private:
 /// <summary>
 /// Gets the token for the action attribute
 /// </summary>
 /// <param name="action">Interruption level instruction to the handset</param>
 /// <returns>well known uint8_t value for the action attribute</returns>
 uint8_t getActionToken(Action action) const
 {
  return ATTRIBUTESTARTTOKEN_action[(int)action];
 }

 /// <summary>
 /// Encodes an inline string into the stream using UTF8 encoding
 /// </summary>
 /// <param name="stream">The target stream</param>
 /// <param name="text">The text to write</param>
 void writeInlineString(std::vector<uint8_t> & vec, const std::string & text) const
 {
  // indicate that the follow bytes comprise a string
  vec.push_back(WBXML::TOKEN_INLINE_STRING_FOLLOWS);

  // write character bytes
  std::string utf8Text = stringutil::w2utf8(stringutil::a2w(text));
  copy(utf8Text.begin(), utf8Text.end(), back_inserter(vec));

  // end is indicated by a null uint8_t
  vec.push_back(WBXML::CHAR_NULL);
 }
 /// <summary>
 /// Encodes the DateTime to the stream.
 /// DateTimes are encoded as Opaque Data with each number in the string represented
 /// by its 4-bit binary value
 /// eg: 1999-04-30 06:40:00
 /// is encoded as 199904300640.
 /// Trailing zero values are not included.
 /// </summary>
 /// <param name="stream">Target stream</param>
 /// <param name="date">DateTime to encode</param>
 void writeDate(std::vector<uint8_t> & vec, time_t date) const
 {
  struct tm * gm = gmtime(&date);
  uint8_t buffer[7];

  buffer[0] = (uint8_t)( (gm->tm_year + 1900) / 100);
  buffer[1] = (uint8_t)((gm->tm_year + 1900) % 100);
  buffer[2] = (uint8_t)(gm->tm_mon + 1);
  buffer[3] = (uint8_t)(gm->tm_mday);

  uint8_t dateLength = 4;

  if(gm->tm_hour != 0)
  {
   buffer[4] = (uint8_t)gm->tm_hour;
   dateLength = 5;
  }

  if(gm->tm_min != 0)
  {
   buffer[5] = (uint8_t)gm->tm_min;
   dateLength = 6;
  }

  if(gm->tm_sec != 0)
  {
   buffer[6] = (uint8_t)gm->tm_sec;
   dateLength = 7;
  }

  // write to stream
  vec.push_back(WBXML::TOKEN_OPAQUEDATA_FOLLOWS);
  vec.push_back(dateLength);
  copy(buffer, buffer + dateLength, back_inserter(vec));
 }
};

const uint8_t ServiceIndication::ATTRIBUTESTARTTOKEN_action[5] = {0x5, 0x6, 0x7, 0x8, 0x9};
std::map<std::string, uint8_t> ServiceIndication::m_hrefStartTokens = std::map<std::string, uint8_t>();
std::map<std::string, uint8_t> ServiceIndication::m_attributeValueTokens = std::map<std::string, uint8_t>();

/// <summary>
/// Well known values used when generating WSP (Wireless Session Protocol) headers
/// </summary>
class WSP
{
public:
 static const uint8_t TRANSACTIONID_CONNECTIONLESSWSP = 0x25;

 static const uint8_t PDUTYPE_PUSH = 0x06;

 static const uint8_t HEADER_CONTENTLENGTH = 0x8D;

 static const uint8_t HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8[4];

 static const uint8_t HEADER_APPLICATIONTYPE = 0xaf;
 static const uint8_t HEADER_APPLICATIONTYPE_x_wap_application_id_w2 = 0x82;

 static const uint8_t HEADER_PUSHFLAG[2];

};

const uint8_t WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8[4] = {0x03,0xAE,0x81,0xEA};
const uint8_t WSP::HEADER_PUSHFLAG[2] = {0xB4, 0x84};

/// <summary>
/// Well known values used when generating a WDP (Wireless Datagram Protocol) header
/// </summary>
class WDP
{
public:
 static const uint8_t INFORMATIONELEMENT_IDENTIFIER_APPLICATIONPORT = 0x05;
};

/// <summary>
/// Encapsulates an SMS WAP Push message
/// </summary>
class PushMessage
{
private:
 // Ports for the WDP information element, instructing the handset which
 // application to load on receving the message
 static const uint8_t WDP_DESTINATIONPORT[2];
 static const uint8_t WDP_SOURCEPORT[2];

 ServiceIndication serviceIndication;

public:
 PushMessage(const std::string & href, const std::string & text) :
  serviceIndication(href, text, ServiceIndication::signal_high)
 {
 }

 /// <summary>
 /// Generates the body of the SMS message
 /// </summary>
 /// <returns>uint8_t array</returns>
 std::vector<uint8_t> getSMSBytes() const
 {
  std::vector<uint8_t> vec;

  getWDPHeaderBytes(vec);
#ifdef _DEBUG
  cout << "getWDPHeaderBytes/n";
  for(size_t i = 0; i < vec.size(); ++i)
  {
   cout << hex << setw(2) << setfill("0") << ((int)vec[i]);
  }
  cout << "/n" << endl;
  size_t start = vec.size();
#endif
  getPDUBytes(vec);
#ifdef _DEBUG
  cout << "getPDUBytes/n";
  for(size_t i = start; i < vec.size(); ++i)
  {
   cout << hex << setw(2) << setfill("0") << ((int)vec[i]);
  }
  cout << "/n" << endl;
#endif

  return vec;
 }

 /// <summary>
 /// Generates the PDU (Protocol Data Unit) comprising the encoded Service Indication
 /// and the WSP (Wireless Session Protocol) headers
 /// </summary>
 /// <returns>uint8_t array comprising the PDU</returns>
 void getPDUBytes(std::vector<uint8_t> & vec) const
 {
  std::vector<uint8_t> body = serviceIndication.getWBXMLBytes();

  std::vector<uint8_t> header = getWSPHeaderBytes((uint8_t)body.size());
#ifdef _DEBUG
  cout << "getWSPHeaderBytes/n";
  for(size_t i = 0; i < header.size(); ++i)
  {
   cout << hex << setw(2) << setfill("0") << ((int)header[i]);
  }
  cout << "/n" << endl;

  cout << "serviceIndication.getWBXMLBytes/n";
  for(size_t i = 0; i < body.size(); ++i)
  {
   cout << hex << setw(2) << setfill("0") << ((int)body[i]);
  }
  cout << "/n" << endl;
#endif

  copy(header.begin(), header.end(), back_inserter(vec));
  copy(body.begin(), body.end(), back_inserter(vec));
 }

 /// <summary>
 /// Generates the WSP (Wireless Session Protocol) headers with the well known
 /// uint8_t values specfic to a Service Indication
 /// </summary>
 /// <param name="contentLength">the length of the encoded Service Indication</param>
 /// <returns>uint8_t array comprising the headers</returns>
 //25060a03ae81eaaf828dc1b484
 //25060a03ae81eaaf828dc8b484

 std::vector<uint8_t> getWSPHeaderBytes(uint8_t contentLength) const
 {
  std::vector<uint8_t> vec;

  vec.push_back(WSP::TRANSACTIONID_CONNECTIONLESSWSP);
  vec.push_back(WSP::PDUTYPE_PUSH);

  uint8_t headerLength = sizeof(WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8) / sizeof(WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8[0])
   + sizeof(WSP::HEADER_APPLICATIONTYPE)
   + sizeof(WSP::HEADER_APPLICATIONTYPE_x_wap_application_id_w2)
   + sizeof(WSP::HEADER_CONTENTLENGTH)
   + 1
   + sizeof(WSP::HEADER_PUSHFLAG) / sizeof(WSP::HEADER_PUSHFLAG[0]);
  vec.push_back(headerLength);

  copy(WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8
   , WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8 + sizeof(WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8) / sizeof(WSP::HEADER_CONTENTTYPE_application_vnd_wap_sic_utf_8[0])
   , back_inserter(vec));


  vec.push_back(WSP::HEADER_APPLICATIONTYPE);
  vec.push_back(WSP::HEADER_APPLICATIONTYPE_x_wap_application_id_w2);

  vec.push_back(WSP::HEADER_CONTENTLENGTH);
  vec.push_back((uint8_t)(contentLength + 128));

  copy(WSP::HEADER_PUSHFLAG
   , WSP::HEADER_PUSHFLAG + sizeof(WSP::HEADER_PUSHFLAG) / sizeof(WSP::HEADER_PUSHFLAG[0])
   , back_inserter(vec));

  return vec;
 }

 /// <summary>
 /// Generates the WDP (Wireless Datagram Protocol) or UDH (User Data Header) for the
 /// SMS message. In the case comprising the Application Port information element
 /// indicating to the handset which application to start on receipt of the message
 /// </summary>
 /// <returns>uint8_t array comprising the header</returns>
 void getWDPHeaderBytes(std::vector<uint8_t> & vec) const
 {
  uint8_t headerLength = sizeof(WDP::INFORMATIONELEMENT_IDENTIFIER_APPLICATIONPORT)
   + 1
   + sizeof(WDP_DESTINATIONPORT) / sizeof(WDP_DESTINATIONPORT[0])
   + sizeof(WDP_SOURCEPORT) / sizeof(WDP_SOURCEPORT[0]);
  vec.push_back(headerLength);

  vec.push_back(WDP::INFORMATIONELEMENT_IDENTIFIER_APPLICATIONPORT);
  vec.push_back((uint8_t)(sizeof(WDP_DESTINATIONPORT) / sizeof(WDP_DESTINATIONPORT[0]) + sizeof(WDP_SOURCEPORT) / sizeof(WDP_SOURCEPORT[0])));
  copy(WDP_DESTINATIONPORT
   , WDP_DESTINATIONPORT + sizeof(WDP_DESTINATIONPORT) / sizeof(WDP_DESTINATIONPORT[0])
   , back_inserter(vec));
  copy(WDP_SOURCEPORT
   , WDP_SOURCEPORT + sizeof(WDP_SOURCEPORT) / sizeof(WDP_SOURCEPORT[0])
   , back_inserter(vec));
 }
};

const uint8_t PushMessage::WDP_DESTINATIONPORT[] = {0x0b, 0x84};
const uint8_t PushMessage::WDP_SOURCEPORT[] = {0x23, 0xf0};
 
 

23:30 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | WAP开发
PHP生成动态WAP页面(转载)
 
WAP(无线通讯协议)是在数字移动电话、个人手持设备(PDA等)及计算机之间进行通讯的开放性全球标准。由于静态的WAP页面在很多方面不能满足用户个性化的服务请求,因此通过WAP服务器端语言产生动态的WML页面,具有很广泛的应用价值和很高的商业价值。
  WAP应用结构非常类似于Internet,一个典型的WAP应用请求是这样的:首先,具有WAP用户代理功能的移动终端(WAP手机等)通过内部运行的微浏览器(Micro Browser)对某一网站以无线方式发送WAP服务请求。该请求先由WAP网关截获,对信息内容进行编码压缩,以减少网络数据流量,同时根据需要将WAP协议转换成HTTP协议,然后将处理后的请求转送到相应WAP服务器。在WAP服务器端,根据页面扩展名等性质,被请求的页面直接或由服务器端脚本解释后输出,再经网关传回用户。
  从上述WAP应用流程可以看到,生成动态WAP页面与动态产生Web网页的过程非常类似。但是由于WAP应用使用的WML语言来源于语法严格的XML,因此要求输出的格式必须按WAP网页的规范输出。同时,由于WAP协议的应用范围、移动客户端的软硬件水平等特殊性,对每次输出的页面的大小、图像的格式及容量都有一定限制。下面我们以PHP脚本语言为例,看看如何动态输出WAP页面。
  一、设置WEB服务器

  首先你的 Web服务器要安装好PHP,即能处理PHP脚本程序。其次,为使Web服务器能同时识别和处理PHP、WML、WBMP等文件,Web 服务器的MIME表需添加以下的几种文件类型。

  text/vnd.wap.wml .wml 
  image/vnd.wap.wbmp .wbmp 
  application/vnd.wap.wmlc .wmlc 
  text/vnd.wap.wmls.wmls
  application/vnd.wap.wmlsc .wmlsc 

  二、用PHP输出简单动态WAP页面 

  下面有一个最简单的PHP生成WAP页面的例子。注意由于需要PHP解释器来解释该程序,并输出WAP页面,因此所有类似程序应以.php为扩展名。

  <?php
  header(″Content-type: text/vnd.wap.wml″);
  echo (″ <card>

″);
  echo date( ″l dS of F Y h:i:s A″ ); 
  echo (″

″);
  ?> 

  该例子在WAP手机模拟器中可以浏览,输出当前日期时间,而在普通的浏览器中无法识别,甚至会被认为是错误下载。这是因为在程序开头就声明了该输出文档为WML类型,该类型只有WAP设备能够识别并解释。值得注意的是,我们常见的HTML语言对规范性要求不严,大多数浏览器能“容忍”其中相当多的编写错误,而WML规范相当严格,一点失误都可能导致无法输出所需页面。

  一旦我们知道了用PHP脚本输出WAP页面的标准过程,我们就能够使用PHP强大的功能配合以WML语言的交互处理以及WML Script的简单脚本,开发出适合我们需要的应用系统了。

   三、用PHP动态生成图像 

  WAP应用使用一种特殊黑白的图像格式WBMP。我们可以用一些工具来将已有图像转换成WBMP格式,然后在WML文档中使用。但是在WAP站点上如果能动态地生成所需图像如K线图等,将会有广阔的应用前景。幸运的是,PHP的GD库(版本1.8以上)已经提供了相应函数。

  <?PHP
  Header(″Content-type: image/vnd.wap.wbmp″);
  Sim = ImageCreate(50, 50);
  Swhite = ImageColorAllocate(Sim,255,255,255);
  Sblack = ImageColorAllocate(Sim,0,0,0);
  ImageRectangle(Sim, 5, 5, 20, 20, Sblack);
  ImageWBMP(Sim);   ImageDestroy(Sim);
  ?>

  该文件将在WAP模拟器中显示一个黑色矩形框。注意要使用GD的图像函数库,必须在PHP配置中加载PHP_GD.DLL库文件。

  四、在PHP中处理汉字

  WAP作为一种全球应用,选择了UNICODE 2.0作为其标准字符集编码,以便能同时处理包括英文、中文、日文、法文等多种文字。而我们平常处理汉字使用的是GB2312编码,不同的内码标准势必不能通用,因此如果不在两种编码之间通过码表进行转换,就会出现汉字乱码现象。现在已经有较成熟的GB-2312与UNICODE编码转换的程序和函数,并在ASP、PHP、JSP等系统中使用,我们可以在一些技术站点上找到它们。

  目前的大多数WAP手机(Nokia7110、爱立信R320S等等)都是使用UTF-8编码的,也就是采用UNICODE来编码。这样,如果我们直接在WML使用中文字符(GB2312编码),将会产生乱码,手机用户无法识别,所以我们在输出中文之前,要使用程序或函数对中文进行UNICODE的编码。而在少数支持GB2312编码的手机或WAP终端设备中,我们可以在程序中定义好文档的内码类型后即可直接正确显示汉字,例如:

  <?php 
  header(″Content-type: text/vnd.wap.wml; charset=gb2312″);
  echo (″<card>

″);
  echo (″中文测试″);
  echo (″

″);
 



 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1603928

0
0
 
(请您对文章做出评价)
 
« 上一篇: 史玉柱:社会对我的要求比对陈天桥丁磊高
» 下一篇: Struts例子的设计分析
 
try { GS_googleAddAdSenseService("ca-pub-4210569241504288"); GS_googleEnableAllServices(); } catch (e) { } try { GA_googleAddSlot("ca-pub-4210569241504288", "cnblogs_blogpost_body"); GA_googleAddSlot("ca-pub-4210569241504288", "cnblogs_commentbox_up"); GA_googleAddSlot("ca-pub-4210569241504288", "cnblogs_blogpost_bottom"); } catch (e) { } try { GA_googleFetchAds(); } catch (e) { } var blog_ad_has_shown = false;

posted on 2008-01-29 13:51 exce4 阅读(2584) 评论(2)  编辑 收藏 网摘

<!-- </rdf:RDF> -->#1楼[楼主] 2008-01-29 14:44 exce4      

评论  

关于基于jsp+resin的移动wap的中文参数传递问题?

out.print(outWML.outHref(DefaultURL+”free.jsp?name=假使我漂亮-jade关心妍”, “假使我漂亮(jade关心妍)”));
这句,我将一个中文参数传递到free.jsp页面

free.jsp
//










“>


String para = new String(request.getParameter(“name“).getBytes(“iso8859_1“));
out.print(“

“+para+“

“);//输出获得的参数,都为乱码
out.print(outWML.outHref(DefaultURL+“mring.jsp“, “劲歌金曲爬行榜首页“));
out.print(monternet);
-->

//
出来的结果怎么都是乱码



是关于java的,java的默认参数传递方式是utf8码,今天终于解决了
不是楼上说的问题
在发送url请求的页面和接收url请求的页面做如下设置
//utf8
//参数处理方式utf8
//页面字符处理方式 iso-8859-1

2004-07-06 11:33 | khan

o?按照ISO-8859-1传递就行了?我怎么记得也会出问题呢?过网关的时候也会出点莫名其妙的事情。

我是都urlencode了,然后替换一下%,然后程序再解码。


2004-07-08 09:05 | virushuo

呵呵,普通的传递用urlencode.class,解码用urldecode.class
表单的传递用iso-8859-1就可以了,呵呵

以上是指移动的wap业务,所有汉字编码必须是utf8的情况下
   回复   引用   查看    

#2楼 [ 楼主] 2008-01-29 14:51 exce4       

昨天和一个老程序员吃饭聊起codelphi,说很久以前,经常能从这里搜索到一些好的技术文章。
最近的工作也蛮辛苦。开始接触以前从来没有接触过的GNU/linux下的基于gcc的开发。两样东西都是现学的。工作了3个星期,只写了一个 电信smgp3协议的tlv参数解析包。所谓的tlv参数就是(tag ,length,value),tag表示一个指令标志,length,表示这个指令所携带数据的长度,value表示指令所携带的数据,用这种方式传递 参数可以很大程度的在不影响效率的情况下减少空参数所占的空间,节省网络带宽。贴部分代码给大家指正

#ifndef _PTLV_HPP
#define _PTLV_HPP

#include
#include
#include
#include
/*sowpdu*/
typedef unsigned short WORD;
typedef unsigned char BYTE;

typedef int BOOL;
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif


/*
Name: PTlv
Copyright:
Author:
Date: 14-12-05 15:05
Description:
*/
class PTlv{
private:
WORD tag;//tlv 标识
WORD len; //value 长度
BYTE *value; //参数数据体
int byteOffset;

public:
PTlv(WORD new_tag, WORD new_len, BYTE *new_value);
PTlv(WORD new_tag, BYTE new_value);

PTlv();

void Clone(PTlv &src_tlv);

~PTlv();

enum VALUE_TYPE{ //
INTEGER_1 = 0×0001, //byte
INTEGER_2 = 0×0002, //word
OCTET_STRING = 0×0003 //string
};

enum TLV_Tag{
TLV_TP_PID = 0×0001, //GSM协议类型。详细解释请参考GSM03.40中的9.2.3.9
TLV_TP_UDHI = 0×0002, //GSM协议类型。详细解释请参考GSM03.40中的9.2.3.23,仅使用1位,右对齐。
TLV_LINK_ID = 0×0003, //交易标识,用于唯一标识一次交易
TLV_CHARGE_USER_TYPE = 0×0004, //计费用户类型。
// 0=对短消息接收方计费;
// 1=对短消息发送方计费;
// 2=对SP计费;
// 3=表示本字段无效,对谁计费参见ChargeTermID或ChargeTermPseudo 字段。
TLV_CHARGE_TERM_TYPE = 0×0005, //计费用户的号码类型。
// 0=真实号码;
// 1=伪码;其它保留。
TLV_CHARGE_TERM_PSEUDO = 0×0006, //计费用户的伪码
TLV_DEST_TERM_TYPE = 0×0007, //短消息接收方的号码类型。
// 0=真实号码;
// 1=伪码;其它保留
TLV_DEST_TERM_PSEUDO = 0×0008, //短消息接收方的伪码,当有多个接收方伪码时,要求每个接收方伪码的长度一样。
TLV_PK_TOTAL = 0×0009, //相同Msg_Id的消息总条数。
TLV_PK_NUMBER = 0×000A, //相同Msg_Id的消息序号,从1开始。
TLV_SUBMIT_MSG_TYPE = 0×000B, //SP发送的消息类型。
// 0=普通短消息;
// 1=WEB方式定制结果消息;
// 2=WEB方式取消定制结果消息;
// 3=终端方式定制结果消息;
// 4=终端方式取消定制结果消息;
// 5=包月扣费通知消息;
// 6=WEB方式定制二次确认消息;
// 7=WEB方式取消定制二次确认消息;
// 8=终端方式定制二次确认消息;
// 9=终端方式取消定制二次确认消息;
// 10=WEB方式点播二次确认消息;
// 11=终端方式点播二次确认消息(暂保留);
// 12=群发请求;
// 13:同步订购(包括点播和定制)关系;
// 14:群发结果通知消息。
// 无该字段时,默认为”普通短消息”
// 15:同步订购(包括点播和定制)关系回复;其它保留;
TLV_SP_DEAL_RESLT = 0×000C, //SP对消息的处理结果
// 0=成功;
// 1=失败;其它保留。
// 该字段在SubmitMsgType为0、5、6、7、8、9、10、11、14时无效。

TLV_SRC_TERM_TYPE = 0×000D, //短消息发送方的号码类型。
// 0=真实号码;
// 1=伪码;其它保留。

TLV_SRC_TERM_PSEUDO = 0×000E, //短消息发送方的伪码
TLV_NODES_COUNT = 0×000F, //经过的网关数量。该字段的初始值为1。

TLV_MSG_SRC = 0×0010, //信息内容的来源。
// 在固定网短消息业务中,MsgSrc填写SP的服务代码。
// 在移动网短消息业务中,MsgSrc填写SP的企业代码。
TLV_SRC_TYPE = 0×0011, //传递给SP 的源号码的类型。
// 0=真实号码;
// 1=伪码;其它保留。
TLV_M_SERVICE_ID = 0×0012 //业务代码。用于移动网业务
};

WORD getTag() const { return tag;}
void setTag(WORD new_tag){ tag = new_tag; }

WORD getTLVLen() const { return 2+2+len; }
WORD getValueLen() const { return len;}
int getOffset() const{ return byteOffset;}

BOOL getValue(BYTE *pstr, int value_len);

static BOOL IsValidTag(WORD the_tag);
static std::string AliasByTag(WORD the_tag);

BOOL Decode(BYTE *pstr, int tlv_len);
BOOL Encode(WORD the_tag, WORD the_len, BYTE *the_value);
BOOL Encode(WORD new_tag, BYTE new_value);

void PrintOn(std::ostream & strm) const;
void ToString(std::ostream & strm) const;

WORD getValueType(WORD the_tag) const;
protected:

};

#endif //_TLV_HPP

//tlv.cpp///
#include “TLV.hpp”

// PTLV

/**构造器
*@param */
PTlv::PTlv(WORD new_tag, WORD new_len, BYTE *new_value){
Encode(new_tag, new_len, new_value);
}

PTlv::PTlv(WORD new_tag, BYTE new_value){
Encode(new_tag, new_value);
}

PTlv::PTlv(){
tag=0×0000;
len=0×0000;
value=0;
byteOffset=0;
}

/**/
void PTlv::Clone(PTlv &src_tlv){
tag = src_tlv.getTag();
len = src_tlv.getValueLen();
byteOffset= src_tlv.getOffset();
if(value != NULL) {
delete []value;
value = NULL;
}
value = new BYTE[len];
memset(value, 0, len);
src_tlv.getValue(value,len);
}

/*析构器*/
PTlv::~PTlv(){
if (value != NULL)
delete []value;
value = NULL;
byteOffset = 0;
len = 0;
}

BOOL PTlv::IsValidTag(WORD the_tag){
switch(the_tag){
case TLV_TP_PID:// = 0×00000001,
case TLV_TP_UDHI:// = 0×00000002,
case TLV_LINK_ID:// = 0×00000003,
case TLV_CHARGE_USER_TYPE:// = 0×00000004,
case TLV_CHARGE_TERM_TYPE:// = 0×00000005,
case TLV_CHARGE_TERM_PSEUDO:// = 0×00000006,
case TLV_DEST_TERM_TYPE:// = 0×00000007,
case TLV_DEST_TERM_PSEUDO:// = 0×00000008,
case TLV_PK_TOTAL:// = 0×00000009,
case TLV_PK_NUMBER:// = 0×0000000A,
case TLV_SUBMIT_MSG_TYPE:// = 0×0000000B,
case TLV_SP_DEAL_RESLT:// = 0×0000000C,
case TLV_SRC_TERM_TYPE:// = 0×0000000D,
case TLV_SRC_TERM_PSEUDO:// = 0×0000000E,
case TLV_NODES_COUNT:// = 0×0000000F,
case TLV_MSG_SRC:// = 0×00000010,
case TLV_SRC_TYPE:// = 0×00000011,
case TLV_M_SERVICE_ID:// = 0×00000012,
return TRUE;
default :
return FALSE;
}
}

/**
* 取得tag的别名
* @param the_tag tag标识
* @return string 别名
*/
std::string PTlv::AliasByTag(WORD the_tag){
switch(the_tag){
case TLV_TP_PID:
return “TLV_TP_PID”;
case TLV_TP_UDHI:
return “TLV_TP_UDHI”;
case TLV_LINK_ID:
return “TLV_LINK_ID”;
case TLV_CHARGE_USER_TYPE:
return “TLV_CHARGE_USER_TYPE”;
case TLV_CHARGE_TERM_TYPE:
return “TLV_CHARGE_TERM_TYPE”;
case TLV_CHARGE_TERM_PSEUDO:
return “TLV_CHARGE_TERM_PSEUDO”;
case TLV_DEST_TERM_TYPE:
return “TLV_DEST_TERM_TYPE”;
case TLV_DEST_TERM_PSEUDO:
return “TLV_DEST_TERM_PSEUDO”;
case TLV_PK_TOTAL:
return “TLV_PK_TOTAL”;
case TLV_PK_NUMBER:
return “TLV_PK_NUMBER”;
case TLV_SUBMIT_MSG_TYPE:
return “TLV_SUBMIT_MSG_TYPE”;
case TLV_SP_DEAL_RESLT:
return “TLV_SP_DEAL_RESLT”;
case TLV_SRC_TERM_TYPE:
return “TLV_SRC_TERM_TYPE”;
case TLV_SRC_TERM_PSEUDO:
return “TLV_SRC_TERM_PSEUDO”;
case TLV_NODES_COUNT:
return “TLV_NODES_COUNT”;
case TLV_MSG_SRC:
return “TLV_MSG_SRC”;
case TLV_SRC_TYPE:
return “TLV_SRC_TYPE”;
case TLV_M_SERVICE_ID:
return “TLV_M_SERVICE_ID”;
default :
return “TLV_UNKNOWN_TAG_ID”;
}
}

void PTlv::PrintOn(std::ostream &strm) const{
strm << “SMGP3_TLV:{/n”;
strm << std::setw(15) << “tag:” << AliasByTag(tag) << ” 0x” << std::hex << std::setw(sizeof(tag))<< std::setfill(’0′) << tag << ‘/n’;
strm << std::setfill(’ ‘) << std::setw(15) << “len:” << len << std::endl;
strm << std::setfill(’ ‘) << std::setw(17) << “value:” << value <

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

sdjncjc

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值