BES服务器推送机制分析(一)
来源:互联网 发布:java八大基本数据类型 编辑:程序博客网 时间:2024/05/16 12:37
作者: 邓明轩
前言 数据推送是 BlackBerry 应用平台的一大优势,在 BlackBerry 应用平台上部署的应用可以 和 BlackBerry 推送邮件一样通过推送实时地将数据从服务器端推送到 BlackBerry 手持设备 端。所以,对于很多应用开发商而言,BlackBerry 应用平台提供的推送功能是 BlackBerry 应 用集成必然会使用到的强大功能。 然而,因为种种原因,有一些开发商发现使用 BlackBerry Enterprise Server (BES) 应用平 台的推送功能并不能保证数据到达 BlackBerry 设备,导致应用层面的种种问题。实际上, BlackBerry 应用平台提供了从多机制保证数据推送的成功,只要我们充分了解 BES 中 MD(S 以 下简称 MDS)的推送机制,我们就可以利用平台提供的机制保证数据到达 BlackBerry 手持设 备。 本章节的主要目的是详细分析 MDS 的推送机制,让读者更好地了解 BlackBerry 应用平 台,从而可以开发更加强壮,更加稳定的 BlackBerry 应用。 值得读者注意的是,本章节的内容是结合少量的官方文档,配合于大量的测试总结而来 的,旨在为读者提供更多的线索以理解 MDS 推送机制。如果本章节的内容与 RIM 提供的任 何官方文档有冲突的话请以官方文档为准。同时,不同版本的 BlackBerry 手持设备在不同场 景下也可能有不同的行为,所以读者在生产环境实施 BlackBerry 应用时也要结合用户所使用 的 BlackBerry 设备进行测试,才能最好地保证应用的稳定性。 MDS 推送介绍 MDS 推送架构 在详细讲解 MDS 推送机制之前我们先从整体上了解 MDS 推送的架构,下面是 MDS 推 送架构的示意图:
从示意图中可以看到在 BlackBerry 应用平台上的数据推送从整体上可以分为六步,按时 间顺序分别为:
1. 第一步:应用服务器向 MDS/BES 服务器发送推送请求,所发送的请求为 HTTP 格式的请 求,有关请求的详细格式在下一小节中有详细讲解。
2. 第二步:MDS/BES 服务器查询相关配置数据库,确定应用服务器所发送的请求是否为合 法的请求。此外,MDS/BES 服务器还会根据资源情况确定是否接收该请求。对于是否接 收请求的判断在下一节内容中也有详细讨论。
3. 第三步:MDS/BES 服务器向应用服务器返回消息,通知应用服务器是否接受该请求。返 回消息以 HTTP 答复的方式返回给应用服务器。
4. 第四步:MDS/BES 服务器将数据推送到手持设备端
5. 第五步:手持设备端对数据进行处理后向 MDS/BES 服务器返回确认消息
6. 第六步:MDS/BES 根据手持设备端返回的消息决定向应用服务器返回什么异步消息,这 一步并不是必然发生的,根据推送请求的不同有可能不发生。 从这里我们可以看到,从应用服务器到手持设备端的推送通道是由多个不同的通道连接
而成的,这其中有很多个连接点,某一个连接点出现异常都可以导致推送的失败。所以应用 开发商需要详细了解这些连接点可能出现的问题,在应用开发过程中进行规避。
在详细描述各个可能出现问题的关键点之前,需要先了解推送的基本实现。对于应用开 发人员而言,完成数据推送需要完成的主要程序编写工作有:应用服务器端发出推送请求的 程序,手持设备端侦听接收推送数据的程序,和服务器端侦听接收确认消息的程序,如下图 橙色框图所显示的:
以下章节将对这三个部分作详细讲解。
数据推送命令格式
在 BlackBerry 应用平台上的数据推送是由应用服务器发起的,应用服务器根据应用逻辑 进行判断,发现有数据需要推送到用户的手持设备端时连接 MDS/BES 服务器进行数据推送。 应用服务器与 MDS/BES 服务器的连接方式为 HTTP 连接,通过 HTTP 连接发送一个 POST 请 求,将需要推送的数据作为 POST 的内容。
该 POST 请求的 URL 格式为:
http://BES_HOST:BES_PORT/push?DESTINATION=DESINTATION_ADDRESS&PORT=HANDHEL
D_APPLICATION_PORT&REQUESTURI=/
下面是请求中各参数的说明:
参数
描述
BES_HOST
BlackBerry Enterprise Server 的服务器名或者是 IP
BES_PORT
BlackBerry Enterprise Server 的推送端口,一般是 8080
DESTINATION_ADDRESS
用户的邮件地址或者是设备码 (PIN)
HANDHELD_APPLICATION_PORT
BlackBerry 手持设备需要帧听的端口
也就是说,应用服务器通过 BES_HOST 和 BES_PORT 指定需要连接的 MDS/BES 服务器和
需要连接的端口。BES 服务器安装后缺省使用 8080 作为推送端口,所以推送应用开发人员 一般情况下只需要了解 MDS/BES 服务器的服务器名或者是 IP 地址就可以了。如果 BES 管理 人员在安装服务器后修改过推送端口,则需要告知开发人员使用新指定的端口。
推送 URL 中 push?后面的部分用于指定数据的接收者和手持设备需要帧听的端口。 DESTINATION_ADDRESS 用于指定数据的接收者,可以使用该用户的邮件地址或者是该用户手 持设备的 PIN 码。HANDHELD_APPLICATION_PORT 用于指定手持设备需要帧听的端口,这里 使用的端口只是一个约定,要求在手持设备上运行的程序从这一端口中读取数据。
除了 URL 参数以后,在推送过程还可以使用一些规定好的 HTTP 头指定该推送的属性,
HTTP 头参数表如下:
HTTP 头
描述
X-RIM-Push-ID
用于指定该消息的 ID,该 ID 需要是一个唯一值,可以用于 取消推送或者是查看推送的状态。
X-RIM-Push-NotifyURL
用于指定确认消息的接收 URL。
X-RIM-Push-Reliability-Mode
用于指定推送的可靠性。
X-RIM-Push-Deliver-Before
用于指定推送数据的最迟推送时间。
X-RIM-Push-Priority
用于指定推送数据的优先级。
X-RIM-Push-ID 属性一般建议使用 URL 和编号结合的方式,如 123@blackberry.com。为
了更好地控制数据的推送,一般不建议使用随机数作为推送 ID。使用统一的 ID 生成方式更 有利于推送数据的取消和状态确定。注意,如果没有指定该参数的话,MDS/BES 服务器会自 动生成一个唯一的 ID,这样就无法在应用服务器上使用这个 ID 对特定的推送数据进行处理 了。此外,推送 ID 不能以@ppg.rim.com 结束。
X-RIM-Push-NotifyURL 属 性 通 过 URL 的 形 式 指 定 了 确 认 消 息 的 接 收 地 址 , 如 http://testingserver:7778。指定了这一属性后,MDS/BES 服务器会将推送的确认消息以 HTTP 请求的形式发送到指定的服务器。也就是说应用开发者需要开发一个 HTTP 服务器端程序监 听指定服务器的指定端口(上例中则是服务器 testingserver 的 7778 端口),通过这个服务器 端程序获取确认消息。在确认消息中会包含 HTTP 头 X-RIM-Push-ID 和 X-RIM-Push-Status,通
过 X-RIM-Push-ID 告知监听者是哪条推送数据的确认消息,通过 X-RIM-Push-Status 指明该数 据推送的结果,200 代表推送成功,400 代表推送失败。现实环境中对于数据推送是否成功
不能简单地以结果 200 或者是 400 进行判断,需要结合很多因素进行判断,本文的后续章节 会详细描述。
属性 X-RIM-Push-Reliability-Mode 用于指定推送的可靠性,值可以是 TRANSPORT 或者是 APPLICATION,另外有一个值 APPLICATIONPREFERRED 涉及更复杂的场景,这里不做描述。如 果指定可靠性为 TRANSPORT 则以数据到达手持设备准,本文称之为非应用依赖。如果指定 可靠性为 APPLICATION 则以数据被手持设备端应用接收为准,本文称之为应用依赖。对于不 同可靠性设置,本文的后续章节会有详细描述。总体而言,非应用依赖的推送更适合于广播 性质的不要求应用一定收到数据的场景,而应用依赖更适合于点对点的要求应用一定要收到 数据的场景
属性 X-RIM-Push-Deliver-Before 用于指定数据推送的最后时间,如果在指定时间前该数 据仍无法被成功推送到手持设备端,则该数据会被视作过期而被 MDS/BES 服务器丢弃。
属性 X-RIM-Push-Priority 用于指定推送数据的优先级,指定推送数据的优先级并不能加 快数据的推送,指定优先级的结果是推送数据到达手持设备端的行为不同。可以选择的值有: none (缺省),low, medium 和 high。如果指定优先级为 low,medium 或者是 high,则用 户只是接收到数据,如果指定优先级为 high,则用户在接收到数据后还会看到提示对话框。
数据推送关键代码
理论上讲,应用服务器的数据推送代码可以用任何可以提供 HTTP 支持的语言编写,需 要实现的不过是通过 HTTP 协议往指定的服务器和端口发送 POST 消息。
下面以 java 代码为例讲解数据推送的关键代码部分。本例的代码片段为一个 Java Application 的部分代码,该 Java Application 在服务器上以 J2SE 应用的形式运行,启动一个 线程发送 HTTP 的 POST 请求。以下代码为该线程的 run 函数:
public void run() {
Random random = new Random();
String pushId = "pushID:" + random.nextInt(); String data = "What need to be pushed";
try {
URL _pushURL = new URL("http", "testingBES.abc.com", 8080,
"/push?DESTINATION=user1@abc.com&PORT=55100&REQUESTURI=/");
HttpURLConnection conn = (HttpURLConnection) _pushURL.openConnection();
conn.setDoInput(true); conn.setDoOutput(true); conn.setRequestMethod("POST");
conn.setRequestProperty("X-RIM-PUSH-ID", pushId);
conn.setRequestProperty("X-RIM-Push-NotifyURL",
"http://testingserver:7778");
conn.setRequestProperty("X-RIM-Push-Reliability-Mode",
"APPLICATION");
OutputStream out = conn.getOutputStream();
out.write(data.getBytes());
out.close();
InputStream ins = conn.getInputStream();
int contentLength = conn.getContentLength();
int responseCode = conn.getResponseCode(); System.out.println("Response Code is:" + responseCode);
if (contentLength > 0) {
byte[] someArray = new byte[contentLength]; DataInputStream dins = new DataInputStream(ins); dins.readFully(someArray);
System.out.println(new String(someArray));
} else {
System.out.println("Content legth of response is 0");
}
conn.disconnect();
} catch (IOException e) { System.err.println("Exception:" + e);
}
}
在以上代码中,先通过 URL 类建立一个 HTTP 连接定义,URL 类的构造函数中第一个参 数为协议类型,这里使用“http”,第二个参数为服务器名,第三个参数为请求的端口号。 第二个参数和第三个参数对应了数据推送请求中的 BES_HOST 和 BES_PORT 属性。URL 类的 构造函数中第四个参数为相对 URL,推送属性 DESTINATION 和 PORT 都放在相对 URL 中,代 码如下:
URL _pushURL = new URL("http", "testingBES.abc.com", 8080,
"/push?DESTINATION=user1@abc.com&PORT=55100&REQUESTURI=/");
建立了 URL 类的实例后,可以通过该实例的 openConnection 函数打开连接,返回的是 一个 HttpURLConnection 实例。
HttpURLConnection conn = (HttpURLConnection) _pushURL.openConnection();
打开 http 连接后可以通过连接实例的 setRequestProperty 函数设置 http 头,推送请求中
的 http 头属性在这里设置,如:
conn.setRequestProperty("X-RIM-PUSH-ID", pushId);
其中一个关键的 http 头属性是 X-RIM-Push-Reliability-Mode,设置该属性为“TRANSPORT” 表示该推送是非应用依赖的推送,设置该属性为“APPLICATION”表示该推送是应用依赖的
推 送 。 两 者 的 具 体 区 别 在 后 面 的 章 节 会 有 详 细 的 讨 论 。 以 下 代 码 设 置 该 属 性 为
“APPLICATION”:
conn.setRequestProperty("X-RIM-Push-Reliability-Mode",
"APPLICATION");
特别要注意,如果设置推送类型为应用依赖,对于服务器的设置和手持设备有特定的要 求。一方面是服务器端必须是 4.0 以上,同时必须设置允许对所使用的端口进行应用依赖的 推送,一方面是要手持设备的 ROM 版本也是 4.0 以上,同时服务器必须知道手持设备能否 支持应用依赖的推送。对于服务器的端口设置,设置界面如下,可以由 BES 管理员进行设置:
如果是使用开发环境的 MDS-CS 模拟器模拟应用依赖推送的话,需要修改开发环境的 MDS/config 目录中的 rimpublic.property 文件,去除 push.application.reliable.ports 一行的注释, 同时加上需要推送的端口,如“push.application.reliable.ports=100,55100”。
对于服务器端是否知道手持设备端的版本,需要通过手持设备端的操作完成。只要手持 设备端的浏览器通过 BES 访问过网络,服务器就会记录该手持设备是否支持应用依赖的推 送。如果只是测试的话,在测试前通过手持设备端的浏览器打开任何一个网站就可以了。如 果是生产环境,则需要考虑客户端应用在第一次启动的时候主动访问一次网络,以保证客户 端都可以正常接收应用依赖的推送。
如果以上设置没有完成的话,对 MDS/BES 服务器发送应用依赖的推送时会被拒绝,服 务器端会出现“The specified delivery method is not possible”的错误。应用服务器的推送程 序则会捕获以下异常:
“java.io.IOException: Server returned HTTP response code: 400 for
URL: http://app...”
设置属性后就需要获取 http 连接的 OutputStream,将需要推送的数据写入 OutputStream
中,写完后关闭连接。
需要注意的是 MDS/BES 服务器是否接收该推送请求是通过 http 返回码实现的,所以推 送应用中需要获取到 http 连接的返回码,通过返回码判断推送是否成功,代码如下:
int responseCode = conn.getResponseCode();
客户端接收关键代码
客户端数据接收程序运行在 BlackBerry 手持设备上,必须通过 java 语言实现,需要调用
BlackBerry 设备端的 API。
客户端接收程序的主体就是启动一个线程,开启 http://:<port>连接(其中 port 是客户 端需要侦听的端口号),然后从该连接中获取 InputStream,从 InputStream 中获取服务器所
推送的数据。
下面是侦听线程的 run 方法,为保持程序的完整,代码中使用到的类属性并没有修改,
从 该 函 数 中看 到 的本 函数 没 有 定 义的 变 量都 是该 类 的 类 属性 。 有关 完整 代 码 , 请参 考
BlackBerry 开发环境所提供的标准样例。
public void run(){ StreamConnection stream = null;
InputStream input = null; MDSPushInputStream pushInputStream=null;
while (!_stop)
{
try
{
synchronized(this)
{
_notify =
(StreamConnectionNotifier)Connector.open("http://:55100");
}
while (!_stop)
{
stream = _notify.acceptAndOpen();
try
{
input = stream.openInputStream();
pushInputStream= new
MDSPushInputStream((HttpServerConnection)stream, input); DataBuffer db = new DataBuffer();
byte[] data = new byte[256];
int chunk = 0;
while ( -1 != (chunk = input.read(data)) )
{
db.write(data, 0, chunk);
}
pushInputStream.accept(); input.close(); stream.close();
data = db.getArray();
}
catch (IOException e1)
{
System.err.println(e1.toString());
if ( input != null )
{
try
{
input.close();
}
catch (IOException e2)
{
}
}
if ( stream != null )
{
try
{
stream.close();
}
catch (IOException e2)
{
}
}
}
}
_notify.close();
_notify = null;
}
catch (IOException ioe)
{
if ( _notify != null )
{
try
{
_notify.close();
_notify = null;
}
catch ( IOException e )
{
}
}
}
}
}
以上代码中,建立端口侦听的代码如下:
_notify = (StreamConnectionNotifier)Connector.open("http://:55100"); 其中 URL“http://:55100”是用于侦听推送的特定格式,55100 为需要侦听的端口。两 个线程不能同时打开同一个端口的侦听,所以在这里使用了 synchronized 对线程并发进行了
控制。
建立侦听后可以通过函数 acceptAndOpen 开始侦听,当线程调用该方法开始侦听的时候 线程会阻塞在这里,直到有推送消息到达。代码如下:
stream = _notify.acceptAndOpen();
侦听之后的代码为获取连接的 InputStream,从 InputStream 中获取服务器端推送的数据。 读取完数据后关闭 InputStream 和 StreamConnection,然后重新调用 acceptAdnOpen 继续侦 听端口,准备接收新的推送数据。
开 发 人 员 如 果 希 望 使 用 应 用 依 赖 的 推 送 , 在 客 户 端 必 须 将 InputStream 转 换 成 MDSPushInputStream,同时在接收完数据后调用 MDSPushInputStream 的 accept 函数造知服 务器该数据已收到。这也是应用依赖和非应用依赖在代码实现层面的最主要区别。代码如下:
pushInputStream= new
MDSPushInputStream((HttpServerConnection)stream, input);
……
pushInputStream.accept();
从良好的编程习惯来讲 ,在连接使用后需要主 动关闭所使用的连接和 资源,如 InputStream、StreamConnection、StreamConnectionNotifier 等。所以在样例代码中也可以看 到该样例在异常处理阶段都对这些资源进行了关闭操作。在关闭这些连接和资源的时候对 StreamConnectionNotifier 需要特别注意,有可能在 StreamConnectionNotifier 中会保留有未 读取的推送数据,需要调用 acceptAdnOpen 函数将保留的数据读取出来,直到 acceptAdnOpen 处于阻塞状态。
另外,如果使用了应用依赖的推送,客户端程序调用 MDSPushInputStream 的 accept 函 数后确认消息不会马上发出,而是在对应用 InputStream 和 StreamConnection 关闭后才发出。 所以对于应用依赖的推送,保证 InputStream 和 StreamConnection 的关闭非常之重要。
异步确认消息接收代码
异步消息的接收代码运行在服务器端,在推送请求的 X-RIM-Push-NotifyURL 属性所指定 的服务器上侦听指定端口。和应用服务器端的数据推送代码一样,异步确认消息的接收程序 可以通过任何支持 HTTP 的语言编写。主要的工作是侦听指定端口,从指定端口获取 MDS/BES 发送回来的确认消息,根据 HTTP 协议获取相关属性并返回相关信息。
下面以 java 代码为例对关键代码进行说明。这段代码一个 java Application 的一部分,是 侦听线程的 run 函数。
public void run() {
try {
System.out.println("Waiting for notification on port" + 7778
+ "...");
while (true) {
ServerSocket serverSocket = new ServerSocket(7778);
serverSocket.setSoTimeout(120000);
try {
Socket clientSocket = serverSocket.accept();
InputStream input = clientSocket.getInputStream(); StringBuffer str = new StringBuffer();
int byteRead = input.read();
while ((byteRead != -1) && (input.available() > 0)) {
str.append((char) byteRead);
byteRead = input.read();
} System.out.println(str.toString());
OutputStream output = clientSocket.getOutputStream();
String response = "HTTP/1.0 200 OK";
output.write(response.getBytes());
output.flush();
output.close();
input.close();
clientSocket.close();
} catch (SocketTimeoutException ste) { System.out.println("Notification connection timeout.
Restarting...");
}
serverSocket.close();
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
异步确认消息的接收代码比较简单,基本上就是打开一个 Socket 侦听指定的端口,本 例中是 7778,然后不断地从该端口读取数据,打开 Socket 侦听的代码如下:
ServerSocket serverSocket = new ServerSocket(7778);
在异步消息接收过程中需要注意的是接收完 MDS/BES 服务器的确认消息后,应该给 MDS/BES 服务器返回一个 200 的成功消息,否则 MDS/BES 服务器会认为发送确认消息失败, 可能重次几次后再放弃,这样异步消息接收程序有可能重复收到同一条确认消息。返回成功
消息的代码如下:
OutputStream output = clientSocket.getOutputStream();
String response = "HTTP/1.0 200 OK"; output.write(response.getBytes()); output.flush();
output.close();
BlackBerry SDK下载
相关链接:
BES服务器推送机制分析(一)
BES服务器推送机制分析(二)
BES服务器推送机制分析(三)
- BES服务器推送机制分析(一)
- BES服务器推送机制分析(二)
- BES服务器推送机制分析(三)
- BES 推送应用实例演示与分析(一)
- BES 推送应用实例演示与分析(二)
- BES 推送应用实例演示与分析(三)
- 黑莓推送机制-(1)BES/MDS推送架构
- BES 10 企业推送详解
- 苹果推送机制APNs(一)
- Android--推送机制实现原理(一)
- BES服务器的使用经验
- BlackBerry BES推送例子代码(php)
- Android推送通知机制分析
- Android推送通知机制分析
- 说说BES(续)
- BES
- [IOS开发] 苹果推送机制APNs(一)
- IOS——消息推送机制(一)
- 在 Ubuntu 上源码安装 Xen(转,但有修改)
- 通过参数showcommands 将编译的命令显示
- 网络分析与网络数据集—功能调用GP服务
- study_Javascript_01javascript语法
- [转]四.设计的优先级 2发布用户期望的功能
- BES服务器推送机制分析(一)
- [转]四.设计的优先级 3.让您的程序与众不同
- 使用C#调用外部Ping命令获取网络连接情况
- 数据仓库拉链算法在ORACLE中的实现
- 呀呀个呸的
- 回归
- 我让盗个QQ号,急!!!!
- suse的使用
- matlab中的曲线拟合与插值