1 引言
管道的概念源於Unix,是不同線程之間直接傳輸數據的基本手段.JDK中java.io包中就有管道類,同時,管道在JXTA中是最基本的概念,是對等點之間的數據傳輸的主要方式.對等管道協議(PBP)明確規範了對等管道的綁定,解析,響應.
本文依次剖析集中式(JDK)和對等環境下(JXTA)管道的實現方式,對比分析其異同,然後嘗試在JXTA中建立一個虛擬的全雙工的管道.
本文的目標是通過對不同環境下管道的實現方式對比分析,來理解為什麼JXTA採用管道作為基本的數據傳輸手段.
2 管道的形象化描述
一個生活中的情景:現在有兩個地區A,B.A是石油生產區,B是石油消費區,現在B地區需要消費A地區的石油,當然可以通過海運,空運獲得,然而最通常的方式是架設輸油管道.如圖所示:
1 引言
管道的概念源於Unix,是不同線程之間直接傳輸數據的基本手段.JDK中java.io包中就有管道類,同時,管道在JXTA中是最基本的概念,是對等點之間的數據傳輸的主要方式.對等管道協議(PBP)明確規範了對等管道的綁定,解析,響應.
本文依次剖析集中式(JDK)和對等環境下(JXTA)管道的實現方式,對比分析其異同,然後嘗試在JXTA中建立一個虛擬的全雙工的管道.
本文的目標是通過對不同環境下管道的實現方式對比分析,來理解為什麼JXTA採用管道作為基本的數據傳輸手段.
2 管道的形象化描述
一個生活中的情景:現在有兩個地區A,B.A是石油生產區,B是石油消費區,現在B地區需要消費A地區的石油,當然可以通過海運,空運獲得,然而最通常的方式是架設輸油管道.如圖所示:
java中流的概念和管道的概念都可以通過此案例闡述,A與B之間連接的就是管道,負責將A的石油向B輸出.A向管道輸出數據(output),B從管道輸入數據(input),可以這樣理解,管道是A的輸出對象,是B的數據源.這裡就產生了三個類:輸出流A,輸入流B,管道.輸入流B負責如何獲取數據(read 操作),輸出流A負責如何消費數據(write操作),管道負責連接它們(connect 操作).其實,在實現時,管道類分解為管道口,管道出口,由入口出口負責連接.在複雜的網路環境中,這種連接方式可以有專門的網路協議負責(例如,JXTA中的PBP,全稱Pipe Bind Protocol).
由以上描述,我們可以清楚知道最原始的管道就是單向的,文章後面介紹的雙向管道,是用兩個單向管道虛擬的,而非真實的連接方式.不難發現管道最關鍵的問題是如何協調輸出(A)與輸入(B).這在不同的網路環境會遇到不同的問題,最簡單的是同一JVM下的不同過程(線程或任務)之間用同步方式傳遞數據.而對等環境下,如何去發現對方就是一個很現實的問題,這僅僅只是問題的其中之一,下面的章節會依次分析.
3 集中式環境下管道的實現
問題的描述:A與B是在同一JVM中,A,B有一方能夠發現另一方的存在,A將數據發往B方,A發送數據與B接收數據是相互獨立的.
現在回到問題的最初:為什麼要使用管道?A只管發送,B只管接受,那麼數據在哪兒呢?經過下面的分析,就會明白管道把管理數據緩衝區的重任交給了他自己,A,B均是圍繞這個緩衝區來啟停線程的,顯然這才是問題的本質.
JDK中,類PipeInputStream(即前面所述的B)與PipeOutputStream(即前面所述的的A)可以很好的解決這一問題.
給出類圖如下.
下面是將類PipeOutputStream的connect方法代碼簡化后給予註釋.
public synchronized void connect(PipedInputStream snk) throws IOException {
sink = snk; //將PipeInputStream的實例作為PipeOutputStream的一個屬性,以便調用
snk.in = -1;//緩衝區的輸入位置,<0表示緩衝區為空
snk.out = 0;//緩衝區的輸出位置
snk.connected = true;
}
連接以後,PipeOutputStream的write操作直接調用sink.receive(b);這樣,對緩衝區buffer的維護,就變成了read()和receive()操作之間的線程同步.JDK對緩衝區的處理非常巧妙,採用了循環列表,它用緩衝區的標誌位的變化來代替數據的移動,類似於生活中的時鐘把線性的時間規範為24小時來表示.這不屬於本文的論述範圍,就不繼續分析了.
read操作,正常情況下,從out位置讀取數據.緩衝區空時進入等待狀態.以輪詢的方式(1秒間隔)來自我釋放.
receive操作,正常情況下,向in位置寫入數據.緩衝區滿時進入等待狀態.同樣,以輪詢的方式(1秒間隔)來自我釋放.
4 JXTA對等管道的實現
通過對JDK的分析,我們可以了解到在集中式環境下,管道的架設方案是比較簡單的.在對等環境下(分散式環境下也類似),出於同樣的目標,遇到的問題卻在急劇的擴大.例如,管道入口和出口之間如何相互發現?數據如何保證在不同的環境下傳送?甚至,對管道本身的概念發生質疑:一定是單入口,單出口嗎?
JXTA規範中,管道是在端點之上的服務或應用之間發送和接收信息的虛擬連接通道,管道提供在對等端點傳輸之上的網路抽象.管道有點到點和廣播兩種通信模式.
JXTA是通過管道廣告來唯一標示管道的,輸出管道要找到與其廣告相同的輸入管道才能發送數據,廣告內容如下
<!DOCTYPE jxta:PipeAdvertisement>
<jxta:PipeAdvertisement xmlns:jxta="http://jxta.org";>
<Id>
urn:jxta:uuid-59616261646162614A787461503250335003093E73074218AE3ABBE08EF3CBE303
</Id>
<Type>
JxtaUnicast
</Type>
<Name>
PipeExample
</Name>
</jxta:PipeAdvertisement>
如果您需要對JXTA管道有實例化的概念,請參考Sing Li的使p2p能進行交互操作:Jxta命令shell ,這篇文章有部分內容專門介紹了如何在通過shell使用管道.本文主要是從編程的視角去看管道是如何實現的.
4.1 客戶視角
Project JXTA : Java Programmer's Guide Chapter7有個例子闡述如何去在對等點之間發送信息,讀者可以到www.jxta.org下載源碼.現在從客戶視角簡要的分析它的傳送原理,要深入的了解可以看下一節的系統視角分析.
該例中,有兩個對等點,並且構建了兩個不同的類:一個負責接收(Pipelistener),一個負責發送(PipeExample).具體的接收次序可以參考時序圖:
類Pipelistener實現了介面PipeMsgListener,類PipeExample實現了介面OutputPipeListener.
由時序圖(這是兩個JVM中的類,
時序符號是獨立標示的)可以清晰的獲知,各個對等點的前1,2步是相互獨立的.各自的第3步,採用回調的方式建立輸入和輸出管道.一旦對等系統探測到對方的存在,就分別觸發各自的事件發送或接收消息.顯然JXTA中管道是非同步的.
調試該常式時,注意先建立輸入管道,然後建立輸出管道.
,輸出管道在一定的時間和次數內探測不到輸入管道的存在,就會主動放棄.否則,容易讓網路系統在這些無休止的探測中癱瘓.
4.2 系統視角
從上面的常式中,可以了解對等管道的創建方法,以及數據流程,但是不能明確對等系統是如何去實現的.JXTA中管道的實現比在JDK中實現要複雜得多,具體的技術標準可以參考對等管道綁定協議(PBP),此協議規範了JXTA中管道的概念,但並沒有涉及到如何去實現,這同樣是所有JXTA協議的特徵.它們的目標是闡述what it is,而把how to do it留給開發者,這樣有利於增強系統的開放性.其中Java參考實現,就是該協議實現的一個案例,以下將具體分析.
看管道實現的類圖(以單播為例):
關鍵的類:
InputPipeImpl :輸入管道的實現類
NonBlockingOutputPipe :輸出管道的實現類
PipeServiceImpl :管道服務的實現類,負責創建輸入輸出管道
PipeResolver :提供管道綁定的解析服務
通過客戶視角的分析,可以得知系統外部是通過PipeServiceImpl來獲取輸入輸出管道.那麼消息是如何在對等系統中通過管道過濾和傳遞的? 從程序實現的角度,涉及到太多的技術細節,JXTA的參考實現中有著龐雜的監聽系統.本文嘗試用一個案例從兩個層次去解析這個問題,兩個層次分別是消息的具體形式,服務和端點協議的具體分發策略.很顯然,這裡我們把注意力放在了管道的架構路徑上,而把如何去架構放在了一邊,我想它們是有先後關係的,並且距離並不遙遠.
5 案例描述
現在假設有兩個對等點alas 和sisal ,在一個區域網內,按照客戶視角那一節的常式sisal先建立輸入管道,alas建立輸出管道.由於同一網內可以用廣播的方式發送查詢信息,可以不設rendevous,並且路由是兩點間的,消息傳遞過程得到了一定的簡化.
6 案例分析
以上案例中,從輸入輸出管道的建立到完成對接並傳輸數據總共有5個步驟:
sisal建立輸入管道
alasl建立輸出管道,需要查找輸入管道,通過廣播向網路發出管道查詢消息
sisal獲得alas的管道查詢消息,通過單播向sisal發出響應表示
alas獲得sisal的響應,通過單播向alas發出數據
sisal獲得數據