要完成有用的工作,P2P 應用程序中的對等點必須能夠彼此發現對方並與對方交互。本文提供了一種基於 IP 多播的發現的實現。
在軟件實體能夠參與具有 P2P 應用程序特征的直接的對等交互之前,該實體必須發現將要與之交互的適當的對等點。所有可行的 P2P 體系結構都提供一種針對發現問題的解決方案。在本文我將描述其中一種機制的實現。讓我們通過回顧來開始今天的討論。再訪發現 對等點發現使 P2P 應用程序中的對等點能夠彼此定位以便相互之間可以交互。實現對等點發現服務有多種方法。最簡單的機制是顯式點到點配置。這種機制通過要求每個對等點知道所有它可能與之交互的其它對等點,並與它們相連,來進行工作。點到點配置的主要優點是簡單。它的主要缺點是缺乏靈活性並且缺少擴展到對等點的大型網絡的能力。
發現的另一個公共模型是使用中央目錄作為中介。該模型在許多傳統的、非 P2P 分布式類型的應用程序中間很流行,其優點是很好理解。對等點向中央目錄注冊自己的存在,並使用中央目錄定位其它對等點。這種模型的主要優點是易於管理和擴展的能力。但是,其集中化設計會導致單點故障,因此它對自然力或網上沖浪人數增加所帶來的危害缺乏抵御能力。
許多流行的 P2P 應用程序使用網絡模型而不是中央目錄,在網絡模型中,單個對等點只知道局域網絡上的對等點身份。每個對等點都作為那些與之相連的對等點的目錄。對等點通過向相鄰對等點傳播目錄查詢並返回相關的響應來進行合作。這種模型的主要優點是沒有集中化。它的主要缺點是由於傳播查詢耗費了大量的網絡和處理能力。
上面三種機制有無數種變體。不討論這些變體了,讓我們繼續前進並研究另一種發現機制。
IP 多播發現
就每個對等點維護自己的目錄這點而言,多播模型類似於網絡模型。但是,對等點不通過合作來實現大規模網絡查詢。另外,對等點利用網絡本身提供的特性(IP 多播)來定位和標識其它對等點。
IP 多播是無連接和不可靠的(不象 TCP/IP 是面向連接和可靠的)。雖然它使用 IP 數據報;但是不象單播 IP 數據報那樣是從一台主機發送到另一台主機,多播 IP 數據報可以同時發往多台主機。
對等點定期使用 IP 多播來宣布自己的存在。宣布包含了它們的主機名和一個用於正常通信的端口。對此消息感興趣的對等點檢測這個消息後,抽取出主機名和端口號,並使用該消息建立一個通信通道。
回顧已經足夠了。讓我們開始研究代碼吧。
簡單的客戶機與服務器
我們將從一個簡單的示例開始,該示例演示了兩個進程如何使用 IP 多播進行通信。為了簡化演示,我將分別從客戶機和服務器進程這兩個方面來介紹示例。P2P 應用程序通常會實現這兩個進程,將它們劃分為客戶機或服務器並不容易。
在本例中,服務器進程進行循環並等待數據報包的到來。每接收到一個包,服務器就會向控制台打印一條簡短的診斷消息。客戶機角色要簡單得多 — 它多播單個數據報包並退出。
清單 1 和 2 說明了這兩部分是如何組合在一起的。代碼中的注釋說明了正在發生的事情。
清單 1. 簡單服務器
public
class Server
{
public
static
void
main(String [] arstring)
{
try
{
// Create a multicast datagram socket for receiving IP
// multicast packets. Join the multicast group at
// 230.0.0.1, port 7777.
MulticastSocket multicastSocket = new MulticastSocket(7777);
InetAddress inetAddress = InetAddress.getByName("230.0.0.1");
multicastSocket.joinGroup(inetAddress);
// Loop forever and receive messages from clients. Print
// the received messages.
while (true)
{
byte [] arb = new byte [100];
DatagramPacket datagramPacket = new DatagramPacket(arb, arb.length);
multicastSocket.receive(datagramPacket);
System.out.println(new String(arb));
}
}
catch (Exception exception)
{
exception.printStackTrace();
}
}
}
清單 2. 簡單客戶機
public
class Client
{
public
static
void
main(String [] arstring)
{
try
{
// Create a datagram package and send it to the multicast
// group at 230.0.0.1, port 7777.
byte [] arb = new byte [] {'h','e','l','l','o'};
InetAddress inetAddress = InetAddress.getByName("230.0.0.1");
DatagramPacket datagramPacket =
new DatagramPacket(arb, arb.length, inetAddress, 7777);
MulticastSocket multicastSocket = new MulticastSocket();
multicastSocket.send(datagramPacket);
}
catch (Exception exception)
{
exception.printStackTrace();
}
}
}
java.net 包中的兩個類使它運行。java.net.DatagramPacket 類保存了 IP 數據報包中包含的數據。java.net.MulticastSocket 類創建一個調整到一個特定多播組的多播套接字。
發現組件
盡管上述示例是一個很好的 IP 多播的演示,但它沒有說明實現基於 IP 多播的對等點發現需要什麼。要使它有用,我們需要一個功能不僅限於發送和接收包的軟件組件。理想情況下,這個組件將了解它所接收的包的源對等點,並適當地丟棄一些信息,這些信息是關於那些它認為已經消失、死亡或以其它方式離去的對等點的。
在這個新設計中,對等點是一個多播組的成員。請牢記,發送到多播組的消息會透明地路由到該組的所有成員。
設計包括兩個核心類和三個接口(我使用術語“接口”似乎不太嚴謹 — 在技術上是一個接口和兩個抽象類)。Member 類的實例是一個多播組的成員。這個類管理所有的通信細節。MemberManager 類的一個實例負責了解參與多播組的其它成員。
對等點通過向多播組發送一個消息,來向屬於多播組中的對等點宣布自己的存在。每個消息包含關於發送消息的對等點的信息 — 通常是主機名和用於正常(與發現無關)通信的端口。Member 類和 MemberManager 類對這些消息的內容幾乎一無所知。對該信息的訪問權屬於使用這兩個類的應用程序。
有三個接口跨越了消息傳遞/發現層和使用它的應用程序層之間的邊界。它們是 Reference 抽象類、Message 接口和 MessageFactory 抽象類。應用程序必須提供這三個接口的實現。
Reference 抽象類定義了對多播組成員的引用。MemberManager 類管理一個引用集。應用程序將實現這個類的一個具體版本,它將包含應用程序所需要的任何引用邏輯。該類定義了兩個方法,名稱是 equalsInternal() 和 hashCodeInternal(),並且重新定義了 equals() 和 hashCode() 方法來調用這些方法。它通過這樣做來強制實現者為這兩個關鍵功能提供實現 — MemberManager 依賴於它們。
Message 接口定義了通過網絡代碼交換的消息數據的應用程序視圖。應用程序將該消息看作是相對於應用程序運行范圍的高級概念 — 類似於主機名和端口的概念。網絡代碼希望發送一個由字節組成的包。Message 接口的實現定義了如何將這些高級信息與字節相互轉換。引用是信息的一個關鍵部分,所有消息都必須包含,因此該接口要求實現提供用於讀和寫 reference 的方法。
問題的最後部分是 MessageFactory 抽象類。這個類定義了生成新的 Message 實例的機制。深藏在 Member 類內的網絡代碼使用一個工廠來創建從多播數據報中抽取出的數據的 Message 實例。每個 MessageFactory 實例擁有一個隨機生成的身份,它使用這個身份來從接收的消息中濾出要發送的消息。
總之,這五個類和接口(Member、MemberManager、Reference、Message 和 MessageFactory)構成了一個用於進行對等點發現的簡單框架。當然,還有可以改進的空間。可以很容易地添加一種基於事件的機制,用於向感興趣的偵聽器通知成員的出現或消失。一種用於過濾所接收消息的靈活機制將很有用,但其實現卻比較困難。我將這些建議留作讀者的作業。
Member 類
上面描述的框架的完整源代碼太長了,這裡就不詳細展示了,所以讓我們只研究 Member 類的部分代碼,因為其中包含了操作的大多數內容。更准確地說,操作發生在兩個內部類中:MemberClient 類和 MemberServer 類。
請再次考慮第一個示例。它由一個發送 IP 多播數據報的客戶機和一個接收數據報的服務器組成。在本例中(清單 3 和 4),兩個單獨的應用程序執行這兩項功能。P2P 應用程序中的對等點的行為方式既象客戶機又象服務器,所以我們的 P2P 應用程序應該同時包含兩者才是適合的。
清單 3. MemberClient 類
private
class MemberClient
extends Thread
{
public
void
run()
{
try
{
while (true)
{
try
{
Message message = m_messagefactory.createSendMessage();
Reference reference = message.createReference();
message.writeReference(reference);
message.sync();
byte [] arb = message.getByteArray();
DatagramPacket datagrampacket = new DatagramPacket(arb, arb.length);
datagrampacket.setAddress(m_inetAddress);
datagrampacket.setPort(m_nPort);
MulticastSocket multicastsocket = new MulticastSocket();
multicastsocket.send(datagrampacket);
}
catch (IOException ioException)
{
ioException.printStackTrace();
}
try
{
synchronized (this)
{
wait(m_nPeriod);
}
}
catch (InterruptedException interruptedException)
{
break;
}
}
}
catch (Throwable throwable)
{
throwable.printStackTrace();
}
}
}
清單 3 包含了 MemberClient 類的源代碼。象清單 1 中的客戶機一樣,這個客戶機創建一個 MulticastSocket 實例並使用它來發送一個 DatagramPacket 實例。DatagramPacket 實例包含一個到發送方對等點的引用,該引用是作為字節數組編碼的。只要該對等點還是活動的並在運行,客戶機就會每隔一段常規時間來廣播這條信息。
清單 4. MemberServer 類
private
class MemberServer
extends Thread
{
public
void
run()
{
try
{
MulticastSocket multicastsocket = new MulticastSocket(m_nPort);
multicastsocket.joinGroup(m_inetAddress);
while (true)
{
Message message = m_messagefactory.createReceiveMessage();
byte [] arb = message.getByteArray();
DatagramPacket datagrampacket = new DatagramPacket(arb, arb.length);
multicastsocket.receive(datagrampacket);
message.sync();
if (m_messagefactory.isMine(message) == false)
{
Reference reference = message.createReference();
message.readReference(reference);
m_membermanager.addReference(reference);
}
}
}
catch (Throwable throwable)
{
throwable.printStackTrace();
}
}
}
MemberServer 類在很多方面類似於清單 2 中的服務器。除了創建必需的代碼,使用它從建立的通信(wire)上采集適當的數據報以外,這個服務器還對已編碼的引用進行解碼、創建消息並將消息傳遞到 MemberManager 實例進行保管。干得不錯。
該類的其余部分由用於各種特性的讀方法和寫方法以及用於控制該類的生命周期的 start()和 stop()方法組成。
P2P 應用程序
對等點發現框架完成了,剩下的所有工作就是將它集成到現有的 P2P 應用程序中去了。對原來的 P2P 應用程序的更改相對較少。
首先,在其前身中,P2P 應用程序期望在其特性文件中列出所有已知的對等點。盡管這對演示用途來說很好(請回憶上面討論的點到點配置),並在最多至大約四個對等點的情況下運行良好,但最終它的限制還是太大了。因此,除去了在特性文件中讀取和管理對等點的代碼,並以使用上面的發現機制的代碼取代它。
其次,因為在特性文件中枚舉了對等點,看上去它們就好象持久存在了。現有的應用程序如果假定它們不會消失,有時會僥幸獲得成功。但是,在P2P的真實世界中,事情更易發生變化。新的應用程序進行了重新設計,適於在對等點消失時進行更好的恢復。
更新的源代碼可在參考資料中得到。
結束語
找到對等點只是成功了一半。下一次我將研究一個難題,涉及兩個對等點在現代因特網上的通信,要克服防火牆、網關以及其它一大堆麻煩。