通过 JXTA 进行无线通信,第 2 部分: 实现 JXTA-for-JMS

在本系列的第一篇文章中,介绍了支持 Java 2 Platform, Micro Edition(J2ME)的设备为什么不能直接承载进行企业通信的客户端应用程序。第一篇文章还讨论了如何用 JXTA 技术在企业通信解决方案中集成瘦移动客户机(thin mobile client),并展示了如何在 J2ME 设备中使用 JXTA 协议。

本系列的第二篇文章将展示如何用 JXTA(JXTA-for-JMS,或简称 JXTA4JMS)实现 J2ME 客户机与 JMS 应用程序之间的连接。该 JXTA4JMS 实现包含两个组件:一个组件运行在台式计算机上(桌面端 JXTA4JMS),另一个组件在基于 J2ME 的移动设备上运行(移动端 JXTA4JMS)。

本文首先将用几个实例展示 J2ME 设备或者 JMS 客户机如何使用 JXTA4JMS。然后介绍 JXTA4JMS 架构,并说明 JXTA4JMS 实现中的类的部署情况。最后,本文将展示实际的实现,并提供一个可以工作的应用程序,该程序在 Java Message Service(JMS)应用程序中集成了瘦 J2ME 客户机。

考虑一家用 JMS 完成其企业范围通信需求的公司。这家公司安装了 JMS 提供者(一种消息中间件),使用 JMS 网络的雇员就是通信的客户机(消息的生产者或消费者)。公司希望通信客户机彼此保持连接。当所有用户可以访问他们的 PC 时,JMS 网络就可以满足这项要求。

当用户不能访问他们的 PC 时(例如,当他们不在办公室时),就需要使用 JXTA4JMS。假设 JMS 用户 Alice 离开了办公室。她启用了桌面上和手机上的 JXTA4JMS。桌面端的 JXTA4JMS 开始监听收到的 JMS 消息。每当收到 Alice 的 JMS 消息,它就将这个消息转发给 Alice 的手机。与此类似,当 Alice 要向同事发送消息时,她使用移动端 JXTA4JMS 发送这条 JMS 消息。消息接收者像接收普通 JMS 消息那样接收这条消息。JXTA4JMS 帮助 Alice 保持连接,并用支持 J2ME 的手机继续通过 JMS 网络收发消息。

注意 JXTA4JMS 的一项重要功能:它不干扰其他 JMS 用户的工作。如果 Alice 不想再使用 JXTA4JMS,只要禁用它就行了。JXTA4JMS 对于其他 JMS 用户来说是透明的,他们不受 JXTA4JMS 的影响,因此不会知道 Alice 是启用还是禁用了 JXTA4JMS。

如您所见,JXTA4JMS 可以处理 JMS 客户机和移动客户之间的双向通信。让我们分别考虑这两种用例,以便清楚地阐明 JXTA4JMS 的功能。


从 JMS 客户机到 J2ME 客户机的通信

假定 Bob 要向 Alice 发送 JMS 消息。Bob 用他的桌面计算机发送这个消息。Bob 不知道 Alice 这个时候不在她的办公室,也不知道她启用了桌面计算机和 J2ME 手机上的 JXTA4JMS。

在 Alice 启用桌面计算机和手机中的 JXTA4JMS 之后,这两端都做好了进行消息交换的准备:

  1. 桌面端 JXTA4JMS 开始监听 JMS 提供者上 Alice 队列中的 JMS 消息。

  2. 移动端 JXTA4JMS 创建一个输入 JXTA 管道,并开始监听传入的消息。桌面端 JXTA4JMS 搜索这个管道,将它当成一个输出管道,并用它向移动电话发送消息。因此,这个管道被用来从桌面向移动电话发送消息。注意,JXTA 管道是有两个端口(入口和出口)的虚拟通信信道。创建这个管道的一方位于入口,搜索它的一方位于出口。

  3. 需要另一个 JXTA 管道从手机向桌面发送消息。桌面端 JXTA4JMS 创建了这个管道,并开始监听管道中来自移动端 JXTA4JMS 的消息。移动端 JXTA4JMS 搜索这个管道,并用它向桌面发送 JMS 消息。

现在,有两个 JXTA 管道:一个用于从桌面端发送到移动端的消息,另一个用于从移动端发送到桌面端的消息。

现在看看当 Bob(JMS 用户)向 Alice(JXTA4JMS 用户)发送消息时会发生什么。图 1 以图形方式显示了一系列事件。


图 1. 从 JMS 客户机向 J2ME 客户机发送消息
从 JMS 客户机向 J2ME 客户机发送消息
  1. Bob 向 JMS 提供者的 Alice 的队列发送一条 JMS 消息。

  2. Alice 的 PC 上的 JXTA4JMS 负责持续监听 Alice 队列中的消息,因此 JXTA4JMS 接收到了来自 Bob 的 JMS 消息。

  3. JXTA4JMS 现在要将这条 JMS 消息转换成可以通过 JXTA 网络传送的 JXTA 格式。Alice 的移动客户机用 JXME 与 JXTA 网络通信。回想一下我们在本系列第一篇文章中讨论过的内容,JXME 客户机不直接与 JXTA peer 通信,它们通过中继器与 JXTA 网络通信。不过,这个过程对于 JXTA peer 来说是透明的,它们永远也不知道某一条消息是通过中继器转发的,还是直接到达最终接收者的。这就是为什么 JXTA4JMS 实现生成 JXTA 消息(而不是 JXME 消息,在第一篇文章中介绍了它的结构)的原因。

  4. JXTA4JMS 通过移动端 JXTA4JMS 创建的 JXTA 管道向 JXRA 中继器发送消息。由 JXTA 网络负责通过输出管道和通过中继器正确传递消息。

  5. 在将消息发送给移动客户机之前,JXTA 中继器自动将消息从 JXTA 格式转换为 JXME 格式。

  6. Alice 手机中的移动端 JXTA4JMS 持续地监听同一管道中的消息。所以它会接收到 Bob 的消息。

  7. JXTA4JMS 会在 Alice 的手机屏幕上显示这条消息。

从 J2ME 客户机到 JMS 客户机的通信

现在,看看 JXTA4JMS 是如何帮助 Alice 向 Bob 发送回复消息的。图 2 以图形方式展示了这个场景。


图 2. 从 J2ME 客户机向 JMS 客户机发送消息
从 J2ME 客户机向 JMS 客户机发送消息

图 2 中的这个场景与图 1 中的类似:

  1. Alice 写下回复给 Bob 的消息,并让手机中运行的移动端 JXTA4JMS 将这条消息发送给 Bob。

  2. JXTA4JMS 将 Alice 的消息包装为第一篇文章中讨论过的 JXME 消息格式。这条 JXME 消息还包装了 JMS 消息接收者的名字(在这里是 Bob)。

  3. JXTA4JMS 将消息发送给 JXTA 中继器。注意,这条消息是通过 Alice 的桌面端 JXTA4JMS 创建的 JXTA 管道发送的。

  4. JXTA 中继器将 JXME 格式转换为 JXTA 格式。

  5. JXTA 中继器通过 JXTA 网络将 JXTA 消息发到 Alice 的桌面,同时,在 Alice 的桌面端,JXTA4JMS 已经启用。

  6. 桌面端 JXTA4JMS 接收到来自中继器的消息。注意,消息现在是 JXTA 格式的。

  7. 桌面端 JXTA4JMS 将消息转换为 JMS 格式。它提取出接收 Alice 所发出消息的 JMS 客户用户名(在这里是 Bob),而且还将搜索 JMS 提供者上的 Bob 队列。

  8. 桌面端 JXTA4JMS 将消息发送到 JMS 提供者上的 Bob 队列。

  9. Bob 的客户端 JMS 应用程序监听 Bob 的队列,并接收到来自 Alice 的消息。

JXTA-for-JMS 架构

让我们分析一下 JXTA4JMS 实现的架构。正如在 JXTA4JMS 使用模型的讨论中已经看到的,JXTA4JMS 实现由桌面实现和移动端实现组成。现在来分析一下这两个实现。

桌面端实现由多个层组成,如图 3 所示。分层的架构可以确保 JXTA4JMS 有更好的重用性。您可以根据自已的需求很容易地修改 JXTA4JMS 的任何层,并在其他层保持不变的情况下使用它。


图 3. 桌面端 JXTA4JMS 层
桌面端 JXTA4JMS 层

下面将介绍 JXTA4JMS 的每一层:

  • 监听器层监听来自 JMS 网络的 JMS 消息,以及来自 J2ME 设备的 JXTA 消息。监听器层还利用 JMS 提供者建立 JMS 连接,并在 JXTA 网络上创建管道,以便与 J2ME 移动设备进行通信。这一层只包含一个名为Listener 的类。

  • 路由器层拥有将 JMS 消息路由到 JMS 设备(通过 JXTA 网络)的方法,以及将 J2ME 消息路由到 JMS 客户机的方法。这一层由一个名为 Router 的类组成。Listener 监听收到的消息,并用 Router 类正确路由消息。

  • 格式转换层将 JMS 消息和 JXTA 格式相互转换。这一层由两个类组成:JXTAToJMSMsgConverter JMSToJXTAMsgConverter。在将消息路由到目的地之前,Router 类用格式转换类来转换这些消息。

  • 网络层处理使用 JMS 和 JXTA 网络时的低级细节。这一层由类 JXTASearchJMSSearch 组成。JXTASearch 类搜索由 J2ME 设备创建、并通过 JXTA 网络发布的 JXTA 管道。可以用这个管道向 J2ME 设备发送消息。JMSSearch 类搜索 JMS 用户(J2ME 客户消息的接收者)的 JMS 队列。ListenerRouter 类使用 JXTASearchJMSSearch 这两个类。

JXTA4JMS 的移动端实现负责处理无线通信(从桌面端 JXTA4JMS 实现消息的接收和发送)。下面介绍的 4 个类可以在分层架构中工作,以便执行消息传递任务。

  • JXMEMIDlet 类是我编写的一个 MIDlet 示例应用程序,用于展示如何使用一个有简单用户界面的 JXTA4JMS 通信客户机。

  • JXTA4JMSMessagingClient 类是 J2ME 端实现中最重要的类。JXTA4JMSMessagingClient 类提供所有低级功能,比如连接到中继器,以及交换和处理消息。这使高层的应用程序可以只关注用户界面内容。

  • JXTA4JMSMessage 类生成 JXTA4JMS 消息,并对其进行处理。JXTA4JMS 消息是为这种应用程序定制的 JXME 消息。JXTA4JMSMessagingClient 使用这个类生成和处理 JXTA4JMS 消息。

  • DataStore 类是低级工具类,它存储用于接收来自 JMS 端实现的消息的管道标识符。DataStore 类处理使用 J2ME record store 的所有低级细节。JXTA4JMSMessagingClient 类使用这个工具类存储和接收管道标识符。

配置 JXTA4JMS

在实现 JXTA4JMS 之前,应当首先考虑如何在 JXTA 网络中配置 JXTA4JMS。这有助于了解 JXTA4JMS 如何使用 JXTA 集合点和中继器。

JXTA4JMS 需要运行三个 JXTA 实例。第一个实例表示 JXTA 网络上的 JMS 用户(例如 Alice)。这个 JXTA 实例是 JXTA4JMS 实例的一部分。

第二个实例是一个集合点。集合点是所有 JXTA peer 聚会的地方。集合点将缓存 JXTA 广告,使 JXTA peer 可以发现网络上的其他 peer。我将在后面介绍集合点。

第三个实例是一个 JXTA 中继器,移动 JXTA4JMS 用它在 JXTA 网络上进行通信。

这三个 JXTA 实例的配置要求各有不同,我会在稍后说明。在真实的应用程序中,这三个实例运行在通过某种网络技术(如 Internet)彼此连接的不同计算机上。

为了观察可能没有连接到任何网络的单机中运行的 JXTA4JMS,可以让这三个实例运行在同一台计算机上。这个 JXTA4JMS 实现并不关心不同的实例是运行在同一计算机中,还是运行在不同的计算机中。您还可以配置同一个 JXTA 实例,让它起到两个或者三个实例的作用。

我将本文的源代码打包在 wi-jxta2source.zip 文件中,在那里还可以找到一个 readme 文件,该文件解释了我是如何测试 JXTA4JMS 的。

在第一次运行 JXTA 实例时,会看到 JXTA 配置窗口。对每一个实例,只需要填写一次配置窗口中的字段即可。配置窗口由 basic、advanced、rendezvous/relays 和 security 选项卡组成。对于这三个 JXTA 实例,basic 和 security 选项卡中的项是相同的。图 4 显示了 basic 选项卡。


图 4. JXTA 配置窗口中的 basic 选项卡
JXTA 配置窗口中的 basic 选项卡

basic 选项卡有一个用于输入 peer 名的文本框。例如,Alice 在配置在 JXTA 网络上表示她的 JXTA 实例时,在这个文本框中填入字符串“Alice”。您配置集合点和 relay peer 时,可以使用任何名字。

basic 选项卡还包含一个名为“Use proxy server”的选择框。只有当您位于防火墙后时,才需要选中这个选择框。

security 选项卡包含输入 JXTA 实例的登录信息的字段。图 5 显示了包含 secure username 和 password 字段的 security 选项卡。


图 5. JXTA 配置窗口里的 security 选项卡
JXTA 配置窗口里的 security 选项卡

secure username 与 basic 选项卡中的 peer name 字段不同。peer name 字段指定 JXTA 网络中 peer 的名字,而 secure username 字段表示特定 JXTA 实例的登录名。每次启动 JXTA 实例时,都需要 secure username 和 password。

为了简便起见,可以使用同一个名字(如 Alice)作为 peer name 和 secure username 字段的值。

现在让我们看看如何配置 advanced 选项卡。advanced 选项卡有两部分,一部分用于 TCP,一部分用于 HTTP 设置,如图 6 所示。TCP 和 HTTP 设置包括这个 JXTA4JMS 实例监听的 IP 地址和端口号。默认情况下,JXTA 的配置是对 HTTP 使用端口 9700,对 TCP 使用端口 9701。


图 6. JXTA 配置窗口中的 advanced 选项卡
JXTA 配置窗口中的 advanced 选项卡

如果三个 JXTA 实例使用不同的计算机,那么只需要指定每台计算机的 IP 地址即可。如果在同一计算机中运行多个 JXTA 实现,那么需要对每个 JXTA 实例使用不同的 TCP 和 HTTP 端口号。

例如,当我在一台计算机中试验这个应用程序时,我将 Alice 的 JXTA4JMS 实例的 HTTP 和 TCP 端口号分别配置为 9700 和 9701。我将第二个 JXTA 实例配置为既是集合点,又是 relay peer。第二个实例分别监听 HTTP 和 TCP 的端口号 9702 和 9703。

在配置集合点和 relay peer 时,必须在 advanced 选项卡中再做一件或者几件事:单击 HTTP settings 部分中的 Enable Incoming Connections 选择框。这使其他 peer 可以与集合点和 relay peer 建立 HTTP 连接。

如图 7 所示的 rendezvous/relays 选项卡包含两部分,一部分用于集合点,一部分用于 relay peer。


图 7. JXTA 配置窗口中的 rendezvous/relays 选项卡
JXTA 配置窗口中的 rendezvous/relays 选项卡

在配置 relay peer 时,必须选择 rendezvous/relays 选项卡中的 Act as a JxtaProxyAct as a Relay 选择框。在配置集合点时,必须选择Act as a Rendezvous 选择框。

还需要为其他两个 JXTA 实例( JXTA4JMS 实例和中继器实例)提供集合点实例的 IP 地址和端口号。为此,必须在 Available TCP rendezvousAvailable HTTP rendezvous 字段中填入集合点的 IP 地址和端口号。

现在对集合点做一简单说明。真实的 JXTA 网络包含许多集合点。如果 Bob 和 Alice 通过 JXTA 网络进行通信,那么他们都至少要知道一个集合点。他们知道的不一定是同一个集合点。

如果 Bob 知道集合点 R1,而 Alice 知道集合点 R2,并且集合点 R1 和 R2 知道一个共同的集合点 R3,那么 Bob 和 Alice 就可以通过 JXTA 网络进行无缝通信。JXTA 网络自动管理 peer 的发现,Bob 和 Alice 不用关心这些细节。这是 JXTA 技术的一个重要特性。

现在让我们讨论桌面端 JXTA4JMS 实现的细节,然后再讨论移动端的 JXTA4JMS 实现。


实现桌面端 JXTA4JMS

我已经讨论了 JXTA4JMS 架构中不同层的功能,因此我现在将展示这些层的实现。因为是上层使用低层,所以我对实现的讨论是从下向上进行的。

JXTASearch 类

JXTASearch 类搜索 JXTA 网络中的特定管道。JXTASearch 类以同步方式搜索管道,这意味着对JXTASearch 类的调用在找到管道之后才会返回。要搜索的管道是由移动端创建、发布和监听的管道。这个管道将用于向 J2ME 设备发送消息。

JXTASearch 类包含一个构造函数和两个方法:getPipeAdvertisement() getOutputPipe()。此外,JXTASearch 类还实现了一个名为 OutputPipeListener 的接口。根据 JXTA 实现,任何想接收管道来创建通知的类都要实现OutputPipeListener 接口。OutputPipeListener 接口有一个名为outputPipeEvent() 的方法,它接收来自底层 JXTA 实现的管道解析通知(resolving notifiation)。很快您就会看到JXTASearch 类是如何实现outputPipeEvent() 方法的。

JXTASearch 构造函数

JXTASearch 构造函数(清单 1)采用了PeerGroup 类的一个实例。PeerGroup 类是与 JXTA peer 组交互的一个方便方式。它包含执行那些您可能想在 peer 组中执行的各种任务的方法。我会在后面描述如何使用这个类的两个方法。现在,只需要注意JXTASearch 构造函数只是将 PeerGroup 对象存储在类级的变量(名为 peerGroup),以便以后getPipeAdvertisement()getOutputPipe() 方法使用这些变量。


清单 1.JXTASearch 构造函数

 public JXTASearch ( PeerGroup peerGroup ){
        this.peerGroup = peerGroup;
    }

 



 

getPipeAdvertisement() 方法

清单 2 中显示的getPipeAdvertisement() 方法以管道名为参数,并搜索对应这个管道的广告。Listener 类在调用getPipeAdvertisement() 方法时指定管道的名称。

getPipeAdvertisement() 方法以 PipeAdvertisement 对象的形式返回管道的广告。


清单 2. getPipeAdvertisement() 方法

public PipeAdvertisement getPipeAdvertisement ( String targetPipeName ) {
        Enumeration enum = null;
        PipeAdvertisement pipeAdvert = null;
       
        DiscoveryService discoveryService = peerGroup.getDiscoveryService ();
        try
        {
            while( true ){
                discoveryService.getRemoteAdvertisements (
                                                     null, 
                                                     DiscoveryService.ADV, 
                                                     "Name", 
                                                     targetPipeName, 
                                                     5
                                                     );
                enum = discoveryService.getLocalAdvertisements (
                                                           DiscoveryService.ADV, 
                                                           "Name", 
                                                           targetPipeName
                                                           );
                
                if ( enum != null)
                {
                    while (enum.hasMoreElements()) 
                        pipeAdvert = ( PipeAdvertisement ) enum.nextElement ();
                }//if(enum != null)
                
                if ( pipeAdvert != null ) 
                    break;
        
            }//while( true )
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
        
        return pipeAdvert;
        
    }//getPipeAdvertisement() 



 

getPipeAdvertisement() 方法中的第一项工作是调用存储在构造函数中的 PeerGroup 对象的getDiscoveryService() 方法。这个getDiscoveryService() 方法调用将返回DiscoveryService 对象。

DiscoveryService 类表示一种称为发现服务的 JXTA 服务。JXTA peer 利用发现服务搜索 JXTA 网络上的不同资源。我使用 JXTA 发现服务搜索指定 peer 组中的特定管道。

如果想使用 JXTA 发现服务,那么需要编写 XML 搜索查询,并将查询发送给前面配置 JXTA4JMS 中讨论过的集合点。而且,必须处理搜索查询找到的消息。

不过,JXTA 实现可以使您不用做这项工作。DiscoveryService 类很好地包装了发现服务的功能。只需调用 DiscoveryService 类的几个方法作出响应即可,不用担心 JXTA 所要求和使用的 XML 语法。JXTA 实现负责这些底层细节。

获得 DiscoveryService 对象后,可以用它的方法执行管道搜索操作。DiscoveryService 类有一个名为getRemoteAdvertisements() 的方法,它向 JXTA 网络发送搜索查询。getRemoteAdvertisements() 方法有 5 个参数:

  • 第一个参数是一个标识符。如果知道要搜索的管道的标识符,那么可以将这个标识符作为第一个参数提供给 getRemoteAdvertisements() 方法,以加快远程搜索。不过,在这里,我们不知道要搜索的管道的标识符。因此将 null 作为第一个参数传递给getRemoteAdvertisements() 方法调用。

  • 第二个参数指定要搜索的资源类型(例如,广告、peer 或者 peer 组等)。我们搜索的是管道广告,因此只需要使用 DiscoveryService 类的一个名为ADV 的静态字段。如果搜索 peer 或者 peer 组,那么还要分别指定PEER 或者 PEERGROUP 静态字段。

  • 第三个参数是要搜索的属性的名字。要搜索的是具有特定名字的管道广告。因此,要搜索带有 “Name” 属性的特定值的管道。这类似于在第一篇文章的“JXME 编程”一节中讨论的search() 方法的第二个参数。因此,我们将字符串“Name”作为第三个参数传递给getRemoteAdvertisements() 方法调用。

  • 第四个参数是要搜索的管道名(或者可以说 “Name” 属性的值)。只需将管道的名字作为值传递即可。

  • getRemoteAdvertisements() 方法的最后一个参数是要接收的搜索结果的最大数量。您不知道有多少 peer 会响应这个搜索查询,以及每一个 peer 会返回多少个响应。因此,要对搜索操作进行限制。将“5”作为最后一个参数的值传递,这表示从每一个 peer 接收最多 5 个结果。

getRemoteAdvertisements() 方法不返回任何东西。它将搜索结果存储在由 JXTA 实现维护的本地缓冲中。因此,在调用getRemoteAdvertisements() 方法后,必须调用另一个getLocalAdvertisements() 方法以从本地缓冲区中提取搜索结果。

getLocalAdvertisements() 方法有三个参数:资源类型、属性名和属性值。这三个参数与 getRemoteAdvertisements() 方法的第二、三、四个参数是相同的。

getLocalAdvertisements() 方法返回一个 Enumeration 对象。Enumeration 对象包含以几个Advertisement 对象的形式出现的搜索结果。

getLocalAdvertisement() 方法返回的 Enumeration 对象会有不同类型的 Advertisement 对象。因为搜索的是管道广告,可以预计,如果 Enumeration 对象不是 null,那么它将包括一个或者多个PipeAdvertisement 对象。PipeAdvertisement 对象表示管道广告。

让我们遍历整个 Enumeration 并取出最后的广告,因为最后一个也是最新的。

getOutputPipe() 方法

清单 3 中所示的 getOutputPipe() 方法用一个 PipeAdvertisement 对象作为参数。通常,这个参数与在getPipeAdvertisement() 方法中搜索的PipeAdvertisement 对象是相同的。

getOutputPipe() 方法返回一个 OutputPipe 对象。这个 OutputPipe 对象表示管道广告对应的 JXTA 管道。


清单 3. getOutputPipe() 方法

    public OutputPipe getOutputPipe (PipeAdvertisement pipeAdvert){
        try
        {
            PipeService pipeSvc = peerGroup.getPipeService ();
            pipeSvc.createOutputPipe ( pipeAdvert, this );
            while ( true )
            {
                if ( outputPipe != null )
                    break;
            }//while (true)
         
         return outputPipe;        
        
        }//try
        catch ( Exception e ){
             e.printStackTrace();
        }//catch
         return null;
    }//getOutputPipe()

正如需要使用 JXTA 发现服务来搜索管道广告一样,这里需要使用 JXTA 管道服务创建一个输出管道。JXTA 实现提供了一个名为 PipeService 的类,它包装了 JXTA 管道服务的功能。用PeerGroup 类的getPipeService() 方法可以得到 PipeService 对象。

PipeService 类包含 createInputPipe()createOutputPipe() 方法,可以用它们来创建进行消息交换的输入和输出管道。PipeService 类的createOutputPipe() 方法有两个参数:PipeAdvertisementOutputPipeListener 对象。它用这个广告创建一个 JXTA 管道,并返回一个OutputPipe 对象。

JXTA 管道创建过程是在 JXTA 网络上完成的。我不会讨论这些细节,不过 createOutputPipe() 方法不会等待管道创建好后才返回,所以它是异步返回的。

JXTA 网络随后以管道解析(resolving)事件的形式确认管道的创建。根据 JXTA 实现,只有实现 OutputPipeListener 接口的类才可以接收管道解析事件通知。OutputPipeListener 接口只包含一个名为outputPipeEvent() 的方法。JXTASearch 类实现了outputPipeEvent() 方法,因此它可以接收通知。

通知到达时,outputPipeEvent() 方法就获得了控制权。outputPipeEvent() 实现很简单,清单 4 显示它只有一行。outputPipeEvent() 方法在名为outputPipe 的类级对象中存储新创建的管道。


清单 4. outputPipeEvent() 方法
    public void outputPipeEvent ( OutputPipeEvent e ) {
        outputPipe = e.getOutputPipe ();
    }//outputPipeEvent()

为了简化 JXTASearch 类的使用,我将 getOutputPipe() 方法封闭在一个无限 while 循环中,只有通知到达时,循环才会中断。这个无限 while 循环不断检查outputPipeEvent() 方法是否设置了outputPipe 类级对象。当 getOutputPipe() 方法发现所需要的OutputPipe 对象时,就会返回这个对象。

JXTAToJMSMsgConverter 类

JXTAToJMSMsgConverter 类使用 JXTA 消息,并将它转换为相应的 JMS 消息。JXTAToJMSMsgConverter 类包含 4 个方法(一个构造函数和三个 getter 方法),这些方法的具体说明如下:

JXTAToJMSMsgConverter 构造函数

清单 5 显示了 JXTAToJMSMsgConverter 构造函数。


清单 5. JXTAToJMSMsgConverter 构造函数
public JXTAToJMSMsgConverter (
                              QueueConnectionFactory qConnFactory, 
                              net.jxta.endpoint.Message jxtaMsg
                            ) throws JMSException {
          QueueConnection queueConnection = null;
  
          try
          {
              //Creating a new JMS text message.
              //Step 1:
              queueConnection = qConnFactory.createQueueConnection ();
              //Step 2:              
              queueSession = 
                     QueueConnection.createQueueSession (
                                               false, 
                                               Session.AUTO_ACKNOWLEDGE );
              //Step 3:
              jmsMessage = queueSession.createTextMessage ();
          }
          catch ( Exception e ) {
              e.printStackTrace ();
          }
        MessageElement jmsRecipientElement, msgElement;
        jmsRecipientElement = jxtaMsg.getMessageElement ("JMSRecipient");
        msgElement = jxtaMsg.getMessageElement ("Message");
        jmsRecipient = jmsRecipientElement.toString();
        jmsMessage.setText (msgElement.toString());
        
    }//JXTAToJMSMsgConverter


这个构造函数有两个参数,一个名为 qConnFactoryQueueConnectionFactory 对象,以及一个名为jxtaMsgnet.jxta.endpoint.Message 对象。我将进一步分析这些对象。

Listener 类实例化了一个 QueueConnectionFactory 对象,并将这个对象作为 JXTAToJMSMsgConverter 构造函数的第一个参数进行传递。QueueConnectionFactory 是一个连接工厂和一个 JMS 管理的对象。根据 JMS 架构,需要一个QueueSession 对象来创建新的 JMS 消息。同时还需要一个队列连接工厂(一个QueueConnectionFactory 对象)来创建QueueSession 对象。在文章的后面部分,可以看到如何用连接工厂创建 JMS 消息。

net.jxta.endpoint.Message 对象(JXTAToJMSMsgConverter 构造函数的第二个参数)是将转换为 JMS 格式的 JXTA 消息。看一看JXTAToJMSMsgConverter 构造函数是如何工作的。

必须先创建一个新的 JMS 消息对象,这分为三步。首先,调用 QueueConnectionFactory 对象的 createQueueConnection() 方法。这会提供一个QueueConnection 对象。然后调用QueueConnection 对象的 createQueueSession() 方法,它会给出一个QueueSession 对象。在第三步,调用QueueSession 对象的 createTextMessage() 方法,它返回一个javax.jms.TextMessage 对象。这个javax.jms.TextMessage 对象名为 jmsMessage。最后要用来自 JXTA 消息的数据填充这个 JMS 消息对象。注意清单 5 “创建一个新 JMS 文本消息”中的这三步。

在这里要注意的是,QueueSession 对象有创建不同类型的 JMS 消息的方法。例如,可以用 QueueSession 类的createByteMessage() 方法创建二进制形式的消息。不过,我选择使用createTextMessage() 方法,因为我想在本系列文章中展示文本消息的交换。

现在看一下如何从收到的 JXTA 消息中提取数据,并用这些数据填充 JMS 消息。

收到的 JXTA 消息包含两部分:消息接收者的名字和消息本身。JXTA 消息的每一部分在 JXTA 消息中都是一个元素。消息接收者的名字包装在名为 “JMSRecipient” 的元素中。消息包装在名为 “Message” 的元素中。

可以调用 net.jxta.endpoint.Message 类的 getMessageElement() 方法,并同时传递元素的名字。getMessageElement() 方法返回一个MessageElement 对象,它表示 JXTA 消息的一个元素。

我将在后面介绍 JXTA 消息中的每个元素的结构。现在,只需注意清单 5 中两个MessageElement 对象即可,这两个对象分别名为msgElement(为包装消息的对象)和 jmsRecipientElement(为包装接收者名字的对象)。

现在可以调用每一个 MessageElement 对象的 toString() 方法。这个方法以字符串的形式返回MessageElement 的内容。

然后可以调用 jmsMessage 对象(即前面创建的 javax.jms.TextMessage 对象)的setText() 方法,同时传递 JXTA 消息内容。这会在jmsMessage 对象中设置 JXTA 消息内容。jmsMessage 对象现在就准备好了。

getQueueSession() 方法

getQueueSession() 方法(如清单 6 所示)返回在构造函数中创建的 QueueSession 对象。可以看到Router 类是如何调用getQueueSession() 方法来提取 QueueSession 对象的。Router 类用这个QueueSession 对象将文本消息发送给 JMS 接收者。


清单 6. getQueueSession() 方法

 

    public QueueSession getQueueSession() {
        return queueSession;
    }//getQueueSession()


 

getMessage() 方法

getMessage() 方法(如清单 7 所示)返回在构造函数中创建并填充的 javax.jms.TextMessage 对象,该对象名为jmsMessage


清单 7. getMessage() 方法
    public javax.jms.TextMessage getMessage() {
        return jmsMessage;
    }//getMessage()



getJMSRecepient() 方法

清单 8 中所见的 getJMSRecipient() 方法返回接收者的名字,这个名字可以从前面构造函数中传入的 JXTA 消息中提取。

还要注意的是,在清单 8 中,已经将字符中 “jms/” 作为前缀串添加到 JMS 接收者的名字上。然后用接收者的名字作为 JMS 队列名向队列发送消息。“jms/” 字符串遵守了 Java 规范,所以 JMS 队列的名称是以“jms/” 字符串开头的。


清单 8. getJMSRecipient() 方法
    public String getJMSRecipient() {
        return "jms/" + jmsRecipient;
    }//getJMSRecipient()

JMSToJXTAMsgConverter 类

JMSToJXTAMsgConverter 是消息格式转换器类,它采用了一个 JMS 消息,并生成一个 JXTA 消息。JMSToJXTAMsgConverter 类包含下面介绍的这些方法。

JMSToJXTAMsgConverter 构造函数

现在看看清单 9,它展示了 JMSToJXTAMsgConverter 构造函数。


清单 9. JMSToJXTAMsgConverter 构造函数

 

    public JMSToJXTAMsgConverter ( String sender, 
                                   TextMessage jmsMsg
                                 ) throws JMSException {
        StringMessageElement smeSender  = null;
        StringMessageElement smeMessage = null;
        jxtaMessage = new net.jxta.endpoint.Message ();
        smeSender  = new StringMessageElement ("JMSSender", sender, null);
        smeMessage = new StringMessageElement ("Message", jmsMsg.getText(), null);
        jxtaMessage.addMessageElement (smeSender);
        jxtaMessage.addMessageElement (smeMessage);
    }//JMSToJXTAMsgConverter constructor


 

JMSToJXTAMsgConverter 构造函数几乎执行这个类所要求的所有处理。它有两个参数,第一个参数是发送 JMS 消息的 JMS 客户机的名称。在构造函数JMSToJXTAMsgConverter 中生成的 JXTA 消息里加入发送者的名字。这使 J2ME 接收者知道是哪一个 JMS 客户机发送的消息。第二个参数是以TextMessage 对象形式出现的 JMS 消息。

JXTA 消息是根据收到的 JMS 消息生成的。JXTA 消息由几个消息元素组成。因此,我首先分析 JXTA 消息中每个元素的内部细节。

JXTA 消息元素的结构类似于(第一篇文章的“中继器和 JXME 客户机”一节中介绍的)JXME 消息元素的结构。

JXTA 消息元素包含以下 4 个字段:

  • 消息元素的名字。 在这里,在一个名为 “Message” 的元素中包装 JMS 消息。与此类似,将 JMS 发送者的名字包装到一个名为“JMSSender” 的元素中。注意,元素的名称是可选的,因此可以忽略它。不过,为每个元素命名会使处理变得直观而且更容易实现。

  • 可选的消息元素 MIME 类型。 如果没有指定这个字段,那它的值就假定为 “Application/Octet-Stream”。在这里,所生成的消息的 MIME 类型是“text/plain”

  • 要发送的数据。 消息元素,如包含 JMS 消息内容的消息元素,包含 JMS 发送者名字的 sender 元素等。

  • 可选的签名。 这个字段包含消息元素的加密签名。目前,JXME 还不支持在 J2ME 一端添加签名。因此,在生成 JXTA 消息时没有必要添加签名。

您已经见过了 JXTA 消息元素的结构。JXTA 实现有一个名为 MessageElement 的类,它处理所有类型的 JXTA 消息元素。此外,还有一些从MessageElement 类派生的子类。每一个子类处理一种特定类型的消息元素。例如,StringMessageElement 类处理携带文本字符串的消息元素(这种消息的 MIME 类型是“text/plain”)。

StringMessageElement 类生成 JXTA 消息的每个消息元素。将 JMS 消息发送者的名字包装到名为 smeSenderStringMessageElement 对象中。与此类似,将 JMS 消息内容包装到名为 smeMessage 的另一个 StringMessageElement 对象中。

要用 StringMessageElement 类生成一个消息,必须先调用 StringMessageElement 类的构造函数实例化这条消息。这个构造函数有三个参数。第一个参数指定消息元素的名称,第二个参数指定元素数据的内容,第三个参数指定加密签名。我们没有使用签名,因此传递 null 作为第三个参数的值。

分析一下清单 9 中的JMSToJXTAMsgConverter 构造函数。先实例化名为smeSendersmeMessage 的两个StringMessageElement 对象。在创建smeMessage 对象时,需要使用传入的 JMS 消息的内容。可以用TextMessage 对象的 getText() 方法提取 JMS 消息内容,然后将内容传递给 StringMessageConstructor,以便生成smeMessage 对象。

现在,需要将这两个 StringMessageElement 对象包装到一个 JXTA 消息中。为此,必须先实例化一个名为 jxtaMessagenet.jxta.endpoint.Message 对象,该对象表示了一个完整的 JXTA 消息。然后,需要两次调用它的addMessageElement() 方法,对每个StringMessageElement 对象调用一次。

getMessage() 方法

用清单 10 所示的 getMessage() 方法提取 JXTA 消息。这个方法只返回在构造函数中准备的 jxtaMessage 对象。


清单 10. getMessage() 方法
    public Message getMessage () {
        return jxtaMessage;
    }//getMessage()

JMSSearch 类

JMSSearch 类在 JMS 网络中进行搜索,就像 JXTASearch 在 JXTA 网络中搜索一样。不过,在 JMS 网络中搜索是相当简单的,因为可以使用 Java Naming and Directory Interface(JNDI)搜索不同的 JMS 资源。您可以用 JNDI 搜索像消息对象(队列和连接工厂)、数据库和 mail 会话这样的资源。

这里用 JNDI 搜索队列。看一下清单 11 中的 JMSSearch 类。


清单 11. JMSSearch 类
public class JMSSearch
{
    public JMSSearch (){
    }
    public QueueSender getQueueSender ( String queueName, 
                                       QueueSession queueSession ) {
        InitialContext jndiContxt = null;
        Queue outgoingQueue= null;
        QueueSender qSender = null;
 
        try
        {
            jndiContxt = new InitialContext ();
            outgoingQueue= (Queue) jndiContxt.lookup ( queueName );
            qSender = queueSession.createSender (outgoingQueue);
        }//try
        catch ( Exception e ) {
            e.printStackTrace ();
        }//catch
        
        return qSender;
        
    }//getQueueSender()
}//JMSSearch


JMSSearch 类只包含一个方法 getQueueSender()。这个方法有两个参数:要搜索的队列名和一个QueueSession 对象。这与前面对JXTAToJMSMsgConvertergetQueueSession() 方法进行的讨论中介绍的QueueSession 对象是一样的。我会解释将QueueSession 对象作为参数传递的目的。

getQueueSender() 方法为这个特定的队列名返回 QueueSender 对象。

正如在清单 11 中看到的,getQueueSender() 方法的实现很简单。首先,它实例化一个 InitialContext 对象。这是 JNDI 的要求。InitialContext 对象包含进行搜索操作的方法。

然后,只需调用 InitialContext 类的 lookup() 方法即可。在 JNDI 术语中,搜索被称为查找(lookup)。lookup() 方法以队列名作为参数,返回一个表示所搜索队列的Queue 对象。Queue 对象被命名为outgoingQueue,因为这个 Queue 对象表示消息接收者的 JMS 队列。在后面要用这个队列发送输出消息。例如,假设 Alice 正在路上,带着她的移动端 JXTA4JMS,并向 Bob 发送了一条消息。她的桌面端 JXTA4JMS 收到通过 JXTA 网络传来的这条消息,然后在 JMS 网络中搜索 Bob 的队列,将 Bob 的队列作为outgoingQueue 对象处理,同时将 Alice 的消息发送给(Bob 的)outgoing 队列。

现在您认识到了在队列名前面加上前缀 “jms/” 的重要性了吧(回想一下在 JXTAToJMSMsgConverter 类的getJMSRecepient() 方法前添加的“jms/” 前缀)。JNDI 查询不仅搜索 JMS 资源(其他服务端模块用它搜索非 JMS 资源,如数据库),所以用某种标识符标识出属于特定组的资源(例如,所有属于 JMS 网络的资源)很重要。如果不在队列名前加上字符串前缀,那么可能会将队列名与一些其他非 JMS 网络资源相混淆(如数据库表)。

尽管搜索了所需要的 Queue 对象,但是这里还有一步操作要做。要用搜索到的 Queue 对象创建一个 QueueSender 对象。然后,Router 类就用这个 QueueSender 对象发送 Queue 中的消息。

要创建 QueueSender 对象,还需要一个 QueueSession 对象。这个 QueueSession 对象应当与在JXTAToJMSMsgConverter 类中生成 JMS 消息时使用的那个对象相同。Router 类同时使用JXTAToJMSMsgConverterJMSSearch 类,所以在下面讨论 Router 时,我将说明它如何确保将正确的QueueSession 对象传递给getQueueSender() 方法。

getQueueSender() 方法中做的最后一件事是调用 QueueSession 对象的 createSender() 方法。这个方法用我们刚刚创建的 Queue 对象作为参数,并返回一个 QueueSender 对象。QueueSender 现在就完成了它该做的操作。如果通过这个 sender 对象发送 JMS 消息,那么消息会自动达到正确的接收者。现在,看一下Router 类如何使用刚才创建的所有底层支持来完成路由。

Router 类

Router 类是 JXTA4JMS 通信的通信枢纽。Router 类使用一些低级的层(包括格式转换和网络),并在 JMS 网络与 JXTA 网络之间来回发送消息。Router 类的作用是双重的:

  • 它将从 JMS 网络收到的消息发送给移动客户机。
  • 同时将从 JXTA 网络接收到的消息发送给 JMS 客户机。

因此,它包含两个方法:sendMessageToMobile()sendMessageToJMS()Listener 类在收到来自 JMS 客户机的消息时调用sendMessageToMobile() 方法。sendMessageToMobile() 方法通过 JXTA 网络向 J2ME 客户机发送消息。与此类似,Listener 类在收到来自移动客户机的消息时使用sendMessageToJMS() 方法。

清单 12 显示了 Router 构造函数的实现。


清单 12. Router 构造函数
    public Router ( String peerName, PeerGroup peerGroup ) {
        this.peerName = peerName;
        this.peerGroup = peerGroup;
    }//Router

Router 构造函数的实现很简单。它只采用了两个参数(peer 的名字和它的 peer 组),并存储这些参数,以便 Router 类的两个方法使用它们。

sendMessageToMobile() 方法

清单 13 展示了 sendMessageToMobile() 方法的实现。


清单 13. sendMessageToMobile() 方法
    public void sendMessageToMobile( 
                                   String sender, 
                                   OutputPipe outputPipe, 
                                   TextMessage jmsMessage ) {
        try
        {
            JMSToJXTAMsgConverter jxtaConverter = 
                              new JMSToJXTAMsgConverter ( sender, jmsMessage );
            net.jxta.endpoint.Message jxtaMessage = jxtaConverter.getMessage ();
            outputPipe.send ( jxtaMessage );
        }//try
        catch ( Exception e ) {
            e.printStackTrace ();
        }//catch
    }//sendMessageToMobile()


sendMessageToMobile() 方法有三个参数:

  • JMS 发送者的名字,我们将发送者的 JMS 消息发送给移动客户机。

  • 一个表示 JXTA 管道的 OutputPipe 对象。(移动客户机监听这个管道的另一端,因此通过这个管道发送消息。)在后面讨论Listener 类时,我将说明创建这个OutputPipe 对象的机制。

  • 要发送给移动客户机的 JMS 消息。

很容易猜到,您必须做的就是将 JMS 消息从 JMS 格式转换为 JXTA 格式,然后通过输出管道发送这个消息。

因此,正如在清单 13sendMessageToMobile() 方法中看到的,首先实例化一个JMSToJXTAMsgConverter 对象,并用它获得传入的 JMS 消息的 JXTA 表达。然后,只需调用OutputPipe 对象的send() 方法,同时传递 JMS 消息即可。OutputPipe 对象处理通过 JXTA 网络发送消息的低级过程。

sendMessageToJMS() 方法

现在看一下清单 14,它展示了 sendMessageToJMS() 方法的实现。


清单 14. sendMessageToJMS() 方法
    public void sendMessageToJMS( QueueConnectionFactory qConnFactory, 
                                  net.jxta.endpoint.Message jxtaMessage )
    {
        try {
            JXTAToJMSMsgConverter jmsConverter = new JXTAToJMSMsgConverter (
                                                       qConnFactory,
                                                       jxtaMessage
                                                     );
            String jmsRecipient = jmsConverter.getJMSRecipient();
            TextMessage jmsMessage = jmsConverter.getMessage();
            QueueSession queueSession = jmsConverter.getQueueSession();
            
            JMSSearch jmsSearch = new JMSSearch ();
            QueueSender qSender = jmsSearch.getQueueSender ( jmsRecipient,
                                                             queueSession );
            
            qSender.send ( jmsMessage);
         
        }//try
        catch ( Exception e ){
            e.printStackTrace ();
        }//catch
        
    }//sendMessageToJMS



sendMessageToJMS() 方法采用了两个参数:QueueConnectionFactory 对象和将发送给 JMS 接收者的 JXTA 消息。回想一下,JXTAToJMSMessageConverter 构造函数也要求使用同样的两个对象。因此,sendMessageToJMS() 方法要实例化一个JXTAToJMSMsgConverter 对象,然后调用JXTAToJMSMsgConverter 类的 getJMSRecepient()getMessage() 方法,以便分别提取 JMS 消息的发送者的名字,以及所收到的 JXTA 消息的 JMS 格式。

sendMessageToJMS() 方法还调用了 JXTAToJMSMsgConverter 类的 getQueueSession() 方法。如前所述,这个方法将返回 QueueSession 对象。

然后 sendMessageToJMS() 方法实例化了一个 JMSSearch 对象,并调用该对象的 getQueueSender() 方法。getQueueSender() 方法用 JMS 消息接收者的名字(一个队列名)和QueueSession 对象作为参数。它返回一个QueueSender 对象(已在讨论 JMSSearch 类时介绍)。

最后,调用 QueueSender 对象的 send() 方法。send() 方法采用转换后的 JMS 消息,并将这条消息发送给 JMS 接收者。

Listener 类

Listener 类被设计成一个连续监听模块。需要将这个类设计为在启动后同时监听 JMS 和 JXTA 网络。当它收到 JMS 消息后,立即将这个消息通过 JXTA 网络转发给 J2ME 设备。而当它从 JXTA 网络收到 J2ME 设备发来的消息后,它会立即将这个消息转发给相关的 JMS 客户机。

Listener 类实现了一个 run() 方法,该方法在单独的线程中执行。这个线程不间断地监听来自 JMS 和 JXTA 网络的消息。我将展示如何在run() 方法中实现 JMS 和 JXTA 消息的处理逻辑。Listener 类包含一些专用辅助方法。这些方法有助于创建输入和输出 JXTA 管道。

createPipeAdvertisement() 方法

清单 15 中展示的 createPipeAdvertisement() 方法是一个专用辅助方法,它将创建一个管道广告,并以 PipeAdvertisement 对象的形式返回这个广告。


清单 15. createPipeAdvertisement() 方法
    private PipeAdvertisement createPipeAdvertisement (String peerName) {
        PipeAdvertisement pipeAd = null;
        try 
        {
            String fileName = peerName+".xml";
            File file = new File ( fileName );
            if ( file.exists() )
            {
                FileInputStream is = new FileInputStream (file);
                if ( is.available() > 0 )
                {
                    pipeAd =
                       (PipeAdvertisement) AdvertisementFactory.newAdvertisement(
                                           new MimeMediaType( "text/xml" ),
                                           is 
                                         );
                                         
                }//if ( is.available() > 0)
            }
            else
            {
                pipeAd = 
                     (PipeAdvertisement) AdvertisementFactory.newAdvertisement (
                                          pipeAd.getAdvertisementType ()
                                        );
                pipeAd.setName (peerName);
                pipeAd.setType ( "JxtaUnicast" );
                pipeAd.setPipeID( (ID)net.jxta.id.IDFactory.newPipeID(
                                               netPeerGroup.getPeerGroupID()
                                              )
                                            );
                                              
                FileOutputStream os = new FileOutputStream ( fileName );
                os.write ( pipeAd.toString().getBytes() );
                os.close();
            }//end of else        
            return pipeAd;
        
        }//try
        catch (Exception ex) {
            ex.printStackTrace ();
        }//catch
        return null;
    }//createPipeAdvertisement()



创建管道广告就是一个生成 XML 的任务。需要根据由 JXTA 协议定义的广告格式生成正确的 XML 代码。观察清单 16,它展示了管道广告的 XML 结构。


清单 16. 一个 XML 格式的管道广告示例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jxta:PipeAdvertisement>
<jxta:PipeAdvertisement xmlns:jxta="http://jxta.org">
    <Id>
       urn:jxta:uuid-59616261646162614E50.....91E04
    <Id>
    <Type>
       JxtaUnicast
    <Type>
    <Name>
       AliceJMS
    <Name>
</jxta:PipeAdvertisement>


JXTA 提供了方便您生成 JXTA 广告的类。createPipeAdvertisement() 方法首先检查管道广告是否(由于以前的管道创建操作)已经存在于一个文件中。如果存在这样的文件,那么只需打开该文件并在文件输入流中读取其内容即可。然后,用一个名为AdvertisementFactory 的 JXTA 类从输入流创建一个广告。

AdvertisementFactory 类有一个名为 newAdvertisement() 的静态方法,它有两个参数。第一个参数指定所创建广告的 MIME 类型。在这里,要创建一个 XML 广告,所以 MIME 类型应当是“text/xml”。第二个参数指定从文件中创建的输入流。

newAdvertisement() 方法返回一个 Advertisement 对象,在将它返回给调用应用程序之前,可以将它强制转换为PipeAdvertisement

现在,看一看没有管道广告时如何做。当然,必须从头开始生成管道广告。为此,要重载 AdvertisementFactory 类的 newAdvertisement() 方法。这个方法只采用了一个参数,它指定要创建的广告的类型。newAdvertisement() 方法返回Advertisement 形式的管道广告。将Advertisement 对象强制转换为 PipeAdvertisement 对象。

现在设置刚创建的新管道广告的三个参数。这三个参数是要宣传的管道的名字、管道的类型(在这里是 JXTAUnicast)和管道标识符。我们已经知道前两个参数,但是需要一个名为IDFactory 的标识符工厂来创建新的标识符。这个工厂帮助创建惟一的标识符。

完成了新的广告后,将它保存到磁盘文件中并返回 PipeAdvertisement 对象。

publishPipeAdvertisement() 方法

清单 17 所示的 publishPipeAdvertisement() 方法是一个专用辅助方法,它采用了一个 PipeAdvertisement 对象,并通过 JXTA 网络发布它。


清单 17. publishPipeAdvertisement() 方法
   private boolean publishPipeAdvertisement (PipeAdvertisement pipeAd)
    {
        DiscoveryService discSvc = netPeerGroup.getDiscoveryService ();
        discSvc.remotePublish ( pipeAd, 
                                DiscoveryService.ADV, 
                                DiscoveryService.NO_EXPIRATION
                              );
        return true;
    }//publishPipeAdvertisement()

 



已经在一个 peer 组发布了管道广告。而且,所有广告都有一个失效时间。因此,这个广告会在特定的时间段内出现在特定的 peer 组中。

JXTA 提供了一种称为管道服务的服务,可以用它来发布管道广告。JXTA 实现在一个名为 PipeService 的类中包装了管道服务的功能。可以通过调用PeerGroup 对象的getPipeService() 方法实例化 PipeService 类。

只需调用 PipeService 类的 remotePublish() 方法就行了。remotePublish() 方法在 JXTA 网络上发布广告。它有三个参数(要发布的PipeAdvertisement 对象、广告的类型和广告的失效时间)。

createInputPipe() 方法

清单 18 所展示的 createInputPipe() 方法采用了一个 peer 名,并创建一个输入管道来监听收到的消息。


清单 18. createInputPipe() 方法
    private void createInputPipe (String peerName) {
        PipeAdvertisement pipeAd = createPipeAdvertisement (peerName);
        
        if (pipeAd!= null) {
            boolean published = publishPipeAdvertisement ( pipeAd );
            
            if (published) {
                PipeService pipeSvc = netPeerGroup.getPipeService ();
        
                try {
                    inputPipe = pipeSvc.createInputPipe ( pipeAd );
                    
                }//try
                catch (IOException io) {
                    io.printStackTrace ();
                }//catch
                
            }//if(published)
            
        }//if (pipeAd!= null)
    }//createInputPipe()

 



createInputPipe() 方法分三步创建一个输入管道:

  1. 它调用 createPipeAdvertisement() 方法,同时传递 peer 标识符。createPipeAdvertisement() 方法返回一个已经讨论过的PipeAdvertisement 对象。

  2. 然后它调用 publishPipeAdvertisement() 方法,同时传递 PipeAdvertisement 对象。publishPipeAdvertisement() 方法在 JXTA 网络上发布广告。

  3. 最后,它在发布的 PipeAdvertisement 对象上创建一个 InputPipe 对象。为此,只要调用PipeService 类的createInputPipe() 方法,就会返回一个 PipeService 对象。

createInputPipe() 设置新创建的 InputPipe 对象为一个类级的对象。在后面用这个 JXTAInputPipe 对象监听收到的 JXTA 消息。

searchAndCreateOutputPipe() 方法

已经介绍了如何创建一个输入 JXTA 管道。现在,看一看如何创建一个输出 JXTA 管道。

清单 19 中所见的 searchAndCreateOutputPipe() 方法用一个 peer 名作为参数,并为这个 peer 创建一个输出管道。


清单 19. searchAndCreateOutputPipe() 方法
    private void searchAndCreateOutputPipe(String peerName) {
        try {
            JXTASearch jxtaSearch = new JXTASearch (netPeerGroup);
            PipeAdvertisement pipeAdvert = jxtaSearch.getPipeAdvertisement (peerName);
            if (pipeAdvert != null) 
                outputPipe = jxtaSearch.getOutputPipe(pipeAdvert);
        }//try
        catch ( Exception ex ) {
            ex.printStackTrace ();
        }//catch
    }//searchAndCreateOutputPipe()

创建输出管道与创建输入管道的过程类似。创建输入管道时,首先创建一个 PipeAdvertisement 对象,然后发布它。但是在创建输出管道对象时,首先搜索已经发布的PipeAdvertisement 对象。得到PipeAdvertisement 对象后,可以用与创建输出管道类似的过程创建输入管道。

因此,创建输出管道包括两步:搜索已发布的管道广告,然后用管道广告创建输出管道。在 JXTASearch 类中已经有了完成这两项任务的函数。

调用 JXTASearch 类的 getPipeAdvertisement() 方法,同时传递 peer 的名字。getPipeAdvertisement() 方法返回PipeAdvertisement 对象的名字。

在第二步中,调用 JXTASearch 类的 getOutputPipe() 方法。getOutputPipe() 方法采用了PipeAdvertisement,并返回一个OutputPipe 对象。

已经介绍过 Listener 类中的辅助方法是如何创建输入和输出管道的。现在,来看一下 Listener 类如何使用这些辅助方法。

Listener 构造函数

清单 20 展示了 Listener 构造函数的实现。


清单 20. Listener 构造函数
    public Listener ( String userName, 
                      String connectionFactoryName ) {
        try {
            //Preparing for JMS.
            javax.naming.InitialContext jndiContxt = new InitialContext();
            queueConnFactory = 
                 (QueueConnectionFactory) jndiContxt.lookup (connectionFactoryName);
    
            Queue incomingQueue = (Queue) jndiContxt.lookup ("jms/"+userName);
            QueueConnection queueConnection = 
                  queueConnFactory.createQueueConnection ();
            QueueSession queueSession = 
                  queueConnection.createQueueSession ( false, 
                                                       Session.AUTO_ACKNOWLEDGE
                                                     );
            queueReciver = queueSession.createReceiver ( incomingQueue );
            queueConnection.start();
            //Preparing for JXTA.
            netPeerGroup = PeerGroupFactory.newNetPeerGroup ();
            router = new Router( userName, netPeerGroup );
            createInputPipe (userName + "JMS");
            searchAndCreateOutputPipe(userName + "J2ME");
            
        }//try
        catch ( Exception ex ) {
            ex.printStackTrace ();
        }//catch
    }//Listener

Listener 构造函数为监听 JMS 和 JXTA 网络作准备。它采用了两个参数:用户名和 JMS 连接工厂的名字。用户名指定当前使用 JXTA4JMS 的用户的名字(如 Alice)。对于 JMS 和 JXTA ,这个名字是相同的。

连接工厂的名称指出 Listener 类用哪一个连接工厂执行 JMS 网络上的搜索操作。一些高层应用程序(如清单 21 所示的简单main() 方法)在实例化Listener 对象时会指定这两个参数。


清单 21. main() 方法
    public static void main(String argv[])
    {
        if ( argv.length < 2 )
        {
            usage();
            return;
        }//if ( argv.length < 2)
        
        Listener listener = new Listener ( new String (argv[0]), 
                                           new String (argv[1])
                                          );
        Thread listenerThread = new Thread (listener);
        listenerThread.start();
    }//main()

Listener 构造函数首先准备开始监听 JMS 网络。为此,它要执行以下操作:

  1. 第一步是用 JNDI 搜索连接工厂。搜索连接工厂与搜索 JMS 队列相同(在讨论 JSMSearch 类的 getQueueSender() 方法时解释过)。只需实例化一个InitialContext 对象,并调用它的lookup() 方法即可。

  2. 现在,搜索 Listener 类要监听的 JMS 队列。这个队列叫做 incomingQueue。注意,前面getQueueSender() 方法中创建的outgoingQueue 对象是向其发送 JXTA 消息的对象。在这里搜索的incomingQueue 对象将监听收到的 JMS 消息的队列。这意味着,当 Alice 在路上并带着她的移动端 JXTA4JMS 时,桌面端 JXTA4JMS 将持续监听她的incomingQueue 对象上接收的 JMS 消息。当 JXTA4JMS 在incomingQueue 上收到传入的消息(如来自 Bob)时,它会将这条消息发送 Alice(或者她的移动端 JXTA4JMS)。

  3. 下一步是用连接工厂(一个 QueueConnectionFactory 对象)实例化一个新的 JMS 连接。一个 QueueConnection 对象表示了这个连接。可以简单调用QueueConnectionFactory 对象的createQueueConnection() 方法,它没有任何参数,并且会返回一个QueueConnection 对象。

  4. 现在,用 QueueConnection 对象创建一个 JMS 队列会话。可以对同一个连接创建几个队列会话。要创建新的 JMS 会话,需要调用QueueConnection 对象的createQueueSession() 方法。

  5. 然后用这个 QueueSession 对象创建一个 QueueReceiver 对象。调用 QueueSession 对象的 createReceiver() 方法,同时传递 incomingQueue 对象。createReceiver() 方法将返回一个QueueReceiver 对象。用QueueRecveiver 对象接收来自 incomingQueue 的消息。

    回想一下在讨论 JMSSearch 类的 getQueueSender() 方法时,我们说到需要一个 QueueSender 对象,以便向队列发送消息。QueueSession 对象同时创建了 QueueSenderQueueReceiver 对象。

    注意,要使用两个不同的 QueueSession 对象创建 QueueSenderQueueReceiver 对象,因为使用了不同的队列发送和接收消息。现在还要创建一个接收器(receiver),因为必须检查只从某一个队列传入的消息(即用户当前使用 JXTA4JMS 实现的队列,也就是 Alice)。另一方面,用JMSSearch 类的createQueueSender() 方法创建的 QueueSender 是用于接收 JXTA 消息的特定 JMS 客户机(Bob)的。

Listener 构造函数已经可以监听或者接收来自用户队列的 JMS 消息了。现在,让我们看看 Listener 构造函数如何接收来自 JXTA 网络的消息。

要创建一个输入管道来接收来自移动客户机的 JXTA 消息。与此类似,还要创建一个输出管道向移动客户机发送消息。注意,我们已经知道输入和输出管道的名称,因为这两个管道表示当前使用 JXTA4JMS 实现的用户。输入和输出管道名的后缀不同。将后缀 “JMS” 分配给输入管道名,因为输入管道表示桌面端 JXTA4JMS 管道,移动端 JXTA4JMS 使用这个名字搜索桌面端 JXTA4JMS 管道。将后缀“J2ME” 附加到输出管道名上,因为输出管道表示移动端 JXTA4JMS 管道,桌面端 JXTA4JMS 使用这个名字搜索移动端 JXTA4JMS 管道。

Listener 构造函数用下面的步骤创建输入和输出管道:

  1. 第一步是实例化 PeerGroup 对象。我们是在默认网络 peer 组中工作,因此要创建一个 NetPeerGroup 对象,而不是创建一个PeerGroup 对象。NetPeerGroupPeerGroup 类的子类,因此, 可以用它来取代PeerGroup 类。

  2. 现在可以通过调用 Router 的构造函数来实例化 NetPeerGroup 对象。如前所述,Router 类的构造函数采用用户名和一个PeerGroup 对象作为参数。

  3. 在第三步是要创建一个将在 JXTA 网络上发布(或者广告)的 JXTA 管道。这是 J2ME 移动客户机用来向桌面客户机发送消息的输入管道。

  4. createInputPipe() 方法(已经讨论过)执行创建一个输入管道所需要的所有步骤。只需调用 createInputPipe() 方法并向这个方法传递用户名即可。

  5. 最后一步是搜索和创建一个输出管道。桌面客户机用这个管道向移动客户机发送消息。要创建输出管道,可以使用 searchAndCreateOutputPipe() 方法,我在前面讨论过这个方法。

我已经介绍了 Listener 构造函数所进行的所有准备工作。现在,看一下 Listener 类是如何用它们进行 JXTA4JMS 通信的。

run() 方法

我将 Listener 中的 run() 方法编写为在单独的线程中运行,如清单 22 所示。


清单 22. run() 方法
    public void run()
    {
        while( true ) {
            int counter = 0;
            while ( counter <= 10 ) {
                try {
                    javax.jms.Message message = queueReciver.receive (1000);
                    if ( message != null ) {
                        if ( message instanceof TextMessage ) 
                        {
                            TextMessage txtMsg = (TextMessage) message;
                            router.sendMessageToMobile ( 
                                            txtMsg.getStringProperty("Sender"), 
                                            outputPipe, 
                                            txtMsg 
                                            );
                        }
                    }//if ( message != null ) 
                    counter = counter + 1;
                }//try
                catch ( Exception ex ) {
                    ex.printStackTrace ();
                }//catch
            }//while (counter <= 10)
            counter = 0;
            while ( counter <= 5 ) 
            {
                net.jxta.endpoint.Message msg = null;
                try {
                    msg = inputPipe.poll ( 1000 );
                    if ( msg != null )
                        router.sendMessageToJMS( queueConnFactory, msg );
                    counter = counter + 1;
                }//try
                catch ( InterruptedException iEx ) {
                    iEx.printStackTrace ();
                }//catch
            }//while (counter <= 5)
            counter = 0;
            
        }//while(true )
    }//run()

run() 方法不断监听 JMS 和 JXTA 上收到的消息。当它接收到来自 JMS 客户机的消息后,它会将消息转发给移动 J2ME 客户机。与此类似,当它接收到来自移动客户机(或者 JXTA 网络)的消息后,它会将消息发给 JMS 接收者。

run() 方法首先是一个无限 while 循环,我在其中编写了两个 inner while 循环。第一个循环监听来自 JMS 客户机的消息,第二个 while 循环监听来自移动客户机的消息。循环中的逻辑很简单。该方法使用Router 类正确地路由消息。


实现移动端 JXTA4JMS

我在 JXTA-for-JMS 架构中讨论 JXTA4JMS 架构时介绍了不同 J2ME 类在移动端 JXTA4JMS 中的作用。现在,我将分析这些类的实现细节。

实现 J2ME 端的 JXTA4JMS 时,要使用 JXME 类(PeerNetworkMessageElement)。在本系列的第一篇文章中,我已经描述了这些类。

JXTA4JMSMessage 类

JXTA4JMSMessage 类生成并处理 JXTA4JMS 消息。一个 JXTA4JMS 消息是一个可以定制的 JXME 消息。应用程序在想要向桌面 peer 发送或者接收来自它的消息时就使用JXTA4JMSMessage 类。

回顾一下,在第一篇文章的“中继器与 JXME 客户机之间的消息传递” 一节中,所有 JXME 消息都是由元素组成的。所有 JXME 消息都包含某些共同的元素(例如,在第一篇文章中讨论的清单 3 中的 EndpointSourceAddress 元素)。此外,JXME 消息可以包含其他应用程序特定元素。JXTA4JMS 消息包含下面三个特定于 JXTA4JMS 的元素中的两个:

  • Message 元素包装消息的内容。这个元素总是出现在所有 JXTA4JMS 消息中。

  • JMSSender 元素包装发送这个消息的 JMS 用户的名字。当然,这个元素只出现在从桌面端 JXTA4JMS 收到的消息中。

  • JMSRecipient 元素包装 JXTA4JMS 消息接收者的 JMS 用户名。可以想像得到,这个元素只出现在发送给桌面端 JXTA4JMS 的输出消息中。

JXTA4JMSMessage 对象可以表示输入或者输出消息。JXTA4JMSMessage 类有两个构造函数:其中一个构造函数有一个参数,另一个构造函数有两个参数。有一个参数的构造函数帮助处理收到的消息,而有两个参数的构造函数则生成输出消息。让我们看看它们是如何工作的。

有一个参数的 JXTA4JMSMessage 构造函数

清单 23 显示了有一个参数的 JXTA4JMSMessage 构造函数。它采用了一个 JXME Message 对象作为参数。这个Message 对象表示收到的来自桌面 JXTA4JMS 客户机的消息。Message 对象包含MessageJMSSender 元素。


清单 23. 有一个参数的 JXTA4JMSMessage 构造函数
 
    public JXTA4JMSMessage (Message msg) {
        processMessage(msg);
    }//JXTA4JMSMessage

这个构造函数只是将 Message 对象传递给一个名为 processMessage() 的专用辅助方法。processMessage() 方法提取MessageJMSSender 元素的内容,并将内容分别存储到名为 messageTextsender 的两个类级变量中。

清单 24 展示了 processMessage() 方法,它类似于在第一篇文章的清单 19 中的processMessage() 方法,所以我在这里就不再详细介绍了。


清单 24. processMessage() 方法
 
    private void processMessage (Message msg) {
        try {
            for (int j=0; j < msg.getElementCount(); j++) {
                Element e = msg.getElement(j);
                if ((e.getName()).equals("JMSSender"))
                    sender = new String(e.getData());
                else if ((e.getName()).equals("Message"))
                    messageText = new String (e.getData());
                
            }//for (int j=0;) 
            if (sender != null) {
                Element[] msgElements = new Element [2];
                msgElements[0] = new Element ( 
                                              "JMSSender",
                                              (sender).getBytes(),
                                              null,
                                              null 
                                             );
                msgElements[1] = new Element ( 
                                              "Message", 
                                              messageText.getBytes(), 
                                              null, 
                                              null 
                                             );
                          
                message = new Message ( msgElements );    
           }//if (sender!=null)	
        }//try
        catch(NumberFormatException ne){
            ne.printStackTrace();
        }//catch
    }//processMessage()

存储了两个元素后,processMessage() 方法还生成一个对应于 JMSSender Message 元素内容的 Message 对象。我在第一篇文章的清单 26 介绍了 JXME 消息的生成。

有两个参数 JXTA4JMSMessage 的构造函数

清单 25 展示了有两个参数的 JXTA4JMSMessage 构造函数的实现。两个字符串构成了构造函数的两个参数。第一个字符串(recipient)是 JMS 接收者的名字,第二个字符串(messageText)是要发送的实际消息。


清单 25. 有两个参数的 JXTA4JMSMessage
 
    public JXTA4JMSMessage (String recipient, String messageText) {
       Element[] msgElements = new Element [2];
       this.recipient = recipient;
       this.messageText = messageText;
       msgElements[0] = new Element ( 
                                     "JMSRecipient",
                                     (recipient).getBytes(),
                                     null,
                                     null
                                    );
       msgElements[1] = new Element ( 
                                     "Message", 
                                     messageText.getBytes(), 
                                     null, 
                                     null 
                                     );
                                      
       message = new Message ( msgElements );
    }//JXTA4JMSMessage

这个构造函数将 recipientmessageText 字符串分别存储在名为 recipientmessageText 的两个类级变量中。然后它生成一个对应于接收者和消息字段的Message 对象。

JXTA4JMSMessage 类的 getter 方法

JXTA4JMSMessage 类还包含 4 个 getter 方法,如清单 26 所示:

  • getMessage() 方法返回这两个构造函数所生成的 Message 对象。更高层的类(如 JXMEMIDlet 类)可以使用这个方法获得 Message 对象。

  • getMessageText() 方法返回消息的文本内容。

  • getRecipient() 方法返回输出消息的 JMS 接收者的名字(如果 JXTA4JMSMessage 对象包装了一个输出消息)。

  • getSender() 方法返回输入消息的 JMS 发送者的名字(如果 JXTA4JMSMessage 对象包装了一个输入消息)。

清单 26. JXTA4JMSMessage 类的 4 个 getter 方法
 
    public Message getMessage(){
        return message;
    }//getMessage()
    public String getSender(){
        return sender;
    }//getSender()
    
    public String getRecipient(){
        return recipient;
    }//getRecipient()
    public String getMessageText() {
        return messageText;
    }//getMessageText()

DataStore 类

DataStore 类提供了简单的 J2ME record store 服务。JXTA4JMSMessagingClient 类使用DataStore 类的服务。

DataStore 构造函数(清单 27)采用了 J2ME record store 的名字,并打开它进行读取和写入操作。


清单 27. DataStore 构造函数
    public DataStore(String storeName) {
        try { 
            recStore = RecordStore.openRecordStore ( storeName, true ); 
        } catch ( Exception e ) {
            e.printStackTrace();
        } 
    }//DataStore

DataStore 类还有一个 deleteStore() 方法(如清单 28 所示),它删除一个 J2ME record store。


清单 28. deleteStore() 方法
   public void deleteStore(String storeName) {
        try{
            recStore.closeRecordStore();  
            RecordStore.deleteRecordStore(storeName);
        } catch ( Exception e ) { 
            e.printStackTrace();
        } 
    }//deleteStore()

此外,DataStore 类还有一个 setRecord() 方法,如清单 29 所示,它采用了两个参数,并将它们存储到 J2ME record store 中。这两个参数是管道标识符和管道第一次在 JXTA 网络上发布的时间。JXME 无法帮助您标识某一特定的管道是否已失效,所以您必须自己确定它。


清单 29. setRecord() 方法
    public void setRecord (long time, String pipeId)  {
        String record = (Long.toString(time)) +"@"+ pipeId;
        try {
            recStore.addRecord ( record.getBytes(), 0, record.getBytes().length );
            recStore.closeRecordStore();            
        } catch ( Exception e ){
            e.printStackTrace();
        }//catch
                         
    }//setRecord()

在清单 30 中看到的 getPipeId()getAdvPublishTime() 方法,将从 J2ME record store 中分别提取管道标识符及其发布时间。


清单 30. getPipeId() 和 getAdvPublishTime() 方法
    public String getPipeId() {
        try {
            if ( recStore.getNumRecords() > 0 ) {
                String record = new String( recStore.getRecord (1) );
                int index = record.indexOf("@");
                String pipeId = record.substring (index+1,record.length());
                return pipeId;
            }//if ( recStore.getNumRecords() > 0)
        }//try
        catch (Exception e) {
            e.printStackTrace();
        }//catch
        
        return null;
    }//getPipeId ()
    public long getAdvPublishTime() {
        long timeInMillis = 0L;
        try {
            if ( recStore.getNumRecords() > 0 ) {
                String record = new String( recStore.getRecord (1) );
                int index = record.indexOf("@");
                timeInMillis = Long.parseLong( record.substring (0,index) );
            }
        }//try
        catch (Exception e) {
            e.printStackTrace();
        }//catch
        return timeInMillis;
    }//getAdvPublishTime()

我不想对 J2ME record store 编程进行详细讨论,所以我在参考资料一节中提供了指向一篇不错的 developerWorks 文章的链接。这篇文章展示了如何使用 J2ME record store。

JXTA4JMSMessagingClient 类

JXTA4JMSMessagingClient 类是 J2ME 端 JXTA4JMS 的核心。它为 JXTA4JMS 客户机应用程序提供了所有 JXME 通信功能。这些功能包括:

  • JXTA 中继器连接。
  • 创建输入和输出管道。
  • 搜索(JXTA4JMS 客户机监听的)JXTA 管道。
  • 向桌面 JXTA4JMS 客户机发送消息。
  • 对传入的消息中进行 JXTA 中继轮询。

JXTA4JMSMessagingClient 类使用 PeerNetwork 类通过中继器进行通信。JXTA4JMSMessagingClient 还使用JXTA4JMSMessage 类生成和处理 JXTA4JMS 消息。

JXTA4JMSMessagingClient 类还实现了 Runnable 接口,它只包含一个名为 run() 的方法。run() 方法总是在单独的线程中执行。这个线程将对中继器进行轮询,以检查传入的消息。

高层应用程序(例如 MIDlet)将指定这个线程在多长时间后对中继器进行轮询。在完成轮询后,JXTA4JMSMessagingClient 类向用户显示收到的消息,然后进入睡眠状态,直到下一次轮询。您很快就会看到JXTA4JMSMessagingClient 类是如何用run() 方法实现轮询逻辑的。

JXTA4JMSMessagingClient 类由一个构造函数和一个名为 sendMessage() 的方法组成。此外,它还包含几个专用辅助方法,以便完成底层 JXME 通信。在第一篇文章的“JXME 编程”一节中,我已经讨论了所有 JXME 相关的主题。

JXTA4JMSMessagingClient 构造函数

清单 31 显示了 JXTA4JMSMessagingClient 构造函数,它有 4 个参数:

  • relayURL 参数指定与 JXTA 网络通信所使用的中继器的 URL。

  • peerName 参数指定使用移动端 JXTA4JMS 的 peer 的名字(例如 Alice)。

  • timePeriod 参数是一个表示以毫秒为单位的时间长度的整数。(这是两次轮询之间的时间间隔。JXTA4JMSMessagingClient 类在完成第一次轮询后,等待与时间间隔一样长的时间,然后开始下一次轮询。JXTA4JMSMessagingClient 类不断重复这一过程。)

  • TextField 参数是一个 TextField 对象,它表示在 J2ME 设备屏幕上的一个文本字段。(每次收到消息后,JXTA4JMSMessagingClient 用接收到的消息内容更新这个文本字段。)

清单 31. JXTA4JMSMessagingClient 构造函数
    public JXTA4JMSMessagingClient( 
                          String relayURL, 
                          String peerName, 
                          int timePeriod, 
                          TextField textField )
    {
        this.timePeriod = timePeriod;
        this.textField= textField;
        String pipeName = new String(peerName + "J2ME");
        try {
            byte[] persistentState  = null;            
            peerNetwork = PeerNetwork.createInstance ("J2MEClient");
            persistentState = peerNetwork.connect (relayURL, persistentState);
            DataStore dataStore = new DataStore(pipeName);
            if (dataStore.getPipeId() == null) {
                String pipeId = createPipe(pipeName);
                listenToPipe(pipeId);
                dataStore.setRecord( System.currentTimeMillis(), pipeId);
            }//if(dataStore.getPipeId() == null )
            else
            {
                long difference = 
                     (System.currentTimeMillis() - dataStore.getAdvPublishTime());
                                
                if (difference > expiryTime) {
                    dataStore.deleteStore(pipeName);
                    dataStore = new DataStore(pipeName);
                    String pipeId = createPipe(pipeName); 
                    listenToPipe(pipeId);
                    dataStore.setRecord ( System.currentTimeMillis(), pipeId);
                }//if (difference > expiryTime)
                else
                    listenToPipe(dataStore.getPipeId());
            }//else
            outputPipeId = searchPipe (peerName + "JMS");
        }//try
        catch ( Exception e ) {
            e.printStackTrace();
        }//catch
        
    }//JXTA4JMSMessagingClient

JXTA4JMSMessagingClient 必须首先将 timePeriodtextField 参数存储为类级变量。这样,当JXTA4JMSMessagingClient 轮询中继器来获得传入的消息时,就可以使用这些参数。

然后 JXTA4JMSMessagingClient 构造函数连接到中继器。下一项任务是创建一个输入管道,并让中继器开始监听它。管道的名称是带有后缀“J2ME” 的用户名。(回想一下,在前面对Listener 构造函数的讨论中,当指定管道名时,要求在桌面客户机监听的管道上添加后缀“JMS”,在 J2ME 客户机监听的管道上附加后缀“J2ME”。)

现在要检查 record store,以查看这个用户是否已经有了有效的管道标识符。如果有,那么可以用同样的管道标识符创建一个管道。如果管道标识符不存在或者失效了,那么可以请求中继器发布另一个管道标识符。得到了管道标识符后,就可以请求中继器开始监听到达这个管道的消息了。

完成输入管道后,搜索桌面客户机创建的管道,并监听来自 J2ME 客户机的消息(管道的名称是用户名加上后缀 “JMS”)。对于 J2ME 客户机,这是一个输出管道。

回想一下,在第一篇文章的“JXME 编程”一节中,我讨论过如何请求标识符,如何搜索管道,以及如何请求中继器开始监听管道。JXTA4JMSMessagingClient 类中的三个辅助方法(searchPipe()createPipe()listenToPipe(),如清单 32 所示)可以完成这些任务。


清单 32. searchPipe()、createPipe() 和 listenToPipe() 辅助方法
    private String searchPipe(String pipeName) {
        String pipeId = null;    
        try {
            int messageID = peerNetwork.search (
                                        PeerNetwork.PIPE,
                                        "Name", 
                                        pipeName, 
                                        -1);
            Message msg = null;
            
            do {
                msg = peerNetwork.poll(5000);
                if (msg != null) {
                    pipeId = processMessage(msg, messageID, "result");
                    return pipeId;
                }//if (msg != null)
            } while (msg!=null);
        }//try            
        catch (IOException ie) { 
            ie.printStackTrace();
        }//catch
        return null;
    }//searchPipe()
    private String createPipe(String pipeName) {
        String pipeId = null;    
        try {
            int messageID = peerNetwork.create (
                                     PeerNetwork.PIPE, 
                                     pipeName, null, 
                                     PeerNetwork.UNICAST_PIPE);
            Message  msg = peerNetwork.poll(5000);
            if (msg == null)
                return null;
            
            pipeId = processMessage(msg, messageID, "success");
        }//try 
        catch (IOException ie) {
            ie.printStackTrace ();
        }//catch
        return pipeId;
    }//createPipe()
    private String listenToPipe (String pipeId) {
        String listenOk = null;
        try {
            int messageID = peerNetwork.listen (pipeId);
            Message msg = peerNetwork.poll(5000);
            if (msg == null)
                return null;
            listenOk = processMessage (msg, messageID, "success");
        }//try 
        catch (IOException ie) {
            ie.printStackTrace ();
        }//catch
        
        return listenOk;
    }//listenToPipe()

sendMessage() 方法

清单 33 中的 sendMessage() 方法采用了 JXTA4JMSMessage 对象作为参数,并通过在JXAT4JMSMessagingClient 构造函数中创建的输出管道向桌面 peer 发送消息。

这个 JXTA4JMSMessage 的最终接收者是一位 JMS 用户,他的信息存储在 JXTA4JMSMessage 对象中。如JMS 客户机向 J2ME 客户机通信中所述,消息首先到达中继器,在这里,它被转换为 JXTA 格式,然后,中继器将消息转发给桌面 JXTA4JMS 客户机,在那里,消息被转换回 JMS 格式,最后,桌面 JXTA4JMS 客户机将消息发送到最终接收者的 JMS 队列中。


清单 33. sendMessage() 方法
    public void sendMessage (JXTA4JMSMessage message) {
        try {
            int messageID = peerNetwork.send (outputPipeId, message.getMessage());
            Message  msg = peerNetwork.poll(5000);
            if (msg != null)
                processMessage(msg, messageID, "success");
        }//try
        catch (IOException ie){
            ie.printStackTrace();
        }//catch
    }//sendMessage()

run() 方法

JXTA4JMSMessagingClient 实现了 Runnable 接口,它只有一个名为 run() 的方法。这个方法定期轮询中继器,以检查是否有收到的消息。

run() 方法实现(如清单 34 所示)很简单。它轮询中继器,然后进入睡眠状态,睡眠的时间是 JXTA4JMSMessagingClient 构造函数的timePeriod 参数所指定的时间。睡眠时间结束后,它再次轮询中继器。

当轮询接收到消息后,它使用 JXTA4JMSMessage 类处理消息,并提取消息的内容。最后,它在 textField 对象上向用户显示收到的消息,这个对象将作为JXTA4JMSMessagingClient 构造函数的最后一个参数传递。

run() 方法在单独的线程中执行。这意味着轮询机制的运行与消息发送是独立的。


清单 34. run() 方法
    public void run() {
        Message msg = null;
        String message = null;
        JXTA4JMSMessage jxmeMsg = null; 
        stringBuffer = new StringBuffer();
        while( true ) {
            try {
                Thread.sleep(timePeriod);
                msg = peerNetwork.poll (2000);
                if (msg != null) {
                    jxmeMsg = new JXTA4JMSMessage (msg);
                    if ((jxmeMsg.getMessage()!=null) &&
                                   (jxmeMsg.getSender()!=null))
                    {
                        textField.setLabel("Message From: "+jxmeMsg.getSender());
                        textField.setString(jxmeMsg.getMessageText());
                    }
                }//if(msg != null)
            }//try
            catch (Exception e) {
                e.printStackTrace();
            }//catch
        }//while(true)
    }//run()

JXMEMIDlet 类

在本文的源代码下载中,我加入了一个名为JXMEMIDlet 的示例 MIDlet。这个示例 MIDlet 展示了移动端 JXTA4JMS 的简单使用方法。

清单 35 显示了 JXMEMIDlet 构造函数,它实例化一个 JXTA4JMSMessagingClient 对象,向JXTA4JMSMessagingClient 构造函数传递硬编码的 peer 名、中继器地址、轮询的时间间隔值和一个文本区对象。


清单 35. JXMEMIDlet 构造函数
    public JXMEMIDlet() {
        try {
            tfMessageFromSender = new TextField ( "", "", 40, TextField.UNEDITABLE);
            jxmeMessagingClient = new JXTA4JMSMessagingClient(
                                         relayURL, 
                                         "Alice", 
                                         15000, 
                                         tfMessageFromSender);
           showMessagingForm();
        }//try
        catch ( Exception e ){
            e.printStackTrace();
        }//catch
        
    }//JXMEMIDlet

清单 36 中看到的 JXMEMIDletstartApp() 方法首先将实例化一个 Thread 对象,然后调用 Thread.start() 方法启动轮询线程。


清单 36. startApp() 方法
    public void startApp(){
        Thread thread = new Thread (jxmeMessagingClient );
        thread.start();
    }//startApp()

在清单 37 中可看到 JXMEMIDletcommandAction() 方法,它展示了如何用 JXTA4JMS 向 JMS 网络发送消息。


清单 37. commandAction() 方法
    public void commandAction (Command c, Item item) {
        if ( c == CMD_SEND ) {
            try {
                if (tfRecipientName.getString().equals( "" )) {
                    Alert alert = new Alert(
                          "Error", 
                          "Please enter recipient name for sending message", 
                          null, 
                          AlertType.ERROR
                          );
                    alert.setTimeout( 1000 );
                    display.setCurrent ((Screen) alert);
                
                }//if (tfRecipientName.getString().equals( "" ))
                else {
                   JXTA4JMSMessage message = new JXTA4JMSMessage( 
                                               (tfRecipientName.getString()),
                                               tfMessageForRecipient.getString());
                   jxmeMessagingClient.sendMessage (message);
                }
            }//try
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }//if (c == CMD_SEND)
    }//commandAction()

图 8 显示了接收和生成消息的用户界面的屏幕快照。当 JXTA4JMS 通过轮询线程接收到消息时,发送者的名字(例如 Bob)出现在“Message From:”字段中。收到消息的内容出现在下一行。

当 Alice 想要向 JMS 用户(如 Bob)发送消息时,她在“Message To:”字段中键入 JMS 用户的名字,在“Message”字段中键入消息,然后按 Send。JXTA4JMS 就会将消息传给 JMS 接收者。


图 8. 消息接收和生成用户界面
消息接收和生成用户界面。

为了更容易试验 JXTA4JMS,我编写了一个简单的 JMS 测试客户机应用程序,并将它添加到了本文的源代码下载中。这个 JMS 客户机应用程序可以表示一个 JMS 用户。源代码下载中的 readme 文件解释了如何测试 Alice 与 Bob 之间的 JXTA4JMS 通信。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值