jxta[/url]探索:双向管道(Bi-directional Pipe)的实现和原理剖析
* 什么是管道(Pipe)?
管道是[url=http://www.jxta.org]jxta[/url]里面比较重要的一个概念。管道是Peer之间的虚拟通道。通常,我们认为对等通信是单个的通信连接。但是也并不是总是这样的。因为防火墙、NAT和其它障碍的存在,许多Peer并不能直接连接。这时,管道更像一个在多种通信协议之上的虚拟层,可以通过起网关作用的Peer对通信提供中继支持。
使用它的好处在于,不用去关心Peer所使用的真正的地址和协议是什么,使用抽象出来的管道,可以为P2P应用提供强大的功能并降低复杂性。
* 为什么要分析双向管道的实现原理?
在JXTA的J2SE参考实现中,提供了三种最基本类型的管道,它们分别是:
a 单向管道(UnicastType)
b 单向安全管道(UnicastSecureType)
c 广播管道(PropagateType)
而实际应用中,可以通过基本类型的管道的组合形成一些更有用的类型,如双向管道(Bi-directional Pipe)。因为我们知道在P2P应用中,如聊天,传送文件等操作都需要实现双向管道。在JXTA的J2SE参考实现中提供了两个有用的类(JxtaBidiPipe和JxtaServerPipe),通过一种巧妙的机制,实现了一个双向管道。
尽管在使用双向管道时,不需要了解内部的实现方法,但是我认为理解双向管道的实现机理对于使用双向管道有一定的帮助,对于进一步分析[url=http://www.jxta.org]jxta也是有益的,希望通过下面的分析,能给jxta应用的开发者一些有益的提示,同时也作为本人的一个参考。
因为jxta在推出后一直在不断的发展中,2.0和1.0版本之间出现了很多的不一致,因此声明,本文列出的代码均基于最新的JXTA2.0 API,在Jxta的J2SE参考实现2.3.1版本上调试通过。
* 用JXTA建立双向管道的过程
编写一个双向管道的应用程序需要用到如下两个类:JxtaBidiPipe和JxtaServerPipe,它们共同实现了一个握手协议,即 使用一个由连接双方共同填写的结构化xml文档,在jxta[/url]里用Message来描述这个文档, 该Message有以下内容
<Credential>决定请求者是否有获得连接的权利<Credential>
<reqPipe>请求者的管道广告(pipe advertisement)</reqPipe>
<remPipe>远程的管道广告(remote pipe advertisement)</remPipe>
<remPeer> 远程的节点广告(remote peer advertisement)</remPeer>
<reliable> 是否可靠性连接 ("true", or "false") </reliable>
<data> 通讯数据 <data>
而实际建立和使用双向管道的过程中,并不需要了解这个Message的,这里只是为后面分析建立的原理作准备。
我们知道,即使是在P2P系统中,也有一个连接发起节点(这里设定为PeerA),和一个连接接受节点 (这里假定为PeerB)
首先在发起节点A在程序当中使用如下语句发起JxtaBidiPipe连接:
try {
FileInputStream is = new FileInputStream("pipe.adv");
eg.pipeAdv =
(PipeAdvertisement)AdvertisementFactory.newAdvertisement(MimeMediaType.XMLUTF8, is);
is.close();
System.out.println("creating the BiDi pipe");
eg.pipe = new JxtaBiDiPipe();
eg.pipe.connect(eg.netPeerGroup,
null,
eg.pipeAdv,
180000,
// register as a message listener
eg);
} catch (Exception e) {
System.out.println("failed to read/parse pipe advertisement");
e.printStackTrace();
System.exit(-1);
}
在节点B处用如下方法响应这个连接:(这里用到了JxtaServerPipe,这是一个辅助类,它的行为非常类似JDK开发中常用的类ServerSocket)
try {
FileInputStream is = new FileInputStream("pipe.adv");
eg.pipeAdv =
(PipeAdvertisement) AdvertisementFactory.newAdvertisement(MimeMediaType.XMLUTF8, is);
is.close();
eg.serverPipe = new JxtaServerPipe(eg.netPeerGroup, eg.pipeAdv);
// we want to block until a connection is established
eg.serverPipe.setPipeTimeout(0);
JxtaBiDiPipe bipipe = serverPipe.accept();
if (bipipe != null ) {
System.out.println("BiDi Pipe created");
receiveAndSendTestMessage(bipipe);
}
} catch (Exception e) {
System.out.println("failed to read/parse pipe advertisement");
e.printStackTrace();
System.exit(-1);
}
建立了双向管道的连接后,就可以进行简单的通讯了。
注意到,这里JxtaBidiPipe和JxtaServerPipe的行为非常类似JDK里面的Socket和ServerSocket。我想这是JXTA[url=http://www.jxta.org]的设计者有意为之,目的是使得Java开发者更方便的使用这些类。
* jxta[/url]双向连接对象(JxtaBidiPipe类型对象)的使用
可以用isBound()来判断出JxtaBidiPipe对象的双向连接是否建立。
可以用getMessage()来接收JxtaBidiPipe上的消息。
可以用sendMessage()在JxtaBidiPipe上发送消息。
* 双向管道建立的原理的源代码剖析
在分析之前,现分析一下[url=http://www.jxta.org]jxta[/url]里面一个单向管道的大致建立过程,因为双向管道实际上就是两个双向管道的组合。
单向管道的建立过程比较简单(以两个节点PeerA和PeerB为例)
(1)首先PeerA根据一个管道的广告建立一个输入管道(Input Pipe) 等待连接;
(2)PeerB根据同样的广告建立一个输出管道(Output Pipe);
(3)如果PeerB的输出管道建立成功,则PeerB会收到相应的连接已经建立的提示(通过响应outputPipeEvent),这时一个从B到A的单向连接就建立起来了,在A中可以通过响应pipeMsgEvent来得到B发送过来的消息的提示,并得到消息的内容。
这样实现的一个单向管道除了只能单向传输外,还有一个显著的缺点在于它要求通信双方必须使用一个共同的管道广告(广告一般用一个组内发布的xml文档来表示),而且在双方发送消息的时候,该文档定义的管道广告一直被使用,不能再用来建立新的连接。
如果按照一般的思路,建立双向管道就需要使用两个共用的管道广告,并在双向通信过程中一直占用这两个管道广告。
而实际上,JXTA里面实现的双向管道,只在初始时使用了一个共用的管道广告PublicPipeA2B advertisement(可以考虑使用组内的服务实现),而后通过巧妙的机制,建立了两个内部使用的管道广告(即不用发布, 分别为privatePipeB2A和privatePipeA2B的advertisement),并用这两个内部管道广告实现了双向通信,并且通信建立以后,原先的共用的管道广告对于该通信过程就无用了,还可以被别的通信管道再次利用。
下面按照时间顺序,分析一个双向管道的建立过程:(序号后面的字母表明是在哪个节点进行,如”1B”表示该动作发生在Peer B)
(1B)节点B处新建一个JxtaServerPipe对象,JxtaServerPipe本身是一个InputPipe, 在它构造的时候使用共用的广告,即publicPipeA2B的advertisement建立一个输入管道等待连接。
(2A) 节点A新建立一个输入管道Inputpipe(就是privatePipeB2A), 并将这个管道的广告,即privatePipeB2A的advertisement封装到一个消息Message里, 这个Message的消息一共填写了Credential, reqPipe, remPeer, reliable四个部分,我们这里只关注reqPipe部分,这里存放的就是广告privatePipeB2A advertisement(参照JxtaBidiPipe的createOpenMessage方法)
(3A) 节点A用publicPipeA2B的advertisement来建立输出管道OutputPipe ,这样就与节点B建立了一个AàB的单向连接。 连接建立以后, 马上把第(2A)步产生的Message用这个管道publicPipeA2B发送给B, 然后又进入等待。(参照JxtaBidiPipe的connect方法)
(4B) 节点B用上面第(2a)步里面接受的Message里包含的reqPipe部分的pipe advertisement建立一个OutputPipe(就是PrivatePipeB2A), 这时就会连接到上面在第(2A)步建立的PrivatePipeB2A的InputPipe上, 到这一步就建立了一个B到A的内部连接管道privatePipeB2A。(参照JxtaServerPipe的processMessage方法)
(5B) 节点B新建立一个InputPipe(就是privatePipeA2B的输入管道), 并把Message的remPeer部分填写上这个新建立的管道的广告,即privatePipeA2B的advertisement,(实际上填写了Credential, remPipe, remPeer三个部分,我们这里只关注remPeer部分),用第(4B)步建立的连接管道PrivatePipeB2A把这个填写好的Message发回给节点A。(参照JxtaServerPipe的processMessage和sendResponseMessage方法)
(6A) 节点A处接收到这个消息后马上用这个remPeer部分包含的privatePipeA2B 的advertisement建立一个输出管道OutputPipe。到这一步就建立了一个新的A到B的连接管道privatePipeA2B,而先前建立的A到B的管道publicPipeA2B就可以不用继续使用了,然后结束第(3A)步的等待,这时一个包含privatePipeA2B和privatePipeB2A的双向管道,即一个JxtaBidiPipe对象就正式建立(绑定)成功了。
*结束语
还有一对JXTA的类JxtaSocket和JxtaServerSocket,实现的机理和上面的方法大致类似,根据JXTA开发者的说法,JxtaBidiPipe和JxtaServerPipe适用于小数据量应用,如即时消息,聊天信息等;而JxtaSocket和JxtaServerSocket适用于大数据量的通信,如文件传输等。我将在下一篇里分析JxtaSocket和JxtaServerSocket的例子。
本文用到的程序请参照[url=http://www.jxta.org/ProgGuideExamples.zip]http://www.jxta.org/ProgGuideExamples.zip
里面的JxtaBidiPipe一节,
还可以到这里下载JXTA的J2SE参考实现2.3.1版本的源代码:
http://download.jxta.org/build/release/2.3.1/jxta-src-2.3.1.zip
( 感谢曾经在JXTA上的同行的探讨和总结对我的启发,转载请注明出处)
yanqlv
2004/10/23